
Azure 應用服務用 YARP 取代了 Nginx,獲得了 80% 以上的吞吐量。他們每天處理 160B 多個請求(1.9 m RPS)。這是微軟的一項了不起的技術創新。
首先我們來介紹一下什麼是 Yarp
Yarp 是什麼?
YARP(Yet Another Reverse Proxy)是一個開源的、高效能的反向代理程式庫,由 Microsoft 開發,使用 C# 語言編寫。它旨在作為 .NET 平台上構建反向代理伺服器的基礎。YARP 主要針對 .NET 5 及以上版本,允許開發者在 .NET 應用程式中輕鬆地實現反向代理的功能。
YARP 的主要特點和功能:
- 模組化與可擴充性: YARP 設計成高度模組化的,這意味著可以根據需求替換或擴充內部元件,如 HTTP 請求路由、負載平衡、健康檢查等。
- 效能: YARP 針對高效能進行了最佳化,利用了 .NET 的非同步程式設計模型和高效的 IO 操作,以處理大量並行連線。
- 設定驅動: YARP 的行為可以透過設定來控制,支援從檔案、資料庫或其他來源動態載入設定。
- 路由: 可以基於各種參數(如路徑、標頭、查詢參數)設定請求路由規則。
- 負載平衡: 內建多種負載平衡策略,如輪詢、最少連線、隨機選擇等,並且可以自訂負載平衡策略。
- 健康檢查: 支援後端服務的健康檢查,以確保請求只會被轉發到健康的後端服務執行個體。
- 轉換器: 允許對請求和回應進行轉換,如修改標頭、路徑或查詢參數。
- 工作階段親和性: 支援工作階段親和性(Session Affinity),確保來自同一用戶端的請求被傳送到相同的後端服務執行個體。
使用 YARP 的一些場景:
- 反向代理: 在用戶端和後端服務之間提供一個中間層,用於請求轉發和負載平衡。
- API 閘道: 作為微服務架構中的 API 閘道,提供路由、鑑權、監控等功能。
- 邊緣服務: 在應用程式和外部世界之間提供安全層,處理 SSL 終止、請求限制等工作。
Yarp 簡單的使用
建立一個 WebApi 的專案
安裝 NuGet 套件
<ItemGroup>
<PackageReference Include="Yarp.ReverseProxy" Version="2.0.0" />
</ItemGroup>
開啟 appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"route1": {
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}"
}
}
},
"Clusters": {
"cluster1": {
"Destinations": {
"destination1": {
"Address": "https://cn.bing.com/"
}
}
}
}
}
}
開啟 Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
然後啟動專案,存取我們的 api 就會被代理轉發到 bing 上

Yarp 工具代理使用
下面我們再提供一個在中介軟體使用 yarp 的方式
我們需要用到 IHttpForwarder
先修改 Program.cs 在這裡我們註入了 HttpForwarder,然後提供一個 Run 中介軟體,在中介軟體中手動指定了端點的位址 https://cn.bing.com/ 然後我們啟動一下專案。
using Yarp.ReverseProxy.Forwarder;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpForwarder(); // 注入IHttpForwarder
var app = builder.Build();
var httpMessage = new HttpMessageInvoker(new HttpClientHandler());
app.Run((async context =>
{
var httpForwarder = context.RequestServices.GetRequiredService<IHttpForwarder>();
var destinationPrefix = "https://cn.bing.com/";
await httpForwarder.SendAsync(context, destinationPrefix, httpMessage);
}));
app.Run();
也是一樣會被代理過去,但是與簡單使用不一樣的是我們是在程式碼層面控制代理的。

