前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ASP.NET Core 奇技淫巧之接口代理转发

ASP.NET Core 奇技淫巧之接口代理转发

作者头像
GuZhenYin
发布于 2020-08-13 03:47:57
发布于 2020-08-13 03:47:57
58600
代码可运行
举报
文章被收录于专栏:GuZhenYinGuZhenYin
运行总次数:0
代码可运行

前言

先讲讲本文的开发背景吧..

在如今前后端分离的大背景下,咱的客户又有要求啦~

要前后端分离~ 然因为种种原因..没办法用用纯前端的框架(其实是学习成本高,又没钱请前端开发人员)...

所以最终决定了一种方案..

那就是采用MVC(只处理前端视图层,单纯是为了托管在.net core上)+Webapi的方式来实现前后端分离(讲真,很奇葩)..

那么问题就随之而来了.

现在主流的前端框架都是托管在nodejs上,是通过axios来访问后端API,可以通过配置axios的代理配置(proxyTable)来实现跨域访问.

那么我们的JS运行在MVC上,托管在.net core上..那咋办呢?..没有现成的转发轮子..我们只有自己造了..

所以这就是本篇的背景 - -.~

正文

幸运的是ASP.NET Core 给我们提供了强大的中间件模式.

我们完全可以通过定义一个转发中间件的形式来实现代理接口转发,流程如图:

废话不多说,我们来创建我们的中间件:

一.创建检测约定URL的接口与实现

首先定义一个接口IUrlRewriter 用来检测我们的URL是否有对应前缀,如果有,则产生新的URL地址:

这里我们定义接口是为了方便以后更好的更换注入类来实现快速更换检测前缀的规则.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface IUrlRewriter
{
        Task<Uri> RewriteUri(HttpContext context);
}

实现这个接口,如下(解释都在注释里了):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public class PrefixRewriter : IUrlRewriter
    {
        private readonly PathString _prefix; //前缀值
        private readonly string _newHost; //转发的地址

        public PrefixRewriter(PathString prefix, string newHost)
        {
            _prefix = prefix;
            _newHost = newHost;
        }

        public Task<Uri> RewriteUri(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments(_prefix))//判断访问是否含有前缀
            {
                var newUri = context.Request.Path.Value.Remove(0, _prefix.Value.Length) + context.Request.QueryString;
                var targetUri = new Uri(_newHost + newUri);
                return Task.FromResult(targetUri);
            }

            return Task.FromResult((Uri)null);
        }
    }

二.创建代理转发需要的ProxyHttpClient

创建独立的ProxyHttpClient,主要是为了区分代理转发的httpClient,方便后期添加日志或做别的处理.代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public class ProxyHttpClient
    {
        public HttpClient Client { get; private set; }

        public ProxyHttpClient(HttpClient httpClient)
        {
            Client = httpClient;
        }
    }

三.创建代理转发的中间件

