先講講本文的開發背景吧..前端
在現在先後端分離的大背景下,咱的客戶又有要求啦~node
要先後端分離~ 然由於種種緣由..沒辦法用用純前端的框架(實際上是學習成本高,又沒錢請前端開發人員)...ios
因此最終決定了一種方案..git
那就是採用MVC(只處理前端視圖層,單純是爲了託管在.net core上)+Webapi的方式來實現先後端分離(講真,很奇葩)..github
那麼問題就隨之而來了.web
如今主流的前端框架都是託管在nodejs上,是經過axios來訪問後端API,能夠經過配置axios的代理配置(proxyTable)來實現跨域訪問.axios
那麼咱們的JS運行在MVC上,託管在.net core上..那咋辦呢?..沒有現成的轉發輪子..咱們只有本身造了..後端
因此這就是本篇的背景 - -.~api
幸運的是ASP.NET Core 給咱們提供了強大的中間件模式.跨域
咱們徹底能夠經過定義一個轉發中間件的形式來實現代理接口轉發,流程如圖:
廢話很少說,咱們來建立咱們的中間件:
首先定義一個接口IUrlRewriter 用來檢測咱們的URL是否有對應前綴,若是有,則產生新的URL地址:
這裏咱們定義接口是爲了方便之後更好的更換注入類來實現快速更換檢測前綴的規則.
public interface IUrlRewriter { Task<Uri> RewriteUri(HttpContext context); }
實現這個接口,以下(解釋都在註釋裏了):
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,主要是爲了區分代理轉發的httpClient,方便後期添加日誌或作別的處理.代碼以下:
public class ProxyHttpClient { public HttpClient Client { get; private set; } public ProxyHttpClient(HttpClient httpClient) { Client = httpClient; } }
代碼以下,中間件嘛,主要就是Invoke方法了,說明能夠看註釋.
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, ProxyHttpClient proxyHttpClient ) { _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) { var targetUri = await urlRewriter.RewriteUri(context); if (targetUri != null) { var requestMessage = GenerateProxifiedRequest(context, targetUri); await SendAsync(context, requestMessage); return; } await _next(context); } private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage) { 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); } }
咱們在Startup的ConfigureServices中添加以下代碼,注入咱們的HttpClient與IUrlRewriter,以下:
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中,啓動咱們的中間件,以下:
app.UseMiddleware<ProxyMiddleware>();
咱們編寫前端代碼以下:
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項目,編寫接口以下:
[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了.應該還不錯.