简介
项目地址:https://microsoft.github.io/reverse-proxy/index.html
说是目前微软用它取代了 Nginx ,使得吞吐量提升了百分之80。
挺感兴趣的,然后去网上找了下教程试下,咱也不懂.NET代码,遇到的坑挺多的,通过网上教程也只是简单的示例,无法满足需求,后面通过和 AI 多天的沟通交流,终于测试出能支持HTTPS,HTTP3的配置。
下面是安装和配置过程
系统环境:Debian11
.NET版本:8.0
YARP版本:2.1.0
1.安装.NET8 和 反向代理-YARP
安装 .NET SDK:YARP 是基于 .NET 的,所以首先需要在你的 Debian 系统上安装 .NET SDK。你可以使用以下命令进行安装
wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
apt update
apt install -y apt-transport-https
apt update
apt install -y dotnet-sdk-8.0
创建一个新的 .NET 项目:创建一个新的 Worker Service 项目,这是一个适合运行长时间后台任务的项目模板。在你想要创建项目的目录中,运行以下命令:
这将创建一个名为 YarpSample 的新项目。
先创建一个用来存放项目的目录
mkdir /var/www && cd /var/www
dotnet new worker -n YarpSample
添加 YARP 包:在项目目录中,添加 YARP NuGet 包。运行以下命令:
cd YarpSample
dotnet add package Microsoft.AspNetCore
dotnet add package Yarp.ReverseProxy
使用 dotnet list package
命令来查看项目中已安装的所有 NuGet 包及其版本。在项目目录中打开命令行,然后输入以下命令:
dotnet list package
Project 'YarpSample' has the following package references
[net8.0]:
Top-level Package Requested Resolved
> Microsoft.Extensions.Hosting 8.0.0 8.0.0
> Yarp.ReverseProxy 2.1.0 2.1.0
HTTP3 QUIC 需要的依赖,libmsquic
是一个开源的 QUIC 和 HTTP/3 库,它提供了 QUIC 协议的实现,这是 HTTP/3 需要的底层协议。使用以下命令来安装libmsquic
apt install libmsquic
2.Yarp配置文件
Program.cs
文件
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Yarp.ReverseProxy.Configuration;
var builder = WebApplication.CreateBuilder(args);
// 添加反向代理服务
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// 使用反向代理中间件
app.UseHttpsRedirection();
app.UsePathBase("/");
app.Use(next => context =>
{
context.Response.Headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload";
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
context.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
context.Response.Headers["X-XSS-Protection"] = "1; mode=block";
context.Response.Headers["Referrer-Policy"] = "no-referrer";
context.Response.Headers["Set-Cookie"] = "Path=/; HttpOnly; Secure";
context.Response.Headers["Alt-Svc"] = "h3=\":443\"; ma=86400";
return next(context);
});
app.MapReverseProxy();
app.Run();
app.UseHttpsRedirection();
:这行代码添加了一个中间件,该中间件会将所有的 HTTP 请求重定向到 HTTPS。这可以提高应用程序的安全性,因为 HTTPS 使用 SSL/TLS 协议来加密 HTTP 请求和响应,防止数据在传输过程中被窃取或篡改。
app.UsePathBase("/");
:这行代码添加了一个中间件,该中间件会从请求的 URL 中移除指定的基路径(在这个例子中是 "/")。这对于在子路径下托管应用程序很有用。例如,如果你的应用程序托管在 https://example.com/myapp/
下,你可以使用 app.UsePathBase("/myapp");
来移除基路径。这样,你就可以像应用程序在根路径下运行一样编写路由规则。
app.Use(next => context => {...});
:这行代码添加了一个自定义的中间件。这个中间件会在每个 HTTP 请求被处理时执行你提供的代码。你可以在 {...}
中添加代码来处理 HTTP 请求或修改 HTTP 响应。
YarpSample.csproj
文件
ItemGroup
框里保留以下两个就好了
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-YarpSample-9xxxdad1-xx5-xxxa-bxxx-6bxxbaxxx16</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" />
</ItemGroup>
</Project>
appsettings.json
文件
下面带域名的地方,根据实际情况调整
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:80"
},
"Http3": {
"Url": "https://*:443",
"Protocols": "Http3"
},
"MySniEndpoint": {
"Url": "https://*:443",
"SslProtocols": [ "Tls12", "Tls13" ],
"Sni": {
"`www.domain.com`": {
"Certificate": {
"Path": "/etc/ssl/fullchain.cer",
"KeyPath": "/etc/ssl/www.domain.com.key"
}
}
}
}
},
"Certificates": {
"Default": {
"Path": "/etc/ssl/fullchain.cer",
"KeyPath": "/etc/ssl/www.domain.com.key"
}
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"route1" : {
"ClusterId": "cluster1",
"Match": {
"Path": "/{**catch-all}"
},
"Transforms": [
{ "RequestHeaderOriginalHost": "true" }
]
}
},
"Clusters": {
"cluster1": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"destination1": {
"Address": "http://127.0.0.1:8080/"
}
}
}
}
}
}
"Certificates":
这是一个包含所有证书配置的对象,这是默认证书的配置。当一个请求到达服务器,但没有匹配任何特定的 SNI(Server Name Indication)规则时,服务器会使用这个默认证书。有配置证书的域名会覆盖该证书。 "Path": "/{**catch-all}"
是一个路由模板,它定义了应用程序如何匹配和处理 HTTP 请求的路径。
/{**catch-all}
会匹配任何路径。**
是一个特殊的路由参数,它表示“零个或多个路径段”。所以,无论请求的路径是 /
、/foo
、/foo/bar
还是其他任何路径,都会被这个路由模板匹配。
当一个请求的路径被这个路由模板匹配时,应用程序会将请求转发到与这个路由关联的处理程序或中间件。在此配置中,这意味着所有的请求都会被转发到 cluster1
集群
遇到的坑
运行项目
dotnet run
我通过这个测试 yp.530503.xyz 域名运行成功了,但是打开后静态资源无法加载出来通过F12查看,静态资源css js 这些文件都被加上了8080 端口,就是我反向代理的那个站点http://IP:8080/,浏览器F12查看请求显示这样https://yp.530503.xyz:8080/js/lib/all.min.css。原因是通过YARP反向代理访问的网站时,静态资源的URL被错误地添加了原始服务器的端口号(8080)
请教AI:
这个问题可能是由于反向代理的目标站点在生成静态资源链接时使用了绝对路径,这导致了原始服务器的端口号被添加到了 URL 中。当你通过 YARP 访问站点时,浏览器会尝试从 https://yp.530503.xyz:8080/ 加载静态资源,而不是你期望的 https://yp.530503.xyz
还给我写了各种规则还有配置,不过都失败了,还是带8080端口,不然就是无法运行报错。
然后我问了为什么我用 NGINX 反向代理 http://ip:8080/ ,就不会出现静态资源后面带有8080端口的问题呢
AI回答:
这个问题可能是由于 NGINX 和 YARP 在处理 HTTP 头部的 Host 字段时的行为不同。当一个 HTTP 请求通过反向代理时,反向代理可以选择保留原始的 Host 头部,或者将其修改为目标服务器的主机名和端口号。
在默认配置下,NGINX 会保留原始的 Host 头部。这意味着,即使请求被代理到 http://ip:8080/,目标服务器仍然会看到原始的 Host 头部,例如 yp.530503.xyz。因此,当目标服务器生成静态资源的 URL 时,它会使用 yp.530503.xyz 而不是http://ip:8080/。
至此AI终于给出正确答案了:
YARP 在默认配置下会修改 Host 头部,将其设置为目标服务器的主机名和端口号。这意味着,目标服务器会看到的 Host 头部是 IP:8080,而不是 yp.530503.xyz。因此,当目标服务器生成静态资源的 URL 时,它会使用 IP:8080。
在 YARP 中,你可以通过在路由配置中添加 Transforms 来修改 HTTP 请求和响应。为了保留原始的 Host 头部,你可以添加一个 RequestHeaderOriginalHost
转换。
在 YARP 中,{ "RequestHeaderOriginalHost": "true" } 是一个转换配置,它告诉 YARP 在代理请求时保留原始的 Host 头部。
当一个 HTTP 请求通过反向代理时,反向代理可以选择保留原始的 Host 头部,或者将其修改为目标服务器的主机名和端口号。Host 头部是 HTTP/1.1 请求中的一个重要字段,它指定了请求的目标主机名。在许多情况下,服务器会根据 Host 头部来决定如何处理请求。
默认情况下,YARP 会修改 Host 头部,将其设置为目标服务器的主机名和端口号。但是,如果你添加了 RequestHeaderOriginalHost 转换并将其设置为 true,那么 YARP 就会在代理请求时保留原始的 Host 头部。
3.设置为系统服务运行
dotnet run
这是默认的运行方式,但是这样终端关掉,服务也就停止运行了。
首先,你需要使用 dotnet publish
命令来发布你的应用程序。这个命令会编译你的应用程序,然后将编译后的文件和所有必要的依赖项复制到一个新的文件夹中。你可以使用以下命令来发布你的应用程序:
dotnet publish -c Release -o /var/www/YarpSample/publish
这个命令会在 /var/www/YarpSample/publish
文件夹中创建发布版本的应用程序。
然后,你需要创建一个 systemd 服务文件来管理你的应用程序。以下是一个示例的服务文件:
vim /etc/systemd/system/yarp.service
[Unit]
Description=YARP Reverse Proxy
[Service]
WorkingDirectory=/var/www/YarpSample/publish
ExecStart=/usr/bin/dotnet /var/www/YarpSample/publish/YarpSample.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=yarp
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
请将 YarpSample.dll
替换为你的 .dll
文件的实际名称。然后,将这个文件保存为 /etc/systemd/system/yarp.service
。
最后,你可以使用以下命令来启动你的服务,并将其设置为在系统启动时自动运行:
systemctl daemon-reload
systemctl stop yarp
systemctl start yarp
启动报错
查看日志
journalctl -u yarp.service -e
日志中,我们可以看到 System.Net.Sockets.SocketException (13): Permission denied
这个错误。这个错误通常意味着你的应用程序没有足够的权限来绑定到一个网络端口。
在 Linux 系统中,只有 root 用户或者具有 CAP_NET_BIND_SERVICE 能力的用户才能绑定到 1024 以下的端口。你的应用程序正在尝试绑定到 443 端口,但是运行应用程序的用户(在这个例子中是 www-data
用户)可能没有足够的权限。
使用 root 用户运行你的应用程序。但是请注意,这样做可能会带来安全风险,因为 root 用户具有完全的系统权限
1.更改 /var/www/YarpSample/publish 目录的所有者为 www-data 用户:
chown -R www-data:www-data /var/www/YarpSample/publish
2.使用 setcap
命令给 dotnet
命令添加 CAP_NET_BIND_SERVICE 能力。这样,dotnet
命令就可以绑定到 1024 以下的端口了,即使它是以非 root 用户运行的。你可以使用以下命令来添加这个能力:
setcap 'cap_net_bind_service=+ep' /usr/share/dotnet/dotnet
参考资料:
https://microsoft.github.io/reverse-proxy/index.html
https://www.cnblogs.com/ysmc/p/16665788.html
https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/servers/kestrel/http3?view=aspnetcore-8.0