代码如下,中间件嘛,主要就是Invoke方法了,说明可以看注释.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public class ProxyMiddleware
    {
       // private ProxyHttpClient _proxyHttpClient;
        private const string CDN_HEADER_NAME = "Cache-Control";
        private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
        private readonly RequestDelegate _next;
        private readonly ILogger<ProxyMiddleware> _logger;

        public ProxyMiddleware(
               RequestDelegate next,
               ILogger<ProxyMiddleware> logger
               
               )
        {
            _next = next;
            _logger = logger;
            //_proxyHttpClient = proxyHttpClient;
        }

        /// <summary>
        /// 通过中间件,拦截访问,检测前缀,并转发
        /// </summary>
        /// <param name="context"></param>
        /// <param name="urlRewriter"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter, ProxyHttpClient proxyHttpClient)
        {
            var targetUri = await urlRewriter.RewriteUri(context);

            if (targetUri != null)
            {
                var requestMessage = GenerateProxifiedRequest(context, targetUri);
                await SendAsync(context, requestMessage, proxyHttpClient);

                return;
            }

            await _next(context);
        }

        private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage, ProxyHttpClient proxyHttpClient)
        {
       

            using (var responseMessage = await proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
            {
                context.Response.StatusCode = (int)responseMessage.StatusCode;

                foreach (var header in responseMessage.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }

                foreach (var header in responseMessage.Content.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }

                context.Response.Headers.Remove("transfer-encoding");

                if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
                {
                    context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
                }

                await responseMessage.Content.CopyToAsync(context.Response.Body);
            }
        }

        private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
        {
            var requestMessage = new HttpRequestMessage();
            CopyRequestContentAndHeaders(context, requestMessage);
            requestMessage.RequestUri = targetUri;
            requestMessage.Headers.Host = targetUri.Host;
            requestMessage.Method = GetMethod(context.Request.Method);
            return requestMessage;
        }

        private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
        {
            var requestMethod = context.Request.Method;
            if (!HttpMethods.IsGet(requestMethod) &&
                !HttpMethods.IsHead(requestMethod) &&
                !HttpMethods.IsDelete(requestMethod) &&
                !HttpMethods.IsTrace(requestMethod))
            {
                var streamContent = new StreamContent(context.Request.Body);
                requestMessage.Content = streamContent;
            }

            foreach (var header in context.Request.Headers)
            {
                if (!NotForwardedHttpHeaders.Contains(header.Key))
                {
                    if (header.Key != "User-Agent")
                    {
                        if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
                        {
                            requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                        }
                    }
                    else
                    {
                        string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;

                        if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
                        {
                            requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
                        }
                    }

                }
            }
        }

        private static HttpMethod GetMethod(string method)
        {
            if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
            if (HttpMethods.IsGet(method)) return HttpMethod.Get;
            if (HttpMethods.IsHead(method)) return HttpMethod.Head;
            if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
            if (HttpMethods.IsPost(method)) return HttpMethod.Post;
            if (HttpMethods.IsPut(method)) return HttpMethod.Put;
            if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
            return new HttpMethod(method);
        }

四.注入和启用我们的中间件和ProxyHttpClient

我们在Startup的ConfigureServices中添加如下代码,注入我们的HttpClient与IUrlRewriter,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 services.AddHttpClient<ProxyHttpClient>()
            .ConfigurePrimaryHttpMessageHandler(x => new HttpClientHandler()
            {
                AllowAutoRedirect = false,
                MaxConnectionsPerServer = int.MaxValue,
                UseCookies = false,
            }); //注入我们定义的HttpClient
 services.AddSingleton<IUrlRewriter>(new PrefixRewriter("/webapp", "http://localhost:63445"));//这里填写前缀与需要转发的地址

然后在Startup的Configure中,启动我们的中间件,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  app.UseMiddleware<ProxyMiddleware>();

五.测试中间件效果

我们编写前端代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            created: function () {
                this.mockTableData1();
                axios.get("/webapp/api/values/get", "123").then(res => { alert(res.data[0]) });
                axios.post("/webapp/api/values/post",{value: 'david'}).then(res => { alert(res.data.message) });

            }

在另外的WebApi项目,编写接口如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", accstring.ToString() };
        }

        [HttpPost]
        public AjaxResult Post(dynamic value)
        {
            string aaa = JsonConvert.SerializeObject(value);
            return Success("OK");
        } 

效果如下,可以看到我们的视图正确的获取到了返回值:

写在最后

这里我们通过中间件的形式实现了接口的代理转发,在具体的使用过程中肯定还会有一些小问题,而且这里我们只实现了Http的转发.ws的则没有.

