SignalR returns "Connection ID required" when ran in an AWS ECS container

986 Views Asked by At

I am trying to get a .Net Core 3.1 SignalR Web API with an Angular front end to work with websockets.

The code works perfectly fine when ran locally, either from within in the IDE or via docker run. However, once the code gets deployed to an ECS instance in AWS behind an API Gateway the web sockets refuse to connect.

I setup my mappings like so:

app.UsePathBase("/ui");
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
               "default",
              "{controller}/{action=Index}/{id?}")
             .RequireCors(PolicyName);
    endpoints.MapHub<SessionHub>("/ws/session");
    endpoints.MapHub<SessionsHub>("/ws/sessions");
});

And on the client I connect to the hub like so:

this.sessionsHubConnection = new HubConnectionBuilder()
  .withUrl(`${window.origin}/ws/sessions`, {
    skipNegotiation: true,
    transport: HttpTransportType.WebSockets,
    accessTokenFactory: () => this.getAccessToken()
  })
  .withAutomaticReconnect()
  .build();

The following Fiddler trace shows the initial HTTP request to initialize the websocket connection and the error being returned by kestrel. enter image description here

I tweaked my web socket middleware for handling the access token to also Console.Write some additional debugging statements that I think might prove insightful:

public async Task Invoke(HttpContext httpContext)
{
    var request = httpContext.Request;
    Console.WriteLine($"Starting connection id: {httpContext.Connection.Id}");

    // web sockets cannot pass headers so we must take the access token from query param and
    // add it to the header before authentication middleware runs
    if (request.Path.StartsWithSegments("/ws", StringComparison.OrdinalIgnoreCase)
        &&
        request.Query.TryGetValue("access_token", out var accessToken))
    {
        request.Headers.Add("Authorization", $"Bearer {accessToken}");
    }

    try
    {
        var sb = new StringBuilder();
        sb.AppendLine($"Connection Id: {httpContext.Connection.Id}");
        Console.WriteLine(sb.ToString());

        await _next(httpContext);

        sb = new StringBuilder();
        sb.AppendLine($"Status code {httpContext.Response.StatusCode}"); <-- this line
        sb.AppendLine($"Connection Id: {httpContext.Connection.Id}");    <-- and this line
        Console.WriteLine(sb.ToString());
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
        Console.WriteLine(e.StackTrace);
        throw;
    }
}

And in the AWS logs you can see that the connection Id is present but is being ignored by the EndpointMiddleware(?) for some reason.

enter image description here

Any idea what could be causing this? Two ideas I have yet to be able to rule out are:

  1. The HTTP/S termination at our API gateway is confusing Kestrel since the browser client is building the socket request under HTTPS, but as far as Kestrel is concerned everything is communicating over HTTP?
  2. app.UsePathBase("/ui"); is confusing Kestrel and all the web socket paths should actually be /ui/ws/session?
0

There are 0 best solutions below