使用 yarp 修改 Bing 的回應內容
我們繼續基於上面的代理使用進行修改 bing 的相應內容!
開啟 Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpForwarder(); // 注入IHttpForwarder
var app = builder.Build();
var httpMessage = new HttpMessageInvoker(new HttpClientHandler()
{
// 忽略https錯誤
ServerCertificateCustomValidationCallback = (_, _, _, _) => true,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.GZip,
UseCookies = false,
UseProxy = false,
UseDefaultCredentials = true,
});
var destinationPrefix = "https://cn.bing.com/";
var bingTransformer = new BingTransformer();
app.Run((async context =>
{
var httpForwarder = context.RequestServices.GetRequiredService<IHttpForwarder>();
await httpForwarder.SendAsync(context, destinationPrefix, httpMessage, new ForwarderRequestConfig(),
bingTransformer);
}));
app.Run();
建立 BingTransformer.cs
public class BingTransformer : HttpTransformer
{
public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest,
string destinationPrefix,
CancellationToken cancellationToken)
{
var uri = RequestUtilities.MakeDestinationAddress(destinationPrefix, httpContext.Request.Path,
httpContext.Request.QueryString);
proxyRequest.RequestUri = uri;
proxyRequest.Headers.Host = uri.Host;
await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken);
}
public override async ValueTask<bool> TransformResponseAsync(HttpContext httpContext,
HttpResponseMessage? proxyResponse,
CancellationToken cancellationToken)
{
await base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken);
if (httpContext.Request.Method == "GET" &&
httpContext.Response.Headers["Content-Type"].Any(x => x.StartsWith("text/html")))
{
var encoding = proxyResponse.Content.Headers.FirstOrDefault(x => x.Key == "Content-Encoding").Value;
if (encoding?.FirstOrDefault() == "gzip")
{
var content = proxyResponse?.Content.ReadAsByteArrayAsync(cancellationToken).Result;
if (content != null)
{
var result = Encoding.UTF8.GetString(GZipDecompressByte(content));
result = result.Replace("国内版", "Token Bing 搜索 - 国内版");
proxyResponse.Content = new StringContent(GZipDecompressString(result));
}
}
else if (encoding.FirstOrDefault() == "br")
{
var content = proxyResponse?.Content.ReadAsByteArrayAsync(cancellationToken).Result;
if (content != null)
{
var result = Encoding.UTF8.GetString(BrDecompress(content));
result = result.Replace("国内版", "Token Bing 搜索 - 国内版");
proxyResponse.Content = new ByteArrayContent(BrCompress(result));
}
}
else
{
var content = proxyResponse?.Content.ReadAsStringAsync(cancellationToken).Result;
if (content != null)
{
content = content.Replace("国内版", "Token Bing 搜索 - 国内版");
proxyResponse.Content = new StringContent(content);
}
}
}
return true;
}
/// <summary>
/// 解壓GZip
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static byte[] GZipDecompressByte(byte[] bytes)
{
using var targetStream = new MemoryStream();
using var compressStream = new MemoryStream(bytes);
using var zipStream = new GZipStream(compressStream, CompressionMode.Decompress);
using (var decompressionStream = new GZipStream(compressStream, CompressionMode.Decompress))
{
decompressionStream.CopyTo(targetStream);
}
return targetStream.ToArray();
}
/// <summary>
/// 解壓GZip
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string GZipDecompressString(string str)
{
using var compressStream = new MemoryStream(Encoding.UTF8.GetBytes(str));
using var zipStream = new GZipStream(compressStream, CompressionMode.Decompress);
using var resultStream = new StreamReader(new MemoryStream(compressStream.ToArray()));
return resultStream.ReadToEnd();
}
/// <summary>
/// Br壓縮
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static byte[] BrCompress(string str)
{
using var outputStream = new MemoryStream();
using (var compressionStream = new BrotliStream(outputStream, CompressionMode.Compress))
{
compressionStream.Write(Encoding.UTF8.GetBytes(str));
}
return outputStream.ToArray();
}
/// <summary>
/// Br解壓
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static byte[] BrDecompress(byte[] input)
{
using (var inputStream = new MemoryStream(input))
using (var outputStream = new MemoryStream())
using (var decompressionStream = new BrotliStream(inputStream, CompressionMode.Decompress))
{
decompressionStream.CopyTo(outputStream);
return outputStream.ToArray();
}
}
}
得到的效果我們將 国内版 修改成了 Token Bing 搜索 - 国内版

Yarp 相關資料
技術交流群:737776595
官方文件:https://microsoft.github.io/reverse-proxy/articles/getting-started.html
來自 token 的分享