如何通过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
相关产品推荐
相关产品推荐