如果要使用的话,其实国外有一个开源的项目:https://github.com/ProxyKit, 已经有900多个star了.应该还不错.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-08-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Kafka 负载均衡在 vivo 的落地实践
Kafka 客户端可以使用分区器依据消息的key计算分区,如果在发送消息时未指定key,则默认分区器会基于round robin算法为每条消息分配分区;
2020labs小助手
2022/06/06
8650
Kafka万亿级消息实战
本文主要总结当Kafka集群流量达到 万亿级记录/天或者十万亿级记录/天  甚至更高后,我们需要具备哪些能力才能保障集群高可用、高可靠、高性能、高吞吐、安全的运行。
2020labs小助手
2021/05/18
1.1K0
案例分享 | Yelp 如何在 Kubernetes 上运行 Kafka(第 2 部分 - 迁移)
上一篇文章,我们详细介绍了开发基于 PaaSTA 的新部署模型的架构和动机。现在想分享我们将现有 Kafka 集群从 EC2 无缝迁移到基于 Kubernetes 的内部计算平台的策略。为了帮助促进迁移,我们构建了与集群架构的各种组件接口的工具,以确保该过程是自动化的,并且不会影响用户读取或写入 Kafka 记录的能力。
灵雀云
2022/08/12
1.1K0
案例分享 | Yelp 如何在 Kubernetes 上运行 Kafka(第 2 部分 - 迁移)
Kafka在美团数据平台的实践
总第526篇 2022年 第043篇 Kafka在美团数据平台承担着统一的数据缓存和分发的角色,随着数据量的增长,集群规模的扩大,Kafka面临的挑战也愈发严峻。本文分享了美团Kafka面临的实际挑战,以及美团针对性的一些优化工作,希望能给从事相关开发工作的同学带来帮助或启发。 1. 现状和挑战 1.1 现状 1.2 挑战 2. 读写延迟优化 2.1 概览 2.2 应用层 2.3 系统层 2.4 混合层-SSD新缓存架构 3. 大规模集群管理优化 3.1 隔离策略 3.2 全链路监控 3.3 服务生命周期
美团技术团队
2022/08/26
7420
Kafka在美团数据平台的实践
Kafka监控工具汇总
对于大数据集群来说,监控功能是非常必要的,通过日志判断故障低效,我们需要完整的指标来帮我们管理Kafka集群。本文讨论Kafka的监控以及一些常用的第三方监控工具。
用户6070864
2019/08/27
2K0
Kafka监控工具汇总
FAQ系列之Kafka
“流媒体”:发布者(“生产者”)经常发送的大量消息(想想数万或数十万)。许多订阅者(“消费者”)经常进行消息轮询。
大数据杂货铺
2021/07/27
1.1K0
FAQ系列之Kafka
0891-CDP Private Cloud Base 7.1.8正式GA
八月再见,九月你好,今天是九月一日,新学年开始,Cloudera正式发布CDP Base 7.1.8和Cloudera Manager 7.7.1。这个版本引入了诸多新功能,比如通过EC提升Ozone的存储效率,Cloudera Manager的HA,多NameNode支持,全面支持Impala4.0,Hive性能提升,HDFS/Schema Registry血缘功能的增强,改进Ranger RMS,以及实时平台的全面增强。 1.平台支持增强 1.新的操作系统支持 CDP Private Cloud Ba
Fayson
2022/09/02
1.1K1
10 Confluent_Kafka权威指南 第十章:监控kafka
Apache Kafka有许多针对其操作的度量,这些度量指标非常多,会让人混淆哪些是重要的,哪些是可以忽略的。这些度量的范围从关于通信量总体速率的简单度量,到针对每种请求类型的详细时间度量,再到每个topic和每个分区的度量。他们提供了broker中的每个操作的详细视图,但也可能使你成为负责管理监视系统的人员的缺点。 本节将详细介绍一直要监控的最关键的度量标准,以及如何响应他们。我们还将描述一些再调试问题的时候需要账务的更重要的度量标准,然而,这并不是可用的度量标准的详细列表,因为列表经常发生变化,而且其中有许多只对硬编码的kafka开放人员有用。
冬天里的懒猫
2020/08/03
2.4K0
案例分享 | Yelp 如何在 Kubernetes 上运行 Kafka(第 1 部分 - 架构)
在 Yelp,Kafka 每天接收数百亿条消息来推进数据驱动并为关键业务管道和服务提供支持。我们最近通过在 PaaSTA (Yelp 自己的平台即服务)上运行集群,对 Kafka 部署架构进行一些改进。基于 K8s 的部署利用了 Kafka 的自定义 Kubernetes operator 以及用于生命周期管理的 Cruise Control 。
灵雀云
2022/08/12
6220
案例分享 | Yelp 如何在 Kubernetes 上运行 Kafka(第 1 部分 - 架构)
【夏之以寒-kafka专栏 01】 Kafka核心组件:从Broker到Streams 矩阵式构建实时数据流
Broker:在Kafka中,Broker是Kafka集群中的一个节点,负责处理Kafka中的核心功能。从物理层面来看,Broker可以是单独的一台服务器,也可以是集群中的一个节点。从逻辑层面来看,Broker是Kafka服务端的实现,负责接收生产者发送的消息,并将这些消息转发给消费者。Broker是Kafka实现分布式、高吞吐、高可靠性的关键组件。
夏之以寒
2024/05/26
3230
Kafka监控系统对比
github地址 : https://github.com/smartloli/kafka-eagle
用户5252199
2022/04/18
2K0
Kafka监控系统对比
如何更好地使用Kafka?
点个关注👆跟腾讯工程师学技术 引言| 要确保Kafka在使用过程中的稳定性,需要从kafka在业务中的使用周期进行依次保障。主要可以分为:事先预防(通过规范的使用、开发,预防问题产生)、运行时监控(保障集群稳定,出问题能及时发现)、故障时解决(有完整的应急预案)这三阶段。 事先预防 事先预防即通过规范的使用、开发,预防问题产生。主要包含集群/生产端/消费端的一些最佳实践、上线前测试以及一些针对紧急情况(如消息积压等)的临时开关功能。 Kafka调优原则: 1.确定优化目标,并且定量给出目标(Kafka
腾讯云开发者
2022/11/28
1.2K0
如何更好地使用Kafka?
使用Kafka Assistant监控Kafka关键指标
Kafka Assistant下载地址:http://www.redisant.cn/ka
用户3871926
2022/12/02
1.1K1
如何在CDH集群中部署Kafka Manager
为了能够方便的查看及管理Kafka集群,yahoo提供了一个基于Web的管理工具(Kafka-Manager)。这个工具可以方便的查看集群中Kafka的Topic的状态(分区、副本及消息量等),支持管理多个集群、重新分配Partition及创建Topic等功能。本篇文章Fayson主要介绍如何在CDH集群中部署Kafka-Manager并简单的介绍使用。
Fayson
2018/09/29
4.4K0
保姆级Kafka 降本实用指南
根据 Gartner 的预测,预计在 2021 年,全球终端用户在公共云服务上的支出将在 2020 年的 2700 亿美元基础上增长 23%,达到 3320 亿美元。
大数据老哥
2021/08/25
5120
保姆级Kafka 降本实用指南
Kafka的灵魂伴侣Logi-KafkaManger(3)之运维管控--集群列表
有想进滴滴LogI开源用户群的加我个人微信: jjdlmn_ 进群(备注:进群) 群里面主要交流 kakfa、es、agent、LogI-kafka-manager、等等相关技术; 群内有专人解答你的问题 对~ 相关技术领域的解答人员都有; 你问的问题都会得到回应
石臻臻的杂货铺[同名公众号]
2021/07/14
2860
Kafka系列之高频面试题
ISR是由Leader维护,Follower从Leader同步数据有一些延迟,超过配置的阈值会把Follower剔除出ISR,存入OSR列表,新加入的Follower也会先存放在OSR中。AR=ISR+OSR。
johnny666
2024/09/21
1720
Kafka最佳实践
要确保Kafka在使用过程中的稳定性,需要从kafka在业务中的使用周期进行依次保障。主要可以分为:事先预防(通过规范的使用、开发,预防问题产生)、运行时监控(保障集群稳定,出问题能及时发现)、故障时解决(有完整的应急预案)这三阶段。
星沉
2024/07/25
6800
Kafka最佳实践
读Kafka Consumer源码
这是OpenMessaging-Java项目GitHub上的一段介绍,大致是说OpenMessaging项目致力于建立MQ领域的标准。
林一
2018/07/24
9050
读Kafka Consumer源码
kafka中文文档
之前的版本:0.7.x,0.8.0,0.8.1.X,0.8.2.X,0.9.0.X,0.10.0.X。
gemron的空间
2019/11/04
15.6K0
kafka中文文档
相关推荐
Kafka 负载均衡在 vivo 的落地实践
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验