.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
相关产品推荐
相关产品推荐

