SignalR:服务器返回了状态码'200',但预期的是状态码'101'。

4
我想通过SignalR将我的Maui Blazor应用程序(Windows)与.NET Blazor服务器应用程序连接起来,以便我可以将消息从服务器推送到客户端。我还使用WPF桌面应用程序进行了测试,结果是一样的,握手失败。
我正在使用的版本是Microsoft.AspNetCore.SignalR.Client" Version="6.0.1",我也尝试了7.0.1。
有趣的是,在@localhost上一切正常工作。而Blazor Wasm与同一服务器完全正常地使用了SignalR。
服务器端托管在@akamai。
错误消息的详细信息:
Microsoft.AspNetCore.SignalR.Client.HubConnection: Trace: Waiting on Connection Lock in StartAsyncInner (/_/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs:247).
Microsoft.AspNetCore.SignalR.Client.HubConnection: Trace: The HubConnection is attempting to transition from the Disconnected state to the Connecting state.
Microsoft.AspNetCore.SignalR.Client.HubConnection: Debug: Starting HubConnection.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Starting HttpConnection.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Establishing connection with server at 'https://staging.vetat.work/hub/printertool'.
Microsoft.AspNetCore.Http.Connections.Client.Internal.LoggingHttpMessageHandler: Trace: Sending HTTP request POST 'https://staging.asd/hub/printertool/negotiate?negotiateVersion=1'.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Established connection 'B07mkUpGtdoEwG6C9uQZbA' with the server.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Starting transport 'WebSockets' with Url: https://staging.vetat.work/hub/printertool.
Microsoft.AspNetCore.Http.Connections.Client.Internal.WebSocketsTransport: Information: Starting transport. Transfer mode: Text. Url: 'wss://staging.vetat.work/hub/printertool?id=UGcgq9uJEOL5hZkA1iZxCQ'.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Error: Failed to start connection. Error starting transport 'WebSockets'.

