dotnet/yarp · error · InvalidOperationException
Expected status 101 Switching Protocols!
Error message
Expected status 101 Switching Protocols!
What it means
Asserted by RawUpgradeScenario in the SampleClient. It sends an HTTP/1.1 request with Connection: upgrade to /api/rawupgrade and expects a 101 Switching Protocols response so it can take over the raw stream for a bidirectional echo test. Anything other than 101 throws InvalidOperationException, meaning the upgrade was not negotiated.
Source
Thrown at testassets/TestClient/Scenarios/RawUpgradeScenario.cs:36
{
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false,
UseProxy = false
};
using var client = new HttpMessageInvoker(handler);
var targetUri = new Uri(new Uri(args.Target, UriKind.Absolute), "api/rawupgrade");
var stopwatch = Stopwatch.StartNew();
var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
request.Headers.TryAddWithoutValidation("Connection", "upgrade");
request.Version = new Version(1, 1);
Console.WriteLine($"Calling {targetUri} with upgradable HTTP/1.1");
var response = await client.SendAsync(request, cancellation);
Console.WriteLine($"Received response: {(int)response.StatusCode} in {stopwatch.ElapsedMilliseconds} ms");
if (response.StatusCode != HttpStatusCode.SwitchingProtocols)
{
throw new InvalidOperationException("Expected status 101 Switching Protocols!");
}
var rawStream = await response.Content.ReadAsStreamAsync(cancellation);
Console.WriteLine("Acquired upgraded stream. Testing bidirectional echo...");
stopwatch.Restart();
var buffer = new byte[1];
for (var i = 0; i <= 255; i++)
{
buffer[0] = (byte)i;
await rawStream.WriteAsync(buffer, cancellation);
var read = await rawStream.ReadAsync(buffer, cancellation);
if (i == 255)
{
if (read != 0)
{
throw new Exception($"Read {read} bytes, expected 0 after sending Goodbye.");
}
View on GitHub (pinned to bd11867bee)
Solutions
- Target the matching test server that implements /api/rawupgrade with a 101 response.
- Ensure no intermediary strips the Connection or Upgrade headers.
- Verify the request is sent as HTTP/1.1 (the scenario forces it, but a misconfigured handler could alter behavior).
- Inspect the printed received status to diagnose (e.g. 200 means upgrade was ignored).
Defensive patterns
Strategy: try-catch
Validate before calling
// Probe upgrade support before the scenario.
using var probe = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, new Uri(args.Target, "api/rawupgrade"));
req.Headers.TryAddWithoutValidation("Connection", "upgrade");
req.Version = new Version(1, 1);
var resp = await probe.SendAsync(req);
if (resp.StatusCode != HttpStatusCode.SwitchingProtocols)
Console.Error.WriteLine($"Warning: /api/rawupgrade returned {resp.StatusCode}, scenario will fail."); Try / catch
try { await RawUpgradeScenario.RunAsync(client, args, cancellation); }
catch (InvalidOperationException ex) when (ex.Message.Contains("101 Switching Protocols")) {
Console.Error.WriteLine("Raw upgrade not negotiated; check --target and intermediaries.");
return 1;
} Prevention
- Target only the test backend that returns 101 for /api/rawupgrade.
- Ensure no proxy strips Connection/Upgrade headers.
- Verify the request path is HTTP/1.1 as the scenario requires.
When it happens
Trigger: Running SampleClient --scenario RawUpgradeScenario against a server that does not return 101 for /api/rawupgrade, or where an intermediary refuses the upgrade.
Common situations: Wrong --target; the rawupgrade test endpoint missing; a reverse proxy/CDN in front that downgrades or drops the Connection: upgrade header; HTTP/1.0 target that cannot switch protocols.
Related errors
- Read {read} bytes, expected 0 after sending Goodbye.
- Read {read} bytes, expected 1.
- Received {buffer[0]}, expected {i}.
- Expected status 409 Conflict!
- Expected to receive a text message, got '{message.MessageTyp
AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13).
Data as JSON: /api/errors/df5b4c316ba0352e.
Report an issue: GitHub.