NET 8.0 SAFE stack web socket CONNECT code 200 cannot have a response body

52 Views Asked by At

I tried to update a working SAFE stack program which uses Elmish.Bridge over Websockets to latest versions of nuget packages, node packages, and to .NET 8.0. I now get an error on the server when I try to establish the connection: 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.

App set up here:

let configureHost (hostBuilder : IHostBuilder) =
    System.Diagnostics.Trace.TraceInformation("Configure host...")
    let defaults =
        new System.Collections.Generic.Dictionary<string, string>()

    defaults.Add(WebHostDefaults.EnvironmentKey, "Development")

    let configuration =
        (new ConfigurationBuilder())
            .AddInMemoryCollection(defaults)
            .AddEnvironmentVariables("ASPNETCORE_")
            .Build()
    hostBuilder.ConfigureWebHostDefaults(fun webHostBuilder ->
        webHostBuilder
            .UseConfiguration(configuration)
            
            .ConfigureLogging(fun logging ->
                    logging
                        .ClearProviders()
                        .AddConsole()
                        .AddAzureWebAppDiagnostics()
                    |> ignore
                )
            |> ignore
    )

let configureApp (app:IApplicationBuilder) =
    app
        .UseCookiePolicy()
        .UseHsts() // See https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-3.1&tabs=visual-studio
        .UseHttpsRedirection() // As above
        .UseAuthentication()
        .UseWebSockets()
        
        
let configureServices (services : IServiceCollection) =
    let config = services.BuildServiceProvider().GetService<IConfiguration>()
    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>()
    
let endpointPipe = pipeline {
    plug head
    plug requestId
}

let appRouter =
    router {
        not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage

        get BridgeShared.serverEndpoint Socket.server
        // other forward and get routers
    }

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/"
    use_gzip
}

and Socket.server above is:

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

Complete error:

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 CONNECT https://localhost:8080/api/socket - - -
fail: Giraffe.Middleware.GiraffeErrorHandlerMiddleware[0]
      An unhandled exception has occurred while executing the request.
      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.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.StatusCheckWriteStream.CheckStatus()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.StatusCheckWriteStream.WriteAsync(ReadOnlyMemory`1 source, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionBody.WriteAsync(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionBody.WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
         at <StartupCode$Giraffe>[email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at [email protected]()
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
         at [email protected]()
fail: Giraffe.Middleware.GiraffeErrorHandlerMiddleware[0]
      An exception was thrown attempting to handle the original exception.
      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.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.StatusCheckWriteStream.CheckStatus()
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.StatusCheckWriteStream.WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
         at System.IO.Stream.WriteAsync(Byte[] buffer, Int32 offset, Int32 count)
         at <StartupCode$Giraffe>[email protected]()
         at [email protected]()
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished HTTP/2 CONNECT https://localhost:8080/api/socket - 200 - text/html;+charset=utf-8 24.0712ms
info: Microsoft.AspNetCore.Server.Kestrel[32]
      Connection id "0HN1DJDDEIH1G", Request id "0HN1DJDDEIH1G:00000011": the application completed without reading the entire request body.

All dependencies are the latest version. Saturn 0.16.1 Giraffe 6.2.0 Elmish.Bridge.Client 7.0.2 Elmish.Bridge.Server 7.0.0

1

There are 1 best solutions below

0
On

It turns out I need to forward rather than get the socket server. I guess that's a consequence of the change outlined here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets?view=aspnetcore-8.0#add-http2-websockets-support-for-existing-controllers

.NET 7 introduced Websockets over HTTP/2 support for Kestrel, the SignalR JavaScript client, and SignalR with Blazor WebAssembly. HTTP/2 WebSockets use CONNECT requests rather than GET. If you previously used [HttpGet("/path")] on your controller action method for Websocket requests, update it to use [Route("/path")] instead.

Thus:

let appRouter =
    router {
        not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage

        forward "" Socket.server
        // other forward and get routers
    }

(The endpoint is already given in the initialisation of Socket.server so "" seems to be sufficient here)