ASP.NET Core 奇技淫巧之接口代理轉(zhuǎn)發(fā)的實(shí)現(xiàn)
前言
先講講本文的開(kāi)發(fā)背景吧..
在如今前后端分離的大背景下,咱的客戶(hù)又有要求啦~
要前后端分離~ 然因?yàn)榉N種原因..沒(méi)辦法用用純前端的框架(其實(shí)是學(xué)習(xí)成本高,又沒(méi)錢(qián)請(qǐng)前端開(kāi)發(fā)人員)...
所以最終決定了一種方案..
那就是采用MVC(只處理前端視圖層,單純是為了托管在.net core上)+Webapi的方式來(lái)實(shí)現(xiàn)前后端分離(講真,很奇葩)..
那么問(wèn)題就隨之而來(lái)了.
現(xiàn)在主流的前端框架都是托管在nodejs上,是通過(guò)axios來(lái)訪(fǎng)問(wèn)后端API,可以通過(guò)配置axios的代理配置(proxyTable)來(lái)實(shí)現(xiàn)跨域訪(fǎng)問(wèn).
那么我們的JS運(yùn)行在MVC上,托管在.net core上..那咋辦呢?..沒(méi)有現(xiàn)成的轉(zhuǎn)發(fā)輪子..我們只有自己造了..
所以這就是本篇的背景 - -.~
正文
幸運(yùn)的是ASP.NET Core 給我們提供了強(qiáng)大的中間件模式.
我們完全可以通過(guò)定義一個(gè)轉(zhuǎn)發(fā)中間件的形式來(lái)實(shí)現(xiàn)代理接口轉(zhuǎn)發(fā),流程如圖:

廢話(huà)不多說(shuō),我們來(lái)創(chuàng)建我們的中間件:
一.創(chuàng)建檢測(cè)約定URL的接口與實(shí)現(xiàn)
首先定義一個(gè)接口IUrlRewriter 用來(lái)檢測(cè)我們的URL是否有對(duì)應(yīng)前綴,如果有,則產(chǎn)生新的URL地址:
這里我們定義接口是為了方便以后更好的更換注入類(lèi)來(lái)實(shí)現(xiàn)快速更換檢測(cè)前綴的規(guī)則.
public interface IUrlRewriter
{
Task<Uri> RewriteUri(HttpContext context);
}
實(shí)現(xiàn)這個(gè)接口,如下(解釋都在注釋里了):
public class PrefixRewriter : IUrlRewriter
{
private readonly PathString _prefix; //前綴值
private readonly string _newHost; //轉(zhuǎn)發(fā)的地址
public PrefixRewriter(PathString prefix, string newHost)
{
_prefix = prefix;
_newHost = newHost;
}
public Task<Uri> RewriteUri(HttpContext context)
{
if (context.Request.Path.StartsWithSegments(_prefix))//判斷訪(fǎng)問(wèn)是否含有前綴
{
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);
}
}
二.創(chuàng)建代理轉(zhuǎn)發(fā)需要的ProxyHttpClient
創(chuàng)建獨(dú)立的ProxyHttpClient,主要是為了區(qū)分代理轉(zhuǎn)發(fā)的httpClient,方便后期添加日志或做別的處理.代碼如下:
public class ProxyHttpClient
{
public HttpClient Client { get; private set; }
public ProxyHttpClient(HttpClient httpClient)
{
Client = httpClient;
}
}
三.創(chuàng)建代理轉(zhuǎn)發(fā)的中間件
代碼如下,中間件嘛,主要就是Invoke方法了,說(shuō)明可以看注釋.
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>
/// 通過(guò)中間件,攔截訪(fǎng)問(wèn),檢測(cè)前綴,并轉(zhuǎn)發(fā)
/// </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
我們?cè)赟tartup的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"));//這里填寫(xiě)前綴與需要轉(zhuǎn)發(fā)的地址
然后在Startup的Configure中,啟動(dòng)我們的中間件,如下:
app.UseMiddleware<ProxyMiddleware>();
五.測(cè)試中間件效果
我們編寫(xiě)前端代碼如下:
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項(xiàng)目,編寫(xiě)接口如下:
[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");
}
效果如下,可以看到我們的視圖正確的獲取到了返回值:

