This proposal builds on the decisions made in our QUIC Exceptions Design proposal.
- Define a new exception type
ProtocolException
, and embed it asHttpRequestException.InnerException
- Throw
ProtocolException
directly fromHttpResponse
content read streams - In case of HTTP/3, embed
QuicException
asProtocolException.InnerException
public class ProtocolException : IOException
{
public ProtocolException(long? protocolErrorCode, string message, Exception innerException) { }
// ProtocolErrorCode == null means we have a QuicException with ApplicationLevelErrorCode == null
public long? ProtocolErrorCode { get; }
// CONSIDER:
// Normally, ProtocolErrorCode >= 256 means HTTP/3 but the peer free to put there anything.
// Exposing exposing HTTP version directly might help with that & it can be also convenient:
// public Version HttpVersion { get; }
}
using var client = new HttpClient();
try
{
var response = await client.GetStringAsync(".");
}
catch (HttpRequestException ex) when (ex.InnerException is ProtocolException protocolException)
{
Console.WriteLine("Error: " + protocolException.ProtocolErrorCode)
if (protocolException.InnerException is QuicException quicException)
Console.WriteLine("Underlying QUIC error: " + quicException.Message);
}
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);
}
// HTTP/2 protocol errors
catch (ProtocolException protocolException)
{
Console.WriteLine("Error: " + protocolException.ProtocolErrorCode)
}
- (+) Single exception to catch them all
- (-) Embedding
IOException
into anotherIOException
is weird
Currently, our internal HTTP/2 exceptions report ProtocolError in the following cases:
- GOAWAY received
- RST_STREAM received
- We detect a protocol violation
Add an enum (now or in the future) that maps HTTP/2 cases and QuicException.QuicError
to something like:
public enum ProtocolErrorReason
{
ConnectionAborted,
StreamAborted,
ProtocolViolationByPeer
}
public class Http2ProtocolException : IOException
{
public Http2ProtocolException(long protocolErrorCode, string message, Exception innerException) { }
public int ProtocolErrorCode { get; }
}
// Use System.Net.Quic.QuicException for HTTP/3
// public class QuicException : IOException { }
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);
}