You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:32:01