dotnet/aspnetcore · error · HttpError

errorMessage || response.statusText

Error message

errorMessage || response.statusText

What it means

When an HTTP response has response.ok === false, FetchHttpClient deserializes the response body as text and throws an HttpError whose message is that text, or the HTTP statusText if the body was empty. The HttpError carries the numeric statusCode so callers can branch on 401, 404, 500, etc.

Source

Thrown at src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts:143

                throw error;
            }
            this._logger.log(
                LogLevel.Warning,
                `Error from HTTP request. ${e}.`,
            );
            throw e;
        } finally {
            if (timeoutId) {
                clearTimeout(timeoutId);
            }
            if (request.abortSignal) {
                request.abortSignal.onabort = null;
            }
        }

        if (!response.ok) {
            const errorMessage = await deserializeContent(response, "text") as string;
            throw new HttpError(errorMessage || response.statusText, response.status);
        }

        const content = deserializeContent(response, request.responseType);
        const payload = await content;

        return new HttpResponse(
            response.status,
            response.statusText,
            payload,
        );
    }

    public getCookieString(url: string): string {
        let cookies: string = "";
        if (Platform.isNode && this._jar) {
            // @ts-ignore: unused variable
            this._jar.getCookies(url, (e, c) => cookies = c.join("; "));
        }

View on GitHub (pinned to 3600ca084e)

Solutions

  1. Catch HttpError specifically and inspect error.statusCode to differentiate 401 (re-auth) from 404 (wrong URL) from 5xx (server fault).
  2. Confirm the SignalR hub is mapped at the URL you are hitting (e.g. app.MapHub<MyHub>("/hub")).
  3. Verify authentication: ensure access tokens are supplied and that CORS allows credentials if withCredentials is true.
  4. If the message is HTML, a proxy/firewall is intercepting; whitelist the hub route.

Example fix

// before
try {
  await connection.start();
} catch (e) {
  console.error(e.message);
}

// after
import { HttpError } from "@microsoft/signalr";
try {
  await connection.start();
} catch (e) {
  if (e instanceof HttpError) {
    if (e.statusCode === 401) await refreshToken();
    else if (e.statusCode === 404) throw new Error("Hub not found at " + url);
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

function isOk(status: number): boolean { return status >= 200 && status < 300; }
// before calling connection.start, verify reachability
const probe = await fetch(url + "/negotiate?negotiateVersion=1", { method: "POST" });
if (!isOk(probe.status)) console.warn("Hub non-OK:", probe.status);

Type guard

function isHttpError(e: unknown): e is signalR.HttpError {
  return e instanceof Error && typeof (e as any).statusCode === "number";
}

Try / catch

import { HttpError } from "@microsoft/signalr";
try { await connection.start(); }
catch (e) {
  if (e instanceof HttpError) {
    switch (e.statusCode) {
      case 401: await refreshAccessToken(); break;
      case 404: throw new Error(`Hub not found at ${url}`);
      default: throw e;
    }
  } else throw e;
}

Prevention

When it happens

Trigger: Any non-2xx response from a SignalR endpoint: negotiate returns 401 Unauthorized, the hub route returns 404, a long-poll request gets 500, or a CDN/proxy returns a 502 body. Surfaced through HttpConnection.start -> negotiate/transport requests.

Common situations: Hub endpoint not mapped on the server (404). Authentication failure or expired token (401/403). CORS preflight rejected (no ok response). Server-side exception during the request (500). Reverse proxy returning an HTML error page that becomes the error message.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/3c9f9d09cbb0ffec. Report an issue: GitHub.