using System.Net;
using System.Net.Http;
namespace ModelContextProtocol;
///
/// Extension methods for .
///
internal static class HttpResponseMessageExtensions
{
private const int MaxResponseBodyLength = 1024;
///
/// Throws an if the property is .
/// Unlike , this method includes the response body in the exception message
/// to help diagnose issues when the server returns error details in the response body.
///
/// The HTTP response message to check.
/// The token to monitor for cancellation requests.
/// A task that represents the asynchronous operation.
/// The response status code does not indicate success.
public static async Task EnsureSuccessStatusCodeWithResponseBodyAsync(this HttpResponseMessage response, CancellationToken cancellationToken = default)
{
if (!response.IsSuccessStatusCode)
{
throw await CreateHttpRequestExceptionWithBodyAsync(response, cancellationToken).ConfigureAwait(false);
}
}
///
/// Creates an for a non-success response, making a best-effort attempt to
/// include the server's response body in the exception message so the diagnostic isn't lost.
///
/// The non-success HTTP response message.
/// The token to monitor for cancellation requests.
/// An with the response status and, when available, its body.
public static async Task CreateHttpRequestExceptionWithBodyAsync(HttpResponseMessage response, CancellationToken cancellationToken = default)
{
string? responseBody = null;
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(5));
responseBody = await response.Content.ReadAsStringAsync(cts.Token).ConfigureAwait(false);
if (responseBody.Length > MaxResponseBodyLength)
{
responseBody = responseBody.Substring(0, MaxResponseBodyLength) + "...";
}
}
catch
{
// Ignore all errors reading the response body (e.g., stream closed, timeout, cancellation) - we'll throw without it.
}
return CreateHttpRequestException(response, responseBody);
}
///
/// Creates an for a non-success response, including the response body in the message.
///
/// The HTTP response message.
/// The response body content, if available.
/// An with the response details.
public static HttpRequestException CreateHttpRequestException(HttpResponseMessage response, string? responseBody)
{
int statusCodeInt = (int)response.StatusCode;
string message = string.IsNullOrEmpty(responseBody)
? $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase})."
: $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase}). Response body: {responseBody}";
return HttpRequestExceptionExtensions.Create(message, innerException: null, response.StatusCode);
}
}
///
/// Helpers for preserving HTTP status codes on across all target frameworks.
///
internal static class HttpRequestExceptionExtensions
{
internal const string StatusCodeDataKey = "ModelContextProtocol.HttpStatusCode";
///
/// Creates an and preserves its HTTP status code.
///
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;
}
///
/// Gets the preserved HTTP status code from an .
///
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,
};
}
}