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

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;
    }
}

错误原因分析

  1. WebSocket的HttpListener未应用CORS策略:你在Application_Start中初始化的HttpListener是独立于ASP.NET Web API的HTTP监听服务,Web API配置的EnableCorsAttribute只对Web API路由处理的请求生效,不会作用于这个独立监听器的请求。当非localhost客户端发起WebSocket握手时,监听器不会返回任何CORS响应头,浏览器因此触发跨域错误。

  2. IIS托管与独立监听器的冲突:你的应用托管在IIS中,但手动启动的HttpListener监听了*:9999端口,这会和IIS的端口管理机制冲突,而且在IIS环境下直接使用HttpListener需要额外的权限,也不符合IIS托管应用的最佳实践。


解决方法

方案1:改用ASP.NET Web API原生WebSocket支持(推荐)

利用Web API原生的WebSocket支持,复用已有的CORS配置,避免独立监听器的问题:

  1. 添加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" }
        );
    }
}
  1. 创建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();
        }
    }
}
  1. 移除独立监听器代码:删除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:45:02