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

.NET 8.0 SAFE Stack中Elmish.Bridge WebSocket连接报错200无法含响应体

问题描述

将基于WebSocket、使用Elmish.Bridge的SAFE Stack程序升级到.NET 8.0并更新所有依赖后,建立连接时服务器抛出错误:
Responses to 'CONNECT' requests with the success status code '200' cannot have a response body. Use the 'IHttpExtendedConnectFeature' to accept and write to the 'CONNECT' stream.

应用核心配置

let configureApp (app:IApplicationBuilder) =
    app
        .UseCookiePolicy()
        .UseHsts()
        .UseHttpsRedirection()
        .UseAuthentication()
        .UseWebSockets()

let app = application {
    // ... 其他配置
    use_gzip
    // ... 其他配置
}

// Socket.server实现
let server : HttpFunc -> Http.HttpContext -> HttpFuncResult =
    Bridge.mkServer serverEndpoint init update
    |> Bridge.withServerHub hub
    |> Bridge.run Giraffe.server

关键错误日志片段

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 CONNECT https://localhost:8080/api/socket - - -
fail: Giraffe.Middleware.GiraffeErrorHandlerMiddleware[0]
      System.InvalidOperationException: Responses to 'CONNECT' requests with the success status code '200' cannot have a response body. Use the 'IHttpExtendedConnectFeature' to accept and write to the 'CONNECT' stream.

当前依赖版本

  • Saturn 0.16.1
  • Giraffe 6.2.0
  • Elmish.Bridge.Client 7.0.2
  • Elmish.Bridge.Server 7.0.0

解决方案

错误根源

.NET 8的Kestrel对HTTP/2 CONNECT 请求的校验逻辑更严格:成功的CONNECT响应不允许携带任何响应体。同时应用中启用的Gzip压缩中间件会尝试为WebSocket连接请求添加响应头/体,加上错误处理机制在连接失败时返回HTML页面,双重触发了该错误。

修复步骤

1. 排除WebSocket端点的响应压缩

移除Saturn自带的use_gzip,手动配置响应压缩并排除WebSocket路径,避免压缩中间件干扰WebSocket连接:

修改configureServices:

let configureServices (services : IServiceCollection) =
    let config = services.BuildServiceProvider().GetService<IConfiguration>()
    // 手动配置响应压缩,排除WebSocket端点
    services.AddResponseCompression(fun options ->
        options.EnableForHttps <- true
        options.ExcludedPaths.Add(BridgeShared.serverEndpoint)
    )
    |> ignore
    
    services
        .AddCookiePolicy(Action<_>(fun (options : CookiePolicyOptions)->
            options.CheckConsentNeeded <- fun context -> true
            options.MinimumSameSitePolicy <- SameSiteMode.Unspecified
            options.HandleSameSiteCookieCompatibility() |> ignore
            )            
        )
        .AddMicrosoftIdentityWebAppAuthentication(config, openIdConnectScheme = Auth.authScheme)
        |> ignore
    services.AddHostedService<Backup.RegularBackup>()

修改configureApp,添加响应压缩中间件:

let configureApp (app:IApplicationBuilder) =
    app
        .UseCookiePolicy()
        .UseHsts()
        .UseHttpsRedirection()
        .UseAuthentication()
        .UseWebSockets()
        // 确保压缩中间件在WebSocket中间件之后
        .UseResponseCompression()

修改app配置,移除use_gzip:

let app = application {
    pipe_through endpointPipe
    error_handler (fun ex _ -> pipeline { render_html (InternalError.layout ex) })
    host_config configureHost
    service_config configureServices
    app_config configureApp
    use_router appRouter
    url "https://0.0.0.0:8080/"
    memory_cache
    use_static "../../deploy/public/"
    // 移除自动gzip配置,改用手动配置
    // use_gzip
}

2. 调整错误处理,适配CONNECT请求

修改错误处理逻辑,避免为CONNECT请求返回HTML响应体:

let errorHandler ex ctx =
    if ctx.Request.Method = "CONNECT" then
        // 对CONNECT请求返回空响应,不携带任何内容
        ctx.Response.StatusCode <- 400
        Task.FromResult(ctx :> HttpContext) :> Task
    else
        pipeline { render_html (InternalError.layout ex) } ex ctx

let app = application {
    // ... 其他配置
    error_handler errorHandler
    // ... 其他配置
}

3. 提升WebSocket路由优先级

确保WebSocket路由在路由表中最优先匹配,避免被其他路由或404处理器拦截:

let appRouter =
    router {
        // 优先处理WebSocket请求
        get BridgeShared.serverEndpoint Socket.server
        // 其他路由配置
        // ...
        not_found_handler (htmlView NotFound.layout)
    }

内容的提问来源于stack exchange,提问作者Paul Whiting

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 05:50:41