簡介
Yarp 是微軟團隊開發的一個反向代理組件, 除了常規的 http 和 https 轉換通訊,它最大的特點是可定制化,很容易根據特定場景開發出需要的定制代理通道。
詳細介紹:https://devblogs.microsoft.com/dotnet/announcing-yarp-1-0-release
源碼倉庫:https://github.com/microsoft/reverse-proxy
文檔地址 :https://microsoft.github.io/reverse-proxy/
基礎使用
1、創建 ASP.NET Core 空項目
使用 Visual Studio :

dotnet new web -o MyProxy
2、 修改代碼 Program.cs 文件
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapGet("/Ping", () => "Hello World!");
app.MapReverseProxy();
app.Run();
3、修改配置文件 appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"routeAll": {
"ClusterId": "clusterBaidu",
"Match": {
"Path": "{**catch-all}"
}
}
},
"Clusters": {
"clusterBaidu": {
"Destinations": {
"baidu": {
"Address": "https://www.baidu.com/"
}
}
}
}
}
}
這里的配置是將所有的請求都轉發到百度。
在 Program.cs 里,還注冊了一個 Get 路由 Ping 。
4、啟動項目
能夠看到在瀏覽器訪問程序監聽的端口號后,顯示的是百度的頁面。打開 F12 ,看到請求頭也是本地的,並不是百度的域名。
測試手動注冊的路由 Ping :
能夠顯示正常。
5、問題整理
(1) Yarp 是不是只能做這種簡單的轉發?
不是,往下有配置文件說明。
(2) JSON 配置文件里有什么要注意的地方嗎?
有。在這個演示的配置文件中 ReverseProxy:Clusters:cluster1:Destinations:destination1:Address 對應的值是:https://www.baidu.com/ ,如果去掉 www ,在項目啟動后會跳轉到百度首頁,不是代理轉發。去掉末尾的 / 符合沒有任何影響。
(3) Yarp 會影響到程序中注冊的路由嗎?
不會影響到程序內部注冊的路由。在 Program.cs 中無論 app.MapReverseProxy(); 在上還是在下,在訪問 Ping 的時候,都是返回 Hello World!
var app = builder.Build();
app.MapReverseProxy();
app.MapGet("/Ping", () => "Hello World!");
app.Run();
進階探索
1、多地址代理
修改配置文件 appsettings.json ,實現默認路由跳轉百度,當訪問 /movie 是訪問 b站。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"routeBaidu": {
"ClusterId": "clusterBaidu",
"Match": {
"Path": "{**catch-all}"
}
},
"routeBiliBili": {
"ClusterId": "clusterBiliBili",
"Match": {
"Path": "/movie/{**catch-all}"
}
}
},
"Clusters": {
"clusterBaidu": {
"Destinations": {
"baidu": {
"Address": "https://www.baidu.com/"
}
}
},
"clusterBiliBili": {
"Destinations": {
"bilibili": {
"Address": "https://www.bilibili.com/"
}
}
}
}
}
}
測試結果:
在后面輸入路由 /movie 后能夠跳轉到b站。但是b站網頁沒有完整顯示,圖片都沒有,這是網站上的策略問題,對於數據接口沒有這些問題。
詳細的配置文件說明,可以查看 https://microsoft.github.io/reverse-proxy/articles/config-files.html
2、規則匹配
網頁上太多資源,為了方便測試,啟用兩個 api 接口。地址分別是:http://localhost:5241/ 和 https://localhost:7184/
兩個 api 接口中分別注冊 /test 路由。
// http://localhost:5241/
app.MapGet("/test", () => "Welcome to Api111!");
// https://localhost:7184/
app.MapGet("/test", () => "Welcome to Api222!");
啟動兩個 api 程序。
C:\Users\Test>curl http://localhost:5241/test
Welcome to Api111!
C:\Users\Test>curl https://localhost:7184/test
Welcome to Api222!
修改 MyProxy 項目的配置文件 appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"routeOne": {
"ClusterId": "clusterOne",
"Match": {
"Path": "/test/{**catch-all}",
"QueryParameters": [
{
"Name": "number",
"Values": [ "1" ]
}
]
}
},
"routeTwo": {
"ClusterId": "clusterTwo",
"Match": {
"Path": "/test/{**catch-all}",
"QueryParameters": [
{
"Name": "number",
"Values": [ "2" ]
}
]
}
},
"routeBaidu": {
"ClusterId": "clusterBaidu",
"Match": {
"Path": "{**catch-all}"
}
}
},
"Clusters": {
"clusterOne": {
"Destinations": {
"apiOne": {
"Address": "http://localhost:5241/"
}
}
},
"clusterTwo": {
"Destinations": {
"apiTwo": {
"Address": "https://localhost:7184/"
}
}
},
"clusterBaidu": {
"Destinations": {
"baidu": {
"Address": "https://www.baidu.com/"
}
}
}
}
}
}
Path :監聽路由地址。
QueryParameters:匹配參數。
QueryParameters:Name:參數名。
QueryParameters:Values:參數值。
MyProxy 的監聽端口是 http://localhost:5024/ 訪問結果如下:
C:\Users\Test>curl http://localhost:5024/ping
Hello World!
C:\Users\Test>curl http://localhost:5024/test
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /test was not found on this server.</p>
</body></html>
C:\Users\Test>curl http://localhost:5024/test?number=1
Welcome to Api111!
C:\Users\Test>curl http://localhost:5024/test?number=2
Welcome to Api222!
能夠根據參數以及參數值導向對應的地址。
3、問題整理
(1)為什么訪問 /movie 不能正常顯示網頁。
因為 b站某些接口開啟了防盜鏈,還有跨域檢測。
(2)在根據參數匹配中,如果匹配的路由一樣,監聽的參數一樣,參數值也一樣會怎么樣?
訪問該路由地址會報錯。
(3)路由匹配的優先級?
程序內注冊的路由優先級最高,其次才是 Yarp 在配置文件里加載的。
小試牛刀
最近的工作是做企業內數據安全方面的。推動公司數據安全體系,通過技術手段提升公司信息安全。
有一個很老的OA系統,十幾年了, .NET Framework 2.0 寫的。漏洞一大堆,包括不限於xss、sql注入等,權限只到表單級別。瀏覽器上按下 F12 能查看到表單鏈接,直接復制出去,別人也能訪問。
在這個系統上要做安全,我想的是在中間加代理,正好適合使用 Yarp 來完成,也方便寫業務處理代碼。嗯,很真實, .NET Core 寫的,方便寫業務代碼。
用戶登錄成功后,會記錄下用戶的 Host 和 Cookie,每次訪問的時候系統的時候,在 Yarp 這里都校驗一下是否與用戶登錄時的匹配。
解決了兩個問題:
1、從網絡層捕獲到所有的請求,方便后面做排查。參數、傳值,出了事故可以找到責任人。
2、隔離真實的站點地址,杜絕弱安全等級網站暴露后被壞人攻擊的風險。
踩坑集錦
1、non-ASCII
項目要代理某網頁,在使用下載功能的時候,接口返回 502 。
info: Yarp.ReverseProxy.Forwarder.HttpForwarder[48]
ResponseHeaders: The destination returned a response that cannot be proxied back to the client.
System.InvalidOperationException: Invalid non-ASCII or control character in header: 0x00E4
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowInvalidHeaderCharacter(Char ch)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ValidateHeaderValueCharacters(StringValues headerValues)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseHeaders.SetValueFast(String key, StringValues value)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
at Yarp.ReverseProxy.Forwarder.HttpTransformer.CopyResponseHeaders(HttpHeaders source, IHeaderDictionary destination)
at Yarp.ReverseProxy.Forwarder.HttpTransformer.TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse)
at Yarp.ReverseProxy.Transforms.Builder.StructuredTransformer.TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse)
at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer)
去 GitHub 翻 Issues
下載接口能正常訪問,文件流也能完整地拿到。重寫了所有的響應頭沒有用。這種不開源的商業站點,也猜不到字符編碼。
最后妥協了,用了一個 .NET 服務在服務器上下載后再轉發。
代理非常規服務接口時,一定要多測試。