System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '200' when status code '101' was expected.
   at System.Net.WebSockets.WebSocketHandle.ValidateResponse(HttpResponseMessage response, String secValue, ClientWebSocketOptions options)
   at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, HttpMessageInvoker invoker, CancellationToken cancellationToken, ClientWebSocketOptions options)
   at System.Net.WebSockets.ClientWebSocket.ConnectAsyncCore(Uri uri, HttpMessageInvoker invoker, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.Internal.WebSocketsTransport.DefaultWebSocketFactory(WebSocketConnectionContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.Internal.WebSocketsTransport.StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Skipping transport WebSockets because it failed to initialize.

System.Net.WebSockets.WebSocketException (0x80004005): The server returned status code '200' when status code '101' was expected.
   at System.Net.WebSockets.WebSocketHandle.ValidateResponse(HttpResponseMessage response, String secValue, ClientWebSocketOptions options)
   at System.Net.WebSockets.WebSocketHandle.ConnectAsync(Uri uri, HttpMessageInvoker invoker, CancellationToken cancellationToken, ClientWebSocketOptions options)
   at System.Net.WebSockets.ClientWebSocket.ConnectAsyncCore(Uri uri, HttpMessageInvoker invoker, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.Internal.WebSocketsTransport.DefaultWebSocketFactory(WebSocketConnectionContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.Internal.WebSocketsTransport.StartAsync(Uri url, TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartTransport(Uri connectUrl, HttpTransportType transportType, TransferFormat transferFormat, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken)
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Establishing connection with server at 'https://staging.asd/hub/printertool'.
Microsoft.AspNetCore.Http.Connections.Client.Internal.LoggingHttpMessageHandler: Trace: Sending HTTP request POST 'https://staging.asd/hub/printertool/negotiate?negotiateVersion=1'.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Established connection 'trPpDN3vsUzTTUqSu0LdwQ' with the server.
Microsoft.AspNetCore.Http.Connections.Client.HttpConnection: Debug: Starting transport 'ServerSentEvents' with Url: https://staging.asd/hub/printertool.
Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport: Information: Starting transport. Transfer mode: Text.
Microsoft.AspNetCore.Http.Connections.Client.Internal.LoggingHttpMessageHandler: Trace: Sending HTTP request GET 'https://staging.asd/hub/printertool?id=snz3w-l4JZ8sYDtO8cK9jQ'.


我试过maui应用程序,也试过调试、记录日志和wpf应用程序。我希望能够从服务器向客户端发送消息。
   var url = _configuration[MauiConstants.AppConfig.SignalR];

        _connection = new HubConnectionBuilder()
            .WithUrl(url, options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(token);
            })
            .ConfigureLogging(logging =>
            {
                // Log to the Console
                logging.AddDebug();

                // This will set ALL logging to Debug level
                logging.SetMinimumLevel(LogLevel.Trace);
            })
            .WithAutomaticReconnect()
            .Build();

为了授权,我正在使用Bearer令牌。
这是服务器配置:
if (config.SignalR.AllowCors)
    {
        _ = app.UseCors(builder =>
          {
              _ = builder.AllowAnyMethod()
                         .AllowAnyHeader()
                         .AllowCredentials()
                         .WithOrigins(config.SignalR.CorsAddress)
                         .SetIsOriginAllowedToAllowWildcardSubdomains();
          });
    }


_ = authenticationBuilder.AddJwtBearer(AuthConstant.Scheme.PrinterTool, options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.Audience = $"{Audience.PrinterTool:G}";
        var signingKey = Convert.FromBase64String(secretKey);
        options.TokenValidationParameters = new TokenValidationParameters
        {
            AuthenticationType = AuthConstant.Scheme.PrinterTool,
            ValidateIssuer = false,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(signingKey),
            ClockSkew = TimeSpan.FromMinutes(Convert.ToDouble(config.AdminAppAuthentication.ExpiresIn))
        };

        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers["Token-Expired"] = "true";
                }

                return Task.CompletedTask;
            },
            OnMessageReceived = context =>
            {
                //If the request is for our hub
                var path = context.HttpContext.Request.Path;

                if (path.StartsWithSegments(config.SignalR.PrinterToolMainHubRoute))
                {
                    var authorization = context.Request.Headers?.Authorization.FirstOrDefault(a => a.StartsWith("Bearer "));
                    if (!string.IsNullOrEmpty(authorization))
                    {
                        context.Token = authorization.Substring("Bearer ".Length);
                    }
                }

                return Task.CompletedTask;
            }
        };
    });

1
HTTP有两种模式:1)版本1.0是流模式,您只会收到一个响应;2)版本1.1是分块模式。在接收到每个分块后,您需要发送下一个分块消息。HTTPClient(版本1.0和1.1)与HTTPSocket之间存在差异,它们不兼容。HTTPClient在每个请求/响应之后关闭连接,而HTTPSocket则保持连接直到关闭。状态码101表示连接从HTTP Client切换到HTTP Socket。看起来服务器不支持Web套接字,并使用HTTP Client进行请求/响应。 - undefined
但是如果服务器不支持 WebSocket,那么在 wasm 和 Blazor 服务器之间如何工作呢?我在我的 Maui 应用程序中使用相同的逻辑。 - undefined
有些情况下,在具有两个端口(两个连接)的代码中会使用继电器。1)HttpClient 2)HTTP Socket 然后将消息从一个连接传递到另一个连接。 - undefined
@jdweng 你提出什么解决方案? - undefined
本地服务器和部署服务器之间存在差异,原因不明。根据异常信息显示可能是HTTP客户端与WebSocket之间的问题。您需要找出本地服务器和部署服务器之间的差异所在。 - undefined
1个回答

0
我想这个问题已经解决了。 我之前也遇到了同样的问题,我通过修改生产服务器上的nginx配置解决了它。
修改如下:
proxy_set_header Connection keep-alive;

通过这个:
proxy_set_header Connection $http_connection;

完整的nginx配置:
server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name api.your-domain.com;
    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;

    ssl_certificate /etc/nginx/ssl/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://172.17.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_headers_hash_max_size 512;
        proxy_headers_hash_bucket_size 128;
    }
}

源代码:SignalR在Nginx后面的ASP.NET Core中

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接