-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Closed
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important workMarks issues that we want to fast track in order to unblock other important work
Milestone
Description
Exposing HTTP/2 and HTTP/3 protocol error codes from SocketsHttpHandler
This proposal builds on our QuicException proposal.
Background and Motivation
GRPC (and potentially other lower-level users) need a way to get the underlying HTTP/2 or HTTP/3 error codes in case a protocol error occurs.
The error code should be observable not only when an error occurs while calling an HttpClient method (#43239), but also when invoking Stream API-s on the response's content read stream (#62228).
Proposed design
- Define a new exception type
HttpProtocolException, and embed it asHttpRequestException.InnerException - Throw
ProtocolExceptiondirectly fromHttpResponsecontent read streams
namespace System.Net.Http;
public sealed class HttpProtocolException : IOException
{
public HttpProtocolException(HttpProtocolError errorCode, string message, Exception? innerException);
public HttpProtocolError ErrorCode { get; }
}
// Map enum values to H2 and H3 error codes
// H3 error codes can be 62 bit long
public enum HttpProtocolError : long
{
// Camel-cased names taken directly from the HTTP/2 spec
// https://datatracker.ietf.org/doc/html/rfc7540#section-7
NoError = 0x0,
ProtocolError,
InternalError,
FlowControlError,
SettingsTimeout,
StreamClosed,
FrameSizeError,
RefusedStream,
Cancel,
CompressionError,
EnhanceYourCalm,
InadequateSecurity,
Http11Required,
// Camel-cased names taken directly from the HTTP/3 spec
// https://datatracker.ietf.org/doc/html/rfc9114/#section-8.1
H3NoError = 0x100,
H3GeneralProtocolError,
H3InternalERror,
H3StreamCreationError,
H3ClosedCriticalStream,
H3FrameUnexpected,
H3FrameError,
H3ExcessiveLoad,
H3IdError,
H3SettingsError,
H3MissingSettings,
H3RequestRejected,
H3RequestCancelled,
H3RequestIncomplete,
H3MessageError,
H3ConnectError,
H3VersionFallback
}Edit: Added a public constructor.
Notes
No public constructors, because we prefer to do the bare minimum to deliver a feature. This means thatWinHttpHandlerand user-madeHttpClientHandlerswon't be able to throwHttpProtocolExceptionfor now. We can consider public constructors later, if necessary.- We throw
ProtocolErrorwhen a connection or stream is aborted, or when we detect a protocol violation ourselves ErrorCode >= 256means HTTP/3- In case of HTTP/3 connection or stream errors, we are embedding
QuicExceptionasProtocolException.InnerException - We don't throw
HttpProtocolExceptionfor transport-level errors
API Usage
Over HttpClient
using var client = new HttpClient();
try
{
var response = await client.GetStringAsync(".");
}
catch (HttpRequestException ex) when (ex.InnerException is ProtocolException protocolException)
{
Console.WriteLine("HTTP error code: " + protocolException.ProtocolErrorCode)
if (protocolException.InnerException is QuicException quicException)
Console.WriteLine("Underlying QUIC error: " + quicException.Message);
}
catch (HttpRequestException ex) when (ex.InnerException is AuthenticationException authenticationException) {
// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions
}Over response stream
using var client = new HttpClient();
using var response = await client.GetAsync(".", HttpCompletionOption.ResponseHeadersRead);
using var responseStream = await response.Content.ReadAsStreamAsync();
using var memoryStream = new MemoryStream();
try {
await responseStream.CopyToAsync(memoryStream);
}
catch (ProtocolException protocolException) {
// HTTP(2|3) protocol error
}
catch (QuicException quicException) {
// QUIC transport error
}
catch (IOException exception) when (ex.InnerException is SocketException socketException) {
// TCP transport error
}Alternative designs
Parallel independent exception types for HTTP/2 and HTTP/3
public class Http2ProtocolException : IOException
{
public int ErrorCode { get; }
}
// Use System.Net.Quic.QuicException for HTTP/3
// public class QuicException : IOException { }Usage
using var client = new HttpClient();
try
{
var response = await client.GetStringAsync("foo.bar");
}
// HTTP/2
catch (HttpRequestException ex) when (ex.InnerException is Http2ProtocolException protocolException)
{
Console.WriteLine(protocolException.ProtocolErrorCode)
}
// HTTP/3
catch (HttpRequestException ex) when (ex.InnerException is QuicException quicException)
{
Console.WriteLine(quicException.ApplicationErrorCode);
}Do not define an enum, use untyped long error codes
public sealed class HttpProtocolException : IOException
{
public long ErrorCode { get; }
}Risks
HttpProtocolErrorenum: implementations may send unspecified error codes. If spec evolves in the future, we will have to introduce new error codes. In both cases field would contain a value outside the enum range, so users would have a workaround.- Unless I'm missing something, this is not a breaking change, since
HttpProtocolExceptionis anIOException.
Metadata
Metadata
Assignees
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Net.HttpblockingMarks issues that we want to fast track in order to unblock other important workMarks issues that we want to fast track in order to unblock other important work