ASP.NET Web API2中WebSocket跨域错误排查求助
问题排查:WebSocket添加后非localhost请求触发CORS错误
我的Web API在内网运行正常,之前因为不需要跨域关闭了所有CORS策略。现在在IIS应用中添加WebSocket后,只有localhost能正常使用,其他请求都触发CORS错误。以下是Global.asax.cs代码,麻烦帮忙排查错误原因:
public class WebApiApplication : System.Web.HttpApplication { private static readonly ILogger Logger = Log.ForContext<WebApiApplication>(); protected void Application_Start() { Logger.Debug("In Application_Start"); EnableCorsAttribute corsAttribute = new EnableCorsAttribute("*", "*", "*"); AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); GlobalConfiguration.Configuration.EnableCors(corsAttribute); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // NEW PART START:Add WebSocket route handler string baseAddress = "http://*:9999/websocket/"; HttpListener listener = new HttpListener(); listener.Prefixes.Add(baseAddress); listener.Start(); // Handle WebSocket upgrade requests listener.BeginGetContext(HandleWebSocketRequest, listener); //NEW PART END Logger.Information("App started !"); } // NEW PART private async Task HandleWebSocket(HttpListenerContext context) { HttpListenerWebSocketContext webSocketContext = null; try { webSocketContext = await context.AcceptWebSocketAsync(subProtocol: null); // WebSocket connection established WebSocket webSocket = webSocketContext.WebSocket; // Handle WebSocket communication await HandleWebSocketCommunication(webSocket); } catch (Exception ex) { // Handle WebSocket upgrade exception Console.WriteLine($"WebSocket upgrade error: {ex.Message}"); if (webSocketContext != null) { webSocketContext.WebSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "Internal Server Error", CancellationToken.None).Wait(); } } } // NEW PART private async Task HandleWebSocketCommunication(WebSocket webSocket) { var buffer = new byte[1024 * 4]; try { while (webSocket.State == WebSocketState.Open) { // Receive a message from the client WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Text) { // Handle text message string message = Encoding.UTF8.GetString(buffer, 0, result.Count); Console.WriteLine($"Received message: {message}"); // React to the received message await ReactToMessage(webSocket, message); } else if (result.MessageType == WebSocketMessageType.Close) { // Handle close message await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } } } catch (WebSocketException ex) { // Handle WebSocket communication exception Console.WriteLine($"WebSocket communication error: {ex.Message}"); } finally { // Close WebSocket connection if (webSocket.State == WebSocketState.Open) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } webSocket.Dispose(); } } // NEW PART private async Task ReactToMessage(WebSocket webSocket, string message) { // Implement your logic to react to the received message // For example, you could process the message and send a response back to the client string responseMessage = $"Received your message: {message}"; byte[] responseBuffer = Encoding.UTF8.GetBytes(responseMessage); await webSocket.SendAsync(new ArraySegment<byte>(responseBuffer), WebSocketMessageType.Text, true, CancellationToken.None); } // NEW PART private void HandleWebSocketRequest(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); if (context.Request.IsWebSocketRequest) { // Upgrade HTTP connection to WebSocket HandleWebSocket(context); } else { // Handle other HTTP requests // For example, you can return a 404 response context.Response.StatusCode = 404; context.Response.Close(); } // Continue listening for incoming requests listener.BeginGetContext(HandleWebSocketRequest, listener); } protected void Application_End(object sender, EventArgs e) { Logger.Debug("In Application_End"); ApplicationShutdownReason shutdownReason = System.Web.Hosting.HostingEnvironment.ShutdownReason; } }
错误原因分析
WebSocket的HttpListener未应用CORS策略:你在
Application_Start中初始化的HttpListener是独立于ASP.NET Web API的HTTP监听服务,Web API配置的EnableCorsAttribute只对Web API路由处理的请求生效,不会作用于这个独立监听器的请求。当非localhost客户端发起WebSocket握手时,监听器不会返回任何CORS响应头,浏览器因此触发跨域错误。IIS托管与独立监听器的冲突:你的应用托管在IIS中,但手动启动的
HttpListener监听了*:9999端口,这会和IIS的端口管理机制冲突,而且在IIS环境下直接使用HttpListener需要额外的权限,也不符合IIS托管应用的最佳实践。
解决方法
方案1:改用ASP.NET Web API原生WebSocket支持(推荐)
利用Web API原生的WebSocket支持,复用已有的CORS配置,避免独立监听器的问题:
- 添加WebSocket路由:在
WebApiConfig.cs中注册WebSocket路由:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 保留原有配置 config.MapHttpAttributeRoutes(); // 添加WebSocket路由 config.Routes.MapHttpRoute( name: "WebSocketRoute", routeTemplate: "websocket", defaults: new { controller = "WebSocket" } ); } }
- 创建WebSocket控制器:
public class WebSocketController : ApiController { public HttpResponseMessage Get() { if (HttpContext.Current.IsWebSocketRequest) { // 接受WebSocket请求 HttpContext.Current.AcceptWebSocketRequest(ProcessWebSocket); return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols); } return new HttpResponseMessage(HttpStatusCode.BadRequest); } private async Task ProcessWebSocket(AspNetWebSocketContext context) { WebSocket webSocket = context.WebSocket; var buffer = new byte[1024 * 4]; try { while (webSocket.State == WebSocketState.Open) { WebSocketReceiveResult result = await webSocket.ReceiveAsync( new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Text) { string message = Encoding.UTF8.GetString(buffer, 0, result.Count); Console.WriteLine($"Received: {message}"); // 返回响应 string response = $"Echo: {message}"; await webSocket.SendAsync( new ArraySegment<byte>(Encoding.UTF8.GetBytes(response)), WebSocketMessageType.Text, true, CancellationToken.None); } else if (result.MessageType == WebSocketMessageType.Close) { await webSocket.CloseAsync( WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } } } catch (Exception ex) { Console.WriteLine($"WebSocket error: {ex.Message}"); } finally { if (webSocket.State == WebSocketState.Open) { await webSocket.CloseAsync( WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } webSocket.Dispose(); } } }
- 移除独立监听器代码:删除
Global.asax.cs中所有关于HttpListener的代码块,保留原有的CORS配置即可。此时WebSocket握手请求会经过Web API的CORS处理,自动返回正确的跨域头。
方案2:给独立HttpListener添加CORS处理(不推荐)
如果必须保留独立的HttpListener,需要手动处理CORS响应头和预检请求:
修改HandleWebSocketRequest方法:
private void HandleWebSocketRequest(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); // 添加CORS响应头 context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, OPTIONS"); context.Response.Headers.Add("Access-Control-Allow-Headers", "Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version"); // 处理OPTIONS预检请求 if (context.Request.HttpMethod == "OPTIONS") { context.Response.StatusCode = 200; context.Response.Close(); listener.BeginGetContext(HandleWebSocketRequest, listener); return; } if (context.Request.IsWebSocketRequest) { HandleWebSocket(context); } else { context.Response.StatusCode = 404; context.Response.Close(); } listener.BeginGetContext(HandleWebSocketRequest, listener); }
注意:这种方式需要手动维护CORS逻辑,且在IIS环境下可能遇到端口权限、进程冲突等问题,仅作为临时方案使用。
内容的提问来源于stack exchange,提问作者Yusuf Keks
相关产品推荐
相关产品推荐

