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

前言

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

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

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

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

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

那么问题就随之而来了.

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

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

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

正文

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

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

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

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

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

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

1public interface IUrlRewriter 2{ 3 Task<Uri> RewriteUri(HttpContext context); 4}

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

1public class PrefixRewriter : IUrlRewriter 2 { 3 private readonly PathString _prefix; //前缀值 4 private readonly string _newHost; //转发的地址 5 6 public PrefixRewriter(PathString prefix, string newHost) 7 { 8 _prefix = prefix; 9 _newHost = newHost; 10 } 11 12 public Task<Uri> RewriteUri(HttpContext context) 13 { 14 if (context.Request.Path.StartsWithSegments(_prefix))//判断访问是否含有前缀 15 { 16 var newUri = context.Request.Path.Value.Remove(0, _prefix.Value.Length) + context.Request.QueryString; 17 var targetUri = new Uri(_newHost + newUri); 18 return Task.FromResult(targetUri); 19 } 20 21 return Task.FromResult((Uri)null); 22 } 23 }

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

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

1public class ProxyHttpClient 2 { 3 public HttpClient Client { get; private set; } 4 5 public ProxyHttpClient(HttpClient httpClient) 6 { 7 Client = httpClient; 8 } 9 }

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

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

1public class ProxyMiddleware 2 { 3 private ProxyHttpClient _proxyHttpClient; 4 private const string CDN_HEADER_NAME = "Cache-Control"; 5 private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" }; 6 private readonly RequestDelegate _next; 7 private readonly ILogger<ProxyMiddleware> _logger; 8 9 public ProxyMiddleware( 10 RequestDelegate next, 11 ILogger<ProxyMiddleware> logger, 12 ProxyHttpClient proxyHttpClient 13 ) 14 { 15 _next = next; 16 _logger = logger; 17 _proxyHttpClient = proxyHttpClient; 18 } 19 20 /// <summary> 21 /// 通过中间件,拦截访问,检测前缀,并转发 22 /// </summary> 23 /// <param name="context"></param> 24 /// <param name="urlRewriter"></param> 25 /// <returns></returns> 26 public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter) 27 28 { 29 var targetUri = await urlRewriter.RewriteUri(context); 30 31 if (targetUri != null) 32 { 33 var requestMessage = GenerateProxifiedRequest(context, targetUri); 34 await SendAsync(context, requestMessage); 35 36 return; 37 } 38 39 await _next(context); 40 } 41 42 private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage) 43 { 44 45 46 using (var responseMessage = await _proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted)) 47 { 48 context.Response.StatusCode = (int)responseMessage.StatusCode; 49 50 foreach (var header in responseMessage.Headers) 51 { 52 context.Response.Headers[header.Key] = header.Value.ToArray(); 53 } 54 55 foreach (var header in responseMessage.Content.Headers) 56 { 57 context.Response.Headers[header.Key] = header.Value.ToArray(); 58 } 59 60 context.Response.Headers.Remove("transfer-encoding"); 61 62 if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME)) 63 { 64 context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store"); 65 } 66 67 await responseMessage.Content.CopyToAsync(context.Response.Body); 68 } 69 } 70 71 private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri) 72 { 73 var requestMessage = new HttpRequestMessage(); 74 CopyRequestContentAndHeaders(context, requestMessage); 75 requestMessage.RequestUri = targetUri; 76 requestMessage.Headers.Host = targetUri.Host; 77 requestMessage.Method = GetMethod(context.Request.Method); 78 return requestMessage; 79 } 80 81 private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage) 82 { 83 var requestMethod = context.Request.Method; 84 if (!HttpMethods.IsGet(requestMethod) && 85 !HttpMethods.IsHead(requestMethod) && 86 !HttpMethods.IsDelete(requestMethod) && 87 !HttpMethods.IsTrace(requestMethod)) 88 { 89 var streamContent = new StreamContent(context.Request.Body); 90 requestMessage.Content = streamContent; 91 } 92 93 foreach (var header in context.Request.Headers) 94 { 95 if (!NotForwardedHttpHeaders.Contains(header.Key)) 96 { 97 if (header.Key != "User-Agent") 98 { 99 if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null) 100 { 101 requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); 102 } 103 } 104 else 105 { 106 string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty; 107 108 if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null) 109 { 110 requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent); 111 } 112 } 113 114 } 115 } 116 } 117 118 private static HttpMethod GetMethod(string method) 119 { 120 if (HttpMethods.IsDelete(method)) return HttpMethod.Delete; 121 if (HttpMethods.IsGet(method)) return HttpMethod.Get; 122 if (HttpMethods.IsHead(method)) return HttpMethod.Head; 123 if (HttpMethods.IsOptions(method)) return HttpMethod.Options; 124 if (HttpMethods.IsPost(method)) return HttpMethod.Post; 125 if (HttpMethods.IsPut(method)) return HttpMethod.Put; 126 if (HttpMethods.IsTrace(method)) return HttpMethod.Trace; 127 return new HttpMethod(method); 128 } 129 }

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

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

1services.AddHttpClient<ProxyHttpClient>() 2 .ConfigurePrimaryHttpMessageHandler(x => new HttpClientHandler() 3 { 4 AllowAutoRedirect = false, 5 MaxConnectionsPerServer = int.MaxValue, 6 UseCookies = false, 7 }); //注入我们定义的HttpClient 8 services.AddSingleton<IUrlRewriter>(new PrefixRewriter("/webapp", "http://localhost:63445"));//这里填写前缀与需要转发的地址

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

app.UseMiddleware<ProxyMiddleware>();

五.测试中间件效果

我们编写前端代码如下:

1 created: function () { 2 this.mockTableData1(); 3 axios.get("/webapp/api/values/get", "123").then(res => { alert(res.data[0]) }); 4 axios.post("/webapp/api/values/post",{value: 'david'}).then(res => { alert(res.data.message) }); 5 6 }

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

1 [HttpGet] 2 public ActionResult<IEnumerable<string>> Get() 3 { 4 return new string[] { "value1", accstring.ToString() }; 5 } 6 7 [HttpPost] 8 public AjaxResult Post(dynamic value) 9 { 10 string aaa = JsonConvert.SerializeObject(value); 11 return Success("OK"); 12 }

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

写在最后

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

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java将前端的json数组字符串转换为列表

记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang

springboot2之优雅处理返回值

前言最近项目组有个老项目要进行前后端分离改造,应前端同学的要求,其后端提供的返回值格式需形如{"status":0,"message":"success","data":{}}方便前端数据处理。要实现前端同学这个需求,其实也挺简单的,