寫(xiě)在最后
這里我們通過(guò)中間件的形式實(shí)現(xiàn)了接口的代理轉(zhuǎn)發(fā),在具體的使用過(guò)程中肯定還會(huì)有一些小問(wèn)題,而且這里我們只實(shí)現(xiàn)了Http的轉(zhuǎn)發(fā).ws的則沒(méi)有.
如果要使用的話(huà),其實(shí)國(guó)外有一個(gè)開(kāi)源的項(xiàng)目:https://github.com/ProxyKit, 已經(jīng)有900多個(gè)star了.應(yīng)該還不錯(cuò).
到此這篇關(guān)于ASP.NET Core 奇技淫巧之接口代理轉(zhuǎn)發(fā)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)ASP.NET Core 接口代理轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.NET Core結(jié)合Nacos實(shí)現(xiàn)配置加解密的方法
當(dāng)我們把應(yīng)用的配置都放到配置中心后,很多人會(huì)想到這樣一個(gè)問(wèn)題,配置里面有敏感的信息要怎么處理呢?本文就詳細(xì)的介紹了.NET Core Nacos配置加解密,感興趣的可以了解一下2021-06-06
asp.net實(shí)現(xiàn)根據(jù)城市獲取天氣預(yù)報(bào)的方法
這篇文章主要介紹了asp.net實(shí)現(xiàn)根據(jù)城市獲取天氣預(yù)報(bào)的方法,涉及asp.net調(diào)用新浪接口獲取天氣預(yù)報(bào)信息的實(shí)現(xiàn)技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-12-12
Linux上使用Docker部署ASP.NET?Core應(yīng)用程序
這篇文章介紹了使用Docker部署ASP.NET?Core應(yīng)用程序的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03
.NET性能優(yōu)化之為集合類(lèi)型設(shè)置初始大小的方法
這篇文章主要介紹了.NET性能優(yōu)化之為集合類(lèi)型設(shè)置初始大小的方法,今天要談的一個(gè)性能優(yōu)化的Tips是一個(gè)老生常談的點(diǎn),但是也是很多人沒(méi)有注意的一個(gè)點(diǎn)。在使用集合類(lèi)型是,你應(yīng)該設(shè)置一個(gè)預(yù)估的初始大小,那么為什么需要這樣做?我們一起來(lái)從源碼的角度說(shuō)一說(shuō)2022-05-05
Repeater事件OnItemCommand取得行內(nèi)控件的方法
這篇文章主要介紹了Repeater事件OnItemCommand取得行內(nèi)控件的方法,有需要的朋友可以參考一下2014-01-01
.NET?Core?使用委托實(shí)現(xiàn)動(dòng)態(tài)流程組裝的思路詳解
模擬管道模型中間件(Middleware)部分,運(yùn)用委托,進(jìn)行動(dòng)態(tài)流程組裝,本次代碼實(shí)現(xiàn)就直接我之前寫(xiě)的動(dòng)態(tài)代理實(shí)現(xiàn)AOP的基礎(chǔ)上改的,就不另起爐灶了,主要思路就是運(yùn)用委托,具體實(shí)現(xiàn)過(guò)程跟隨小編一起看看吧2022-01-01
ASP.NET 防止按鈕多次提交核心實(shí)現(xiàn)代碼
防止按鈕多次提交通常都是在注冊(cè)表單中提示時(shí)的一個(gè)小功能,具體實(shí)現(xiàn)如下,由此需求的朋友可以參考下2013-08-08
ASP.NET 獲取存儲(chǔ)過(guò)程返回值的實(shí)現(xiàn)代碼
ASP.NET 獲取存儲(chǔ)過(guò)程返回值的實(shí)現(xiàn)代碼,需要的朋友可以參考下。2011-12-12
.NetCore使用Swagger+API多版本控制的流程分析
這篇文章主要介紹了.NetCore使用Swagger+API多版本控制的流程分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12

