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

如何通过ASP.NET将SignalR协商请求代理至ASP.NET Core服务器?

ASP.NET代理SignalR协商请求实现跨服务器连接

可以实现通过ASP.NET旧服务器代理SignalR的协商请求,让前端无需直接配置ASP.NET Core服务器地址。核心思路是转发协商请求并修改返回的连接地址,再代理后续的WebSocket/长连接流量到Core服务器。以下是具体实现步骤:

1. 代理SignalR协商请求

在旧ASP.NET服务器中创建控制器处理协商请求,将请求转发到Core服务器后,替换返回结果中的连接地址为旧服务器的代理端点,确保前端后续请求仍发往旧服务器。

public class SignalRProxyController : Controller
{
    private readonly string _coreSignalRServerUrl = "http://your-core-signalr-server";

    [HttpPost]
    [Route("signalr/proxy-negotiate")]
    public async Task<IActionResult> ProxyNegotiate()
    {
        // 构造转发到Core服务器的协商请求
        using var httpClient = new HttpClient();
        var coreNegotiateUrl = $"{_coreSignalRServerUrl}/signalr/negotiate";
        
        // 复制原请求的Headers
        foreach (var header in Request.Headers)
        {
            if (!httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value))
            {
                httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.First());
            }
        }

        // 复制请求体
        var requestContent = new StreamContent(Request.Body);
        foreach (var header in Request.Content.Headers)
        {
            requestContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        // 发送请求到Core服务器
        var response = await httpClient.PostAsync(coreNegotiateUrl, requestContent);
        response.EnsureSuccessStatusCode();
        
        // 解析协商结果并替换连接地址
        var negotiateResult = await response.Content.ReadFromJsonAsync<NegotiateResponse>();
        negotiateResult.Url = "/signalr/proxy-connection"; // 旧服务器的代理连接端点

        return Json(negotiateResult);
    }
}

// 匹配SignalR协商返回的模型结构
public class NegotiateResponse
{
    public string Url { get; set; }
    public string ConnectionId { get; set; }
    public string AccessToken { get; set; }
    public int ConnectionTimeout { get; set; }
    public bool TryWebSockets { get; set; }
    // 其他返回字段根据实际情况补充
}

2. 代理WebSocket/长连接流量

协商完成后,前端会向旧服务器的/signalr/proxy-connection发起WebSocket请求,需要通过OWIN中间件将该流量转发到Core服务器的SignalR端点。

实现OWIN代理中间件

public class SignalRWebSocketProxyMiddleware : OwinMiddleware
{
    private readonly string _coreSignalRBaseUrl;

    public SignalRWebSocketProxyMiddleware(OwinMiddleware next, string coreSignalRBaseUrl) 
        : base(next)
    {
        _coreSignalRBaseUrl = coreSignalRBaseUrl;
    }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        if (request.Path.StartsWithSegments(new PathString("/signalr/proxy-connection")))
        {
            // 转换为Core服务器的WebSocket地址
            var coreWsUrl = $"{_coreSignalRBaseUrl.TrimEnd('/')}/signalr{request.Path}{request.QueryString}"
                .Replace("http://", "ws://")
                .Replace("https://", "wss://");

            // 建立到Core服务器的WebSocket连接
            using var coreWebSocket = new ClientWebSocket();
            foreach (var header in request.Headers)
            {
                // 跳过WebSocket升级相关头,由ClientWebSocket自动处理
                if (!header.Key.Equals("Upgrade", StringComparison.OrdinalIgnoreCase) &&
                    !header.Key.Equals("Connection", StringComparison.OrdinalIgnoreCase))
                {
                    coreWebSocket.Options.SetRequestHeader(header.Key, string.Join(", ", header.Value));
                }
            }

            await coreWebSocket.ConnectAsync(new Uri(coreWsUrl), context.Request.CallCancelled);

            // 双向转发消息:客户端→Core,Core→客户端
            var forwardToCore = ForwardStreamToWebSocket(context.Request.Body, coreWebSocket);
            var forwardToClient = ForwardWebSocketToStream(coreWebSocket, context.Response.Body);

            await Task.WhenAll(forwardToCore, forwardToClient);
            return;
        }

        // 非SignalR代理请求,交给下一个中间件处理
        await Next.Invoke(context);
    }

    private async Task ForwardStreamToWebSocket(Stream clientStream, ClientWebSocket coreWebSocket)
    {
        var buffer = new byte[4096];
        while (true)
        {
            try
            {
                var bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0) break;
                await coreWebSocket.SendAsync(
                    new ArraySegment<byte>(buffer, 0, bytesRead), 
                    WebSocketMessageType.Binary, 
                    endOfMessage: true, 
                    CancellationToken.None);
            }
            catch
            {
                break;
            }
        }
    }

    private async Task ForwardWebSocketToStream(ClientWebSocket coreWebSocket, Stream clientStream)
    {
        var buffer = new byte[4096];
        while (true)
        {
            try
            {
                var receiveResult = await coreWebSocket.ReceiveAsync(
                    new ArraySegment<byte>(buffer), 
                    CancellationToken.None);
                
                if (receiveResult.MessageType == WebSocketMessageType.Close) break;
                await clientStream.WriteAsync(buffer, 0, receiveResult.Count);
                await clientStream.FlushAsync();
            }
            catch
            {
                break;
            }
        }
    }
}

注册OWIN中间件

在旧ASP.NET的OWIN Startup类中添加中间件:

public void Configuration(IAppBuilder app)
{
    // 注册SignalR WebSocket代理中间件,传入Core服务器地址
    app.Use<SignalRWebSocketProxyMiddleware>("http://your-core-signalr-server");
    
    // 其他OWIN配置(如原有SignalR、MVC等)
    app.MapSignalR();
    app.UseMvc();
}

3. 前端配置

前端使用MS SignalR客户端,仅需指向旧服务器的协商代理端点,无需修改其他逻辑:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/signalr/proxy-negotiate")
    .configureLogging(signalR.LogLevel.Information)
    .build();

// 正常使用SignalR连接和事件监听
connection.on("ReceiveMessage", (user, message) => {
    console.log(`${user}: ${message}`);
});

connection.start().catch(err => console.error(err.toString()));

关键注意事项

  • IIS配置:确保旧服务器的IIS已开启WebSocket功能(需安装WebSocket协议组件)。
  • 请求头转发:必须正确复制用户认证相关的头(如Authorization、Cookie),否则Core服务器无法识别用户身份。
  • 错误处理:WebSocket转发过程中需处理连接断开的异常,避免资源泄漏。
  • HTTPS适配:如果使用HTTPS,需将ws://替换为wss://,确保证书配置正确。

内容的提问来源于stack exchange,提问作者Mr Patience

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 08:05:24