如何通过中间件定制化功能
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
请求委托用于生成请求管道。 请求委托处理每个 HTTP/tcp/udp 请求。
具体概念可参考ASP.NET Core 中间件
这里列举一下涉及的三种中间件
HTTP 中间件
HTTP 中间件其实就是ASP.NET Core 中间件
只不过VKProxy 要兼容tcp 和udp 这样的L4代理,而ASP.NET Core只是针对http涉及,所以在入口配置处使用了差异的方式,不直接基于ASP.NET Core,避免影响L4代理
HTTP 中间件定义
委托形式的
namespace Microsoft.AspNetCore.Http;
public delegate Task RequestDelegate(HttpContext context);
using Middleware = System.Func<Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.AspNetCore.Http.RequestDelegate>;
// or
using Middleware = System.Func<Microsoft.AspNetCore.Http.HttpContext, Microsoft.AspNetCore.Http.RequestDelegate, Task>;
或者 接口形式的
namespace Microsoft.AspNetCore.Http;
public interface IMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
依赖注入接口
可以通过以下方法直接注入
public static IServiceCollection UseHttpMiddleware(this IServiceCollection services, Func<RequestDelegate, RequestDelegate> middleware);
public static IServiceCollection UseHttpMiddleware(this IServiceCollection services, Func<HttpContext, RequestDelegate, Task> middleware);
public static IServiceCollection UseHttpMiddleware<T>(this IServiceCollection services, params object?[] args) where T : class;
示例
比如我们想通过log 记录请求开始结束
中间件实现:
public class EchoHttpMiddleware : IMiddleware
{
private readonly ILogger<EchoHttpMiddleware> logger;
public EchoHttpMiddleware(ILogger<EchoHttpMiddleware> logger)
{
this.logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var req = context.Request;
logger.LogInformation($"begin {req.Protocol} {req.Host} {req.Path} {DateTime.Now}");
await next(context);
logger.LogInformation($"end {req.Protocol} {req.Host} {req.Path} {DateTime.Now}");
}
}
注入中间件
var app = Host.CreateDefaultBuilder(args)
.ConfigureServices(i =>
{
i.UseHttpMiddleware<EchoHttpMiddleware>(); // 这一行
})
.UseReverseProxy()
.Build();
await app.RunAsync();
效果:
为了展示,开启了info级别的log,产线大家还是要考虑以下log level,不然可能log爆炸
info: EchoHttpMiddleware[0]
begin HTTP/1.1 www.google.com /ws/d 2025/6/5 10:32:23
info: VKProxy.Server.ReverseProxy[14]
Proxying to http://XXXX/ws/d HTTP/2 RequestVersionOrLower
info: VKProxy.Server.ReverseProxy[15]
Received HTTP/1.1 response NotFound.
info: EchoHttpMiddleware[0]
end HTTP/1.1 www.google.com /ws/d 2025/6/5 10:32:24
UDP 中间件
UDP 中间件定义
由于 L4 属于比较底层的协议,本身没有类似HTTP request / response 一来必须一回的明确区分,所以不方便提供HTTP中间件那样简单的管道形式委托
UDP 中间件接口形式
namespace VKProxy.Middlewares;
public interface IUdpProxyMiddleware
{
Task InitAsync(UdpConnectionContext context, CancellationToken token, UdpDelegate next); // udp没有连接建立过程,所以请求都会调用该方法
Task<ReadOnlyMemory<byte>> OnRequestAsync(UdpConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, UdpProxyDelegate next); // 请求中间件,可以修改请求数据,由于协议比较底层,所以是二进制数据
Task<ReadOnlyMemory<byte>> OnResponseAsync(UdpConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, UdpProxyDelegate next); // Response中间件,可以修改响应数据,由于协议比较底层,所以是二进制数据, 可能会多次调用,取决允许响应次数和实际目的端响应次数
}
public delegate Task<ReadOnlyMemory<byte>> UdpProxyDelegate(UdpConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token);
public delegate Task UdpDelegate(UdpConnectionContext context, CancellationToken token);
依赖注入接口
可以通过以下方法直接注入
public static IServiceCollection UseUdpMiddleware<T>(this IServiceCollection services) where T : class, IUdpProxyMiddleware;
示例
比如我们想通过log 记录请求大小
中间件实现:
public class EchoUdpProxyMiddleware : IUdpProxyMiddleware
{
private readonly ILogger<EchoUdpProxyMiddleware> logger;
public EchoUdpProxyMiddleware(ILogger<EchoUdpProxyMiddleware> logger)
{
this.logger = logger;
}
public Task InitAsync(UdpConnectionContext context, CancellationToken token, UdpDelegate next)
{
return next(context, token);
}
public Task<ReadOnlyMemory<byte>> OnRequestAsync(UdpConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, UdpProxyDelegate next)
{
logger.LogInformation($"udp {DateTime.Now} {context.LocalEndPoint.ToString()} request size: {source.Length}");
return next(context, source, token);
}
public Task<ReadOnlyMemory<byte>> OnResponseAsync(UdpConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, UdpProxyDelegate next)
{
logger.LogInformation($"udp {DateTime.Now} {context.Features.Get<IL4ReverseProxyFeature>()?.SelectedDestination?.EndPoint.ToString()} reponse size: {source.Length}");
return next(context, source, token);
}
}
注入中间件
var app = Host.CreateDefaultBuilder(args)
.ConfigureServices(i =>
{
i.UseUdpMiddleware<EchoUdpProxyMiddleware>(); // 这一行
})
.UseReverseProxy()
.Build();
await app.RunAsync();
TCP 中间件
TCP 中间件定义
由于 L4 属于比较底层的协议,本身没有类似HTTP request / response 一来必须一回的明确区分,所以不方便提供HTTP中间件那样简单的管道形式委托
TCP 中间件接口形式
namespace VKProxy.Middlewares;
public interface ITcpProxyMiddleware
{
Task InitAsync(ConnectionContext context, CancellationToken token, TcpDelegate next); // tcp有连接建立过程,所以tcp初次请求会调用该方法
Task<ReadOnlyMemory<byte>> OnRequestAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next); // 请求中间件,可以修改请求数据,由于协议比较底层,所以是二进制数据,可能会多次调用,取决实际请求次数
Task<ReadOnlyMemory<byte>> OnResponseAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next); // Response中间件,可以修改响应数据,由于协议比较底层,所以是二进制数据, 可能会多次调用,取决实际目的端响应次数
}
public delegate Task<ReadOnlyMemory<byte>> TcpProxyDelegate(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token);
public delegate Task TcpDelegate(ConnectionContext context, CancellationToken token);
依赖注入接口
可以通过以下方法直接注入
public static IServiceCollection UseTcpMiddleware<T>(this IServiceCollection services) where T : class, ITcpProxyMiddleware;
示例
比如我们想通过log 记录请求大小
中间件实现:
public class EchoTcpProxyMiddleware : ITcpProxyMiddleware
{
private readonly ILogger<EchoTcpProxyMiddleware> logger;
public EchoTcpProxyMiddleware(ILogger<EchoTcpProxyMiddleware> logger)
{
this.logger = logger;
}
public Task InitAsync(ConnectionContext context, CancellationToken token, TcpDelegate next)
{
return next(context, token);
}
public Task<ReadOnlyMemory<byte>> OnRequestAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next)
{
logger.LogInformation($"tcp {DateTime.Now} {context.LocalEndPoint.ToString()} request size: {source.Length}");
return next(context, source, token);
}
public Task<ReadOnlyMemory<byte>> OnResponseAsync(ConnectionContext context, ReadOnlyMemory<byte> source, CancellationToken token, TcpProxyDelegate next)
{
logger.LogInformation($"tcp {DateTime.Now} {context.Features.Get<IL4ReverseProxyFeature>()?.SelectedDestination?.EndPoint.ToString()} reponse size: {source.Length}");
return next(context, source, token);
}
}
注入中间件
var app = Host.CreateDefaultBuilder(args)
.ConfigureServices(i =>
{
i.UseTcpMiddleware<EchoTcpProxyMiddleware>(); // 这一行
})
.UseReverseProxy()
.Build();
await app.RunAsync();
最后还有一个socks5的示例以供大家参考如何利用中间件扩展实现socks5