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 as HttpRequestException.InnerException - Throw
ProtocolException directly from HttpResponse content read streams
namespaceSystem.Net.Http;publicsealedclassHttpProtocolException:IOException{publicHttpProtocolException(HttpProtocolErrorerrorCode,stringmessage,Exception?innerException);publicHttpProtocolErrorErrorCode{get;}}// Map enum values to H2 and H3 error codes// H3 error codes can be 62 bit longpublicenumHttpProtocolError:long{// Camel-cased names taken directly from the HTTP/2 spec// https://datatracker.ietf.org/doc/html/rfc7540#section-7NoError=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.1H3NoError=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 that WinHttpHandler and user-made HttpClientHandlers won't be able to throw HttpProtocolException for now. We can consider public constructors later, if necessary.- We throw
ProtocolError when a connection or stream is aborted, or when we detect a protocol violation ourselves ErrorCode >= 256 means HTTP/3- In case of HTTP/3 connection or stream errors, we are embedding
QuicException as ProtocolException.InnerException - We don't throw
HttpProtocolException for transport-level errors
API Usage
Over HttpClient
usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync(".");}catch(HttpRequestExceptionex)when(ex.InnerExceptionisProtocolExceptionprotocolException){Console.WriteLine("HTTP error code: "+protocolException.ProtocolErrorCode)
if (protocolException.InnerExceptionisQuicExceptionquicException)
Console.WriteLine("Underlying QUIC error: "+quicException.Message);}catch(HttpRequestExceptionex)when(ex.InnerExceptionisAuthenticationExceptionauthenticationException){// TLS authentication error. Can originate from QUIC, we map some errors to high-level .NET exceptions}Over response stream
usingvarclient=newHttpClient();usingvarresponse=awaitclient.GetAsync(".",HttpCompletionOption.ResponseHeadersRead);usingvarresponseStream=awaitresponse.Content.ReadAsStreamAsync();usingvarmemoryStream=newMemoryStream();try{awaitresponseStream.CopyToAsync(memoryStream);}catch(ProtocolExceptionprotocolException){// HTTP(2|3) protocol error}catch(QuicExceptionquicException){// QUIC transport error}catch(IOExceptionexception)when(ex.InnerExceptionisSocketExceptionsocketException){// TCP transport error}Alternative designs
Parallel independent exception types for HTTP/2 and HTTP/3
publicclassHttp2ProtocolException:IOException{publicintErrorCode{get;}}// Use System.Net.Quic.QuicException for HTTP/3// public class QuicException : IOException { }Usage
usingvarclient=newHttpClient();try{varresponse=awaitclient.GetStringAsync("foo.bar");}// HTTP/2catch(HttpRequestExceptionex)when(ex.InnerExceptionisHttp2ProtocolExceptionprotocolException){Console.WriteLine(protocolException.ProtocolErrorCode)}// HTTP/3catch(HttpRequestExceptionex)when(ex.InnerExceptionisQuicExceptionquicException){Console.WriteLine(quicException.ApplicationErrorCode);}Do not define an enum, use untyped long error codes
publicsealedclassHttpProtocolException:IOException{publiclongErrorCode{get;}}Risks
HttpProtocolError enum: 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
HttpProtocolException is an IOException.
Exposing HTTP/2 and HTTP/3 protocol error codes from
SocketsHttpHandlerThis proposal builds on our
QuicExceptionproposal.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
HttpClientmethod (#43239), but also when invokingStreamAPI-s on the response's content read stream (#62228).Proposed design
HttpProtocolException, and embed it asHttpRequestException.InnerExceptionProtocolExceptiondirectly fromHttpResponsecontent read streamsEdit: 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.ProtocolErrorwhen a connection or stream is aborted, or when we detect a protocol violation ourselvesErrorCode >= 256means HTTP/3QuicExceptionasProtocolException.InnerExceptionHttpProtocolExceptionfor transport-level errorsAPI Usage
Over HttpClient
Over response stream
Alternative designs
Parallel independent exception types for HTTP/2 and HTTP/3
Usage
Do not define an enum, use untyped
longerror codesRisks
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.HttpProtocolExceptionis anIOException.