Preserve HTTP status codes across target frameworks (#1767)

Copilot-Session: 9a49da98-c8d8-4b90-a6ed-50afe18ac981
This commit is contained in:
Pranav Senthilnathan 2026-07-29 12:35:12 -07:00 • committed by GitHub
commit 5c305450cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 10 deletions

View file

@ -69,10 +69,52 @@ internal static class HttpResponseMessageExtensions
? $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase})."
: $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase}). Response body: {responseBody}";
#if NET
return new HttpRequestException(message, inner: null, response.StatusCode);
#else
return new HttpRequestException(message);
#endif
return HttpRequestExceptionExtensions.Create(message, innerException: null, response.StatusCode);
}
}
/// <summary>
/// Helpers for preserving HTTP status codes on <see cref="HttpRequestException"/> across all target frameworks.
/// </summary>
internal static class HttpRequestExceptionExtensions
{
internal const string StatusCodeDataKey = "ModelContextProtocol.HttpStatusCode";
/// <summary>
/// Creates an <see cref="HttpRequestException"/> and preserves its HTTP status code.
/// </summary>
public static HttpRequestException Create(string message, Exception? innerException, HttpStatusCode? statusCode)
{
#if NET
var exception = new HttpRequestException(message, innerException, statusCode);
#else
var exception = new HttpRequestException(message, innerException);
#endif
if (statusCode is not null)
{
exception.Data[StatusCodeDataKey] = statusCode.Value;
}
return exception;
}
/// <summary>
/// Gets the preserved HTTP status code from an <see cref="HttpRequestException"/>.
/// </summary>
public static HttpStatusCode? GetStatusCode(this HttpRequestException exception)
{
#if NET
if (exception.StatusCode is { } statusCode)
{
return statusCode;
}
#endif
return exception.Data[StatusCodeDataKey] switch
{
HttpStatusCode storedStatusCode => storedStatusCode,
_ => null,
};
}
}

View file

@ -146,14 +146,10 @@ internal sealed partial class AutoDetectingClientSessionTransport : ITransport
// keep HttpRequestException as the surfaced type so existing callers can still catch it and read StatusCode.
await sseTransport.DisposeAsync().ConfigureAwait(false);
LogSseFallbackFailedAfterStreamableHttp(_name, sseError);
#if NET
throw new HttpRequestException(streamableHttpError.Message, sseError, streamableHttpError.StatusCode);
#else
// net472 has no HttpRequestException overload that carries a status code, so this target
// preserves the status text in the message but not a programmatic StatusCode. Preserving the
// status code is intentionally net5+ only rather than an oversight.
throw new HttpRequestException(streamableHttpError.Message, sseError);
#endif
throw HttpRequestExceptionExtensions.Create(
streamableHttpError.Message,
sseError,
streamableHttpError.GetStatusCode());
}
catch
{