使用HTTP CONNECT连接ASP.NET Core服务器时中间件断点未触发
反向HTTP隧道调试问题:CONNECT请求未触发中间件断点
场景
我想实现一个类似Visual Studio dev tunnels的自定义Web服务器,纯学习用途——早在dev tunnels推出前,我就想搞懂反向HTTP隧道的工作原理。
问题
服务器中间件中判断CONNECT请求的代码块断点从未被命中。
客户端代码(控制台程序)
internal class Program { static async Task Main(string[] args) { using var tunnelClient = new TunnelClient("https://localhost:7236"); await tunnelClient.ConnectAsync(CancellationToken.None); while (true) { Console.Write("Tunnel Client running."); await Task.Delay(1000); Console.Write("."); await Task.Delay(1000); Console.Write("."); await Task.Delay(1000); Console.Clear(); } } } public class TunnelClient : IDisposable { private readonly Uri _address; private TcpClient? _client; public TunnelClient(string address) { _address = new Uri(address); } public async Task<bool> ConnectAsync(CancellationToken cancellationToken) { if (_client is not null) return true; try { _client = await ConnectClientAsync(); var message = $"CONNECT {_address.Authority} HTTP/1.1\r\n"; await Task.Delay(5000, cancellationToken); await _client.GetStream().WriteAsync(Encoding.UTF8.GetBytes(message), cancellationToken); return true; } catch (Exception e) { Console.WriteLine(e); throw; } } private async Task<TcpClient> ConnectClientAsync() { switch (_address.Host) { case "localhost": var client = new TcpClient(); await client.ConnectAsync("localhost", _address.Port); return client; default: throw new NotImplementedException(); } } public void Dispose() { _client?.Dispose(); } }
服务器中间件代码(ASP.NET Core)
using System.Net.Sockets; namespace BackEnd.Tunneling; public class TunnelMiddleware { private readonly RequestDelegate _next; public TunnelMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { if (context.Request.Method == "CONNECT") { // 此处断点从未命中 } else { var r = context.Request; // 将非CONNECT请求传递给下一个中间件 await _next(context); } } }
服务器初始化代码(Program.cs)
var builder = WebApplication.CreateBuilder(args); // 注册服务(省略其他配置) var app = builder.Build(); // 注册Tunnel中间件 app.UseMiddleware<BackEnd.Tunneling.TunnelMiddleware>(); // 其他中间件配置(如路由、静态文件等) app.Run();
问题原因及解决方法
1. 客户端CONNECT请求格式不完整
HTTP协议要求请求头以\r\n\r\n结尾,当前客户端仅发送了\r\n,导致服务器无法识别为完整请求,一直处于等待状态。
修复代码:
// 修改客户端的请求消息 var message = $"CONNECT {_address.Authority} HTTP/1.1\r\n\r\n";
2. Kestrel服务器默认处理逻辑限制
ASP.NET Core的Kestrel服务器默认不会将CONNECT请求转发到中间件管道,需要确保配置允许处理该方法,同时正确处理隧道连接的升级。
配置Kestrel并修改中间件:
- 在Program.cs中添加Kestrel配置:
builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(7236, listenOptions => { listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1; }); });
- 修改中间件处理CONNECT请求的逻辑:
public async Task InvokeAsync(HttpContext context) { if (context.Request.Method == "CONNECT") { // 响应CONNECT请求,确认隧道建立 context.Response.StatusCode = StatusCodes.Status200OK; await context.Response.CompleteAsync(); // 获取底层双向管道,用于后续隧道数据转发 var upgradeFeature = context.Features.Get<IHttpUpgradeFeature>(); if (upgradeFeature != null) { var pipe = await upgradeFeature.UpgradeAsync(); // 此处可实现双向数据转发逻辑(如连接到本地服务) // 示例:简单回显测试 await pipe.Input.CopyToAsync(pipe.Output); await pipe.Output.CompleteAsync(); } return; } else { await _next(context); } }
3. 客户端未处理服务器响应
客户端发送请求后需要读取服务器的响应,确认隧道已建立,避免连接处于半开状态。
添加响应读取逻辑:
await _client.GetStream().WriteAsync(Encoding.UTF8.GetBytes(message), cancellationToken); // 读取服务器响应 var buffer = new byte[1024]; var bytesRead = await _client.GetStream().ReadAsync(buffer, cancellationToken); var response = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine($"隧道建立响应:{response}");
内容的提问来源于stack exchange,提问作者Dbl
相关产品推荐
相关产品推荐

