Fixes #76644, fixes #82168.
{
await StartTransferTypeAndErrorServer(transferType, transferError, async uri =>
{
- await Assert.ThrowsAsync<IOException>(() => ReadAsStreamHelper(uri));
+ if (IsWinHttpHandler)
+ {
+ await Assert.ThrowsAsync<IOException>(() => ReadAsStreamHelper(uri));
+ }
+ else
+ {
+ HttpIOException exception = await Assert.ThrowsAsync<HttpIOException>(() => ReadAsStreamHelper(uri));
+ Assert.Equal(HttpRequestError.ResponseEnded, exception.HttpRequestError);
+ }
+
});
}
protected virtual System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; }
protected internal abstract bool TryComputeLength(out long length);
}
+ public class HttpIOException : System.IO.IOException
+ {
+ public System.Net.Http.HttpRequestError HttpRequestError { get { throw null; } }
+ public HttpIOException(System.Net.Http.HttpRequestError httpRequestError, string? message = null, System.Exception? innerException = null) { }
+ }
public abstract partial class HttpMessageHandler : System.IDisposable
{
protected HttpMessageHandler() { }
public static bool operator !=(System.Net.Http.HttpMethod? left, System.Net.Http.HttpMethod? right) { throw null; }
public override string ToString() { throw null; }
}
- public sealed class HttpProtocolException : System.IO.IOException
+ public sealed class HttpProtocolException : System.Net.Http.HttpIOException
{
- public HttpProtocolException(long errorCode, string? message, System.Exception? innerException) { }
+ public HttpProtocolException(long errorCode, string? message, System.Exception? innerException) : base (default(System.Net.Http.HttpRequestError), default(string?), default(System.Exception?)) { }
public long ErrorCode { get { throw null; } }
}
+ public enum HttpRequestError
+ {
+ Unknown = 0,
+ NameResolutionError,
+ ConnectionError,
+ SecureConnectionError,
+ HttpProtocolError,
+ ExtendedConnectNotSupported,
+ VersionNegotiationError,
+ UserAuthenticationError,
+ ProxyTunnelError,
+ InvalidResponse,
+ ResponseEnded,
+ ConfigurationLimitExceeded,
+ }
public partial class HttpRequestException : System.Exception
{
public HttpRequestException() { }
public HttpRequestException(string? message) { }
public HttpRequestException(string? message, System.Exception? inner) { }
public HttpRequestException(string? message, System.Exception? inner, System.Net.HttpStatusCode? statusCode) { }
+ public HttpRequestException(string? message, System.Exception? inner = null, System.Net.HttpStatusCode? statusCode = null, System.Net.Http.HttpRequestError? httpRequestError = null) { }
+ public System.Net.Http.HttpRequestError? HttpRequestError { get { throw null; } }
public System.Net.HttpStatusCode? StatusCode { get { throw null; } }
}
public partial class HttpRequestMessage : System.IDisposable
<data name="net_http_proxy_tunnel_returned_failure_status_code" xml:space="preserve">
<value>The proxy tunnel request to proxy '{0}' failed with status code '{1}'."</value>
</data>
+ <data name="net_http_proxy_tunnel_error" xml:space="preserve">
+ <value>An error occurred while establishing a connection to the proxy tunnel.</value>
+ </data>
<data name="PlatformNotSupported_NetHttp" xml:space="preserve">
<value>System.Net.Http is not supported on this platform.</value>
</data>
<Compile Include="System\Net\Http\HttpParseResult.cs" />
<Compile Include="System\Net\Http\HttpProtocolException.cs" />
<Compile Include="System\Net\Http\HttpRequestException.cs" />
+ <Compile Include="System\Net\Http\HttpRequestError.cs" />
<Compile Include="System\Net\Http\HttpRequestMessage.cs" />
<Compile Include="System\Net\Http\HttpRequestOptions.cs" />
<Compile Include="System\Net\Http\HttpRequestOptionsKey.cs" />
<Compile Include="System\Net\Http\HttpResponseMessage.cs" />
+ <Compile Include="System\Net\Http\HttpIOException.cs" />
<Compile Include="System\Net\Http\HttpRuleParser.cs" />
<Compile Include="System\Net\Http\HttpTelemetry.cs" />
<Compile Include="System\Net\Http\HttpVersionPolicy.cs" />
if (contentLength > maxBufferSize)
{
- error = new HttpRequestException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_content_buffersize_exceeded, maxBufferSize));
+ error = CreateOverCapacityException(maxBufferSize);
return null;
}
internal static Exception WrapStreamCopyException(Exception e)
{
Debug.Assert(StreamCopyExceptionNeedsWrapping(e));
- return new HttpRequestException(SR.net_http_content_stream_copy_error, e);
+ HttpRequestError error = e is HttpIOException ioEx ? ioEx.HttpRequestError : HttpRequestError.Unknown;
+ return new HttpRequestException(SR.net_http_content_stream_copy_error, e, httpRequestError: error);
}
private static int GetPreambleLength(ArraySegment<byte> buffer, Encoding encoding)
return returnFunc(state);
}
- private static HttpRequestException CreateOverCapacityException(int maxBufferSize)
+ private static HttpRequestException CreateOverCapacityException(long maxBufferSize)
{
- return new HttpRequestException(SR.Format(SR.net_http_content_buffersize_exceeded, maxBufferSize));
+ return new HttpRequestException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_content_buffersize_exceeded, maxBufferSize), httpRequestError: HttpRequestError.ConfigurationLimitExceeded);
}
internal sealed class LimitMemoryStream : MemoryStream
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO;
+
+namespace System.Net.Http
+{
+ /// <summary>
+ /// An exception thrown when an error occurs while reading the response.
+ /// </summary>
+ public class HttpIOException : IOException
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpIOException"/> class.
+ /// </summary>
+ /// <param name="httpRequestError">The <see cref="Http.HttpRequestError"/> that caused the exception.</param>
+ /// <param name="message">The message string describing the error.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ public HttpIOException(HttpRequestError httpRequestError, string? message = null, Exception? innerException = null)
+ : base(message, innerException)
+ {
+ HttpRequestError = httpRequestError;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="Http.HttpRequestError"/> that caused the exception.
+ /// </summary>
+ public HttpRequestError HttpRequestError { get; }
+
+ /// <inheritdoc />
+ public override string Message => $"{base.Message} ({HttpRequestError})";
+ }
+}
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO;
+using System.Net.Quic;
namespace System.Net.Http
{
/// When calling <see cref="Stream"/> methods on the stream returned by <see cref="HttpContent.ReadAsStream()"/> or
/// <see cref="HttpContent.ReadAsStreamAsync(Threading.CancellationToken)"/>, <see cref="HttpProtocolException"/> can be thrown directly.
/// </remarks>
- public sealed class HttpProtocolException : IOException
+ public sealed class HttpProtocolException : HttpIOException
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpProtocolException"/> class with the specified error code,
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public HttpProtocolException(long errorCode, string message, Exception? innerException)
- : base(message, innerException)
+ : base(Http.HttpRequestError.HttpProtocolError, message, innerException)
{
ErrorCode = errorCode;
}
return new HttpProtocolException((long)protocolError, message, null);
}
- internal static HttpProtocolException CreateHttp3StreamException(Http3ErrorCode protocolError)
+ internal static HttpProtocolException CreateHttp3StreamException(Http3ErrorCode protocolError, QuicException innerException)
{
string message = SR.Format(SR.net_http_http3_stream_error, GetName(protocolError), ((int)protocolError).ToString("x"));
- return new HttpProtocolException((long)protocolError, message, null);
+ return new HttpProtocolException((long)protocolError, message, innerException);
}
internal static HttpProtocolException CreateHttp3ConnectionException(Http3ErrorCode protocolError, string? message = null)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Net.Http
+{
+ /// <summary>
+ /// Defines error categories representing the reason for <see cref="HttpRequestException"/> or <see cref="HttpIOException"/>.
+ /// </summary>
+ public enum HttpRequestError
+ {
+ /// <summary>
+ /// A generic or unknown error occurred.
+ /// </summary>
+ Unknown = 0,
+
+ /// <summary>
+ /// The DNS name resolution failed.
+ /// </summary>
+ NameResolutionError,
+
+ /// <summary>
+ /// A transport-level failure occurred while connecting to the remote endpoint.
+ /// </summary>
+ ConnectionError,
+
+ /// <summary>
+ /// An error occurred during the TLS handshake.
+ /// </summary>
+ SecureConnectionError,
+
+ /// <summary>
+ /// An HTTP/2 or HTTP/3 protocol error occurred.
+ /// </summary>
+ HttpProtocolError,
+
+ /// <summary>
+ /// Extended CONNECT for WebSockets over HTTP/2 is not supported by the peer.
+ /// </summary>
+ ExtendedConnectNotSupported,
+
+ /// <summary>
+ /// Cannot negotiate the HTTP Version requested.
+ /// </summary>
+ VersionNegotiationError,
+
+ /// <summary>
+ /// The authentication failed.
+ /// </summary>
+ UserAuthenticationError,
+
+ /// <summary>
+ /// An error occurred while establishing a connection to the proxy tunnel.
+ /// </summary>
+ ProxyTunnelError,
+
+ /// <summary>
+ /// An invalid or malformed response has been received.
+ /// </summary>
+ InvalidResponse,
+
+ /// <summary>
+ /// The response ended prematurely.
+ /// </summary>
+ ResponseEnded,
+
+ /// <summary>
+ /// The response exceeded a pre-configured limit such as <see cref="HttpClient.MaxResponseContentBufferSize"/> or <see cref="HttpClientHandler.MaxResponseHeadersLength"/>.
+ /// </summary>
+ ConfigurationLimitExceeded,
+ }
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-
namespace System.Net.Http
{
public class HttpRequestException : Exception
internal RequestRetryType AllowRetry { get; } = RequestRetryType.NoRetry;
public HttpRequestException()
- : this(null, null)
{ }
public HttpRequestException(string? message)
- : this(message, null)
+ : base(message)
{ }
public HttpRequestException(string? message, Exception? inner)
}
/// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestException" /> class with a specific message an inner exception, and an HTTP status code and an <see cref="HttpRequestError"/>.
+ /// </summary>
+ /// <param name="message">A message that describes the current exception.</param>
+ /// <param name="inner">The inner exception.</param>
+ /// <param name="statusCode">The HTTP status code.</param>
+ /// <param name="httpRequestError">The <see cref="HttpRequestError"/> that caused the exception.</param>
+ public HttpRequestException(string? message, Exception? inner = null, HttpStatusCode? statusCode = null, HttpRequestError? httpRequestError = null)
+ : this(message, inner, statusCode)
+ {
+ HttpRequestError = httpRequestError;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="Http.HttpRequestError"/> that caused the exception.
+ /// </summary>
+ /// <value>
+ /// The <see cref="Http.HttpRequestError"/> or <see langword="null"/> if the underlying <see cref="HttpMessageHandler"/> did not provide it.
+ /// </value>
+ public HttpRequestError? HttpRequestError { get; }
+
+ /// <summary>
/// Gets the HTTP status code to be returned with the exception.
/// </summary>
/// <value>
// This constructor is used internally to indicate that a request was not successfully sent due to an IOException,
// and the exception occurred early enough so that the request may be retried on another connection.
- internal HttpRequestException(string? message, Exception? inner, RequestRetryType allowRetry)
- : this(message, inner)
+ internal HttpRequestException(string? message, Exception? inner, RequestRetryType allowRetry, HttpRequestError? httpRequestError = null)
+ : this(message, inner, httpRequestError: httpRequestError)
{
AllowRetry = allowRetry;
}
public HttpResponseMessage? Response => _response;
/// <summary>
- /// Gets the exception that occured or <see langword="null"/> if there was no error.
+ /// Gets the exception that occurred or <see langword="null"/> if there was no error.
/// </summary>
/// <remarks>
/// This property must not be used from outside of the enrichment callbacks.
{
isNewConnection = false;
connection.Dispose();
- throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode), null, HttpStatusCode.Unauthorized);
+ throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode), null, HttpStatusCode.Unauthorized, HttpRequestError.UserAuthenticationError);
}
break;
}
int bytesRead = _connection.Read(buffer.Slice(0, (int)Math.Min((ulong)buffer.Length, _chunkBytesRemaining)));
if (bytesRead == 0)
{
- throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _chunkBytesRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _chunkBytesRemaining));
}
_chunkBytesRemaining -= (ulong)bytesRead;
if (_chunkBytesRemaining == 0)
int bytesRead = await _connection.ReadAsync(buffer.Slice(0, (int)Math.Min((ulong)buffer.Length, _chunkBytesRemaining))).ConfigureAwait(false);
if (bytesRead == 0)
{
- throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _chunkBytesRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _chunkBytesRemaining));
}
_chunkBytesRemaining -= (ulong)bytesRead;
if (_chunkBytesRemaining == 0)
// Parse the hex value from it.
if (!Utf8Parser.TryParse(currentLine, out ulong chunkSize, out int bytesConsumed, 'X'))
{
- throw new IOException(SR.Format(SR.net_http_invalid_response_chunk_header_invalid, BitConverter.ToString(currentLine.ToArray())));
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_chunk_header_invalid, BitConverter.ToString(currentLine.ToArray())));
}
_chunkBytesRemaining = chunkSize;
if (currentLine.Length != 0)
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_chunk_terminator_invalid, Encoding.ASCII.GetString(currentLine)));
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_chunk_terminator_invalid, Encoding.ASCII.GetString(currentLine)));
}
_state = ParsingState.ExpectChunkHeader;
}
else if (c != ' ' && c != '\t') // not called out in the RFC, but WinHTTP allows it
{
- throw new IOException(SR.Format(SR.net_http_invalid_response_chunk_extension_invalid, BitConverter.ToString(lineAfterChunkSize.ToArray())));
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.Format(SR.net_http_invalid_response_chunk_extension_invalid, BitConverter.ToString(lineAfterChunkSize.ToArray())));
}
}
}
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.Versioning;
+using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
throw CancellationHelper.CreateOperationCanceledException(e, cancellationToken);
}
- HttpRequestException ex = new HttpRequestException(SR.net_http_ssl_connection_failed, e);
+ HttpRequestException ex = new HttpRequestException(SR.net_http_ssl_connection_failed, e, httpRequestError: HttpRequestError.SecureConnectionError);
if (request.IsExtendedConnectRequest)
{
// Extended connect request is negotiating strictly for ALPN = "h2" because HttpClient is unaware of a possible downgrade.
}
}
- internal static Exception CreateWrappedException(Exception error, string host, int port, CancellationToken cancellationToken)
+ internal static Exception CreateWrappedException(Exception exception, string host, int port, CancellationToken cancellationToken)
{
- return CancellationHelper.ShouldWrapInOperationCanceledException(error, cancellationToken) ?
- CancellationHelper.CreateOperationCanceledException(error, cancellationToken) :
- new HttpRequestException($"{error.Message} ({host}:{port})", error, RequestRetryType.RetryOnNextProxy);
+ return CancellationHelper.ShouldWrapInOperationCanceledException(exception, cancellationToken) ?
+ CancellationHelper.CreateOperationCanceledException(exception, cancellationToken) :
+ new HttpRequestException($"{exception.Message} ({host}:{port})", exception, RequestRetryType.RetryOnNextProxy, DeduceError(exception));
+
+ static HttpRequestError DeduceError(Exception exception)
+ {
+ // TODO: Deduce quic errors from QuicException.TransportErrorCode once https://github.com/dotnet/runtime/issues/87262 is implemented.
+ if (exception is AuthenticationException)
+ {
+ return HttpRequestError.SecureConnectionError;
+ }
+
+ if (exception is SocketException socketException && socketException.SocketErrorCode == SocketError.HostNotFound)
+ {
+ return HttpRequestError.NameResolutionError;
+ }
+
+ return HttpRequestError.ConnectionError;
+ }
}
}
}
if (bytesRead <= 0 && buffer.Length != 0)
{
// Unexpected end of response stream.
- throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _contentBytesRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _contentBytesRemaining));
}
Debug.Assert((ulong)bytesRead <= _contentBytesRemaining);
CancellationHelper.ThrowIfCancellationRequested(cancellationToken);
// Unexpected end of response stream.
- throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _contentBytesRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _contentBytesRemaining));
}
Debug.Assert((ulong)bytesRead <= _contentBytesRemaining);
throw;
}
+ // TODO: Review this case!
throw new IOException(SR.net_http_http2_connection_not_established, e);
}
return frameHeader;
void ThrowPrematureEOF(int requiredBytes) =>
- throw new IOException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, requiredBytes - _incomingBuffer.ActiveLength));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, requiredBytes - _incomingBuffer.ActiveLength));
void ThrowMissingFrame() =>
- throw new IOException(SR.net_http_invalid_response_missing_frame);
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_missing_frame);
}
private async Task ProcessIncomingFramesAsync()
Debug.Assert(InitialSettingsReceived.Task.IsCompleted);
}
+ catch (HttpProtocolException e)
+ {
+ InitialSettingsReceived.TrySetException(e);
+ throw;
+ }
catch (Exception e)
{
- InitialSettingsReceived.TrySetException(new IOException(SR.net_http_http2_connection_not_established, e));
- throw new IOException(SR.net_http_http2_connection_not_established, e);
+ InitialSettingsReceived.TrySetException(new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_http2_connection_not_established, e));
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_http2_connection_not_established, e);
}
// Keep processing frames as they arrive.
return http2Stream.GetAndClearResponse();
}
- catch (Exception e)
+ catch (HttpIOException e)
{
- if (e is IOException ||
- e is ObjectDisposedException ||
- e is HttpProtocolException ||
- e is InvalidOperationException)
- {
- throw new HttpRequestException(SR.net_http_client_execution_error, e);
- }
-
- throw;
+ throw new HttpRequestException(e.Message, e, httpRequestError: e.HttpRequestError);
+ }
+ catch (Exception e) when (e is IOException || e is ObjectDisposedException || e is InvalidOperationException)
+ {
+ throw new HttpRequestException(SR.net_http_client_execution_error, e, httpRequestError: HttpRequestError.Unknown);
}
}
throw new HttpRequestException(message, innerException, allowRetry: RequestRetryType.RetryOnConnectionFailure);
private static Exception GetRequestAbortedException(Exception? innerException = null) =>
- innerException as HttpProtocolException ?? new IOException(SR.net_http_request_aborted, innerException);
+ innerException as HttpIOException ?? new IOException(SR.net_http_request_aborted, innerException);
[DoesNotReturn]
private static void ThrowRequestAborted(Exception? innerException = null) =>
if (index <= LastHPackRequestPseudoHeaderId)
{
if (NetEventSource.Log.IsEnabled()) Trace($"Invalid request pseudo-header ID {index}.");
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpRequestException(SR.net_http_invalid_response, httpRequestError: HttpRequestError.InvalidResponse);
}
else if (index <= LastHPackStatusPseudoHeaderId)
{
if (index <= LastHPackRequestPseudoHeaderId)
{
if (NetEventSource.Log.IsEnabled()) Trace($"Invalid request pseudo-header ID {index}.");
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpRequestException(SR.net_http_invalid_response, httpRequestError: HttpRequestError.InvalidResponse);
}
else if (index <= LastHPackStatusPseudoHeaderId)
{
_headerBudgetRemaining -= amount;
if (_headerBudgetRemaining < 0)
{
- throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection._pool.Settings.MaxResponseHeadersByteLength));
+ throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection._pool.Settings.MaxResponseHeadersByteLength), httpRequestError: HttpRequestError.ConfigurationLimitExceeded);
}
}
if (_responseProtocolState == ResponseProtocolState.ExpectingHeaders)
{
if (NetEventSource.Log.IsEnabled()) Trace("Received extra status header.");
- throw new HttpRequestException(SR.net_http_invalid_response_multiple_status_codes);
+ throw new HttpRequestException(SR.net_http_invalid_response_multiple_status_codes, httpRequestError: HttpRequestError.ConfigurationLimitExceeded);
}
if (_responseProtocolState != ResponseProtocolState.ExpectingStatus)
{
// Pseudo-headers are allowed only in header block
if (NetEventSource.Log.IsEnabled()) Trace($"Status pseudo-header received in {_responseProtocolState} state.");
- throw new HttpRequestException(SR.net_http_invalid_response_pseudo_header_in_trailer);
+ throw new HttpRequestException(SR.net_http_invalid_response_pseudo_header_in_trailer, httpRequestError: HttpRequestError.InvalidResponse);
}
Debug.Assert(_response != null);
if (_responseProtocolState != ResponseProtocolState.ExpectingHeaders && _responseProtocolState != ResponseProtocolState.ExpectingTrailingHeaders)
{
if (NetEventSource.Log.IsEnabled()) Trace("Received header before status.");
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpRequestException(SR.net_http_invalid_response, httpRequestError: HttpRequestError.InvalidResponse);
}
Encoding? valueEncoding = _connection._pool.Settings._responseHeaderEncodingSelector?.Invoke(descriptor.Name, _request);
else
{
if (NetEventSource.Log.IsEnabled()) Trace($"Invalid response pseudo-header '{Encoding.ASCII.GetString(name)}'.");
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpRequestException(SR.net_http_invalid_response, httpRequestError: HttpRequestError.InvalidResponse);
}
}
else
if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
{
// Invalid header name
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)), httpRequestError: HttpRequestError.InvalidResponse);
}
OnHeader(descriptor, value);
case Http3ErrorCode.RequestRejected:
// The server is rejecting the request without processing it, retry it on a different connection.
- throw new HttpRequestException(SR.net_http_request_aborted, ex, RequestRetryType.RetryOnConnectionFailure);
+ HttpProtocolException rejectedException = HttpProtocolException.CreateHttp3StreamException(code, ex);
+ throw new HttpRequestException(SR.net_http_request_aborted, rejectedException, RequestRetryType.RetryOnConnectionFailure, httpRequestError: HttpRequestError.HttpProtocolError);
default:
// Our stream was reset.
- throw new HttpRequestException(SR.net_http_client_execution_error, _connection.AbortException ?? HttpProtocolException.CreateHttp3StreamException(code));
+ Exception innerException = _connection.AbortException ?? HttpProtocolException.CreateHttp3StreamException(code, ex);
+ HttpRequestError httpRequestError = innerException is HttpProtocolException ? HttpRequestError.HttpProtocolError : HttpRequestError.Unknown;
+ throw new HttpRequestException(SR.net_http_client_execution_error, innerException, httpRequestError: httpRequestError);
}
}
catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted)
Http3ErrorCode code = (Http3ErrorCode)ex.ApplicationErrorCode.Value;
Exception abortException = _connection.Abort(HttpProtocolException.CreateHttp3ConnectionException(code, SR.net_http_http3_connection_close));
- throw new HttpRequestException(SR.net_http_client_execution_error, abortException);
+ throw new HttpRequestException(SR.net_http_client_execution_error, abortException, httpRequestError: HttpRequestError.HttpProtocolError);
}
catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted && _connection.AbortException != null)
{
// we close the connection, propagate the AbortException
- throw new HttpRequestException(SR.net_http_client_execution_error, _connection.AbortException);
+ throw new HttpRequestException(SR.net_http_client_execution_error, _connection.AbortException, httpRequestError: HttpRequestError.Unknown);
}
// It is possible for user's Content code to throw an unexpected OperationCanceledException.
catch (OperationCanceledException ex) when (ex.CancellationToken == _requestBodyCancellationSource.Token || ex.CancellationToken == cancellationToken)
else
{
Debug.Assert(_requestBodyCancellationSource.IsCancellationRequested);
- throw new HttpRequestException(SR.net_http_request_aborted, ex, RequestRetryType.RetryOnConnectionFailure);
+ throw new HttpRequestException(SR.net_http_request_aborted, ex, RequestRetryType.RetryOnConnectionFailure, httpRequestError: HttpRequestError.Unknown);
}
}
- catch (HttpProtocolException ex)
+ catch (HttpIOException ex)
{
- // A connection-level protocol error has occurred on our stream.
_connection.Abort(ex);
- throw new HttpRequestException(SR.net_http_client_execution_error, ex);
+ throw new HttpRequestException(SR.net_http_client_execution_error, ex, httpRequestError: ex.HttpRequestError);
}
catch (Exception ex)
{
{
throw;
}
- throw new HttpRequestException(SR.net_http_client_execution_error, ex);
+ throw new HttpRequestException(SR.net_http_client_execution_error, ex, httpRequestError: HttpRequestError.Unknown);
}
finally
{
{
Trace($"Expected HEADERS as first response frame; received {frameType}.");
}
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
}
await ReadHeadersAsync(payloadLength, cancellationToken).ConfigureAwait(false);
{
Trace("Response content exceeded Content-Length.");
}
- throw new HttpRequestException(SR.net_http_invalid_response);
+ throw new HttpIOException(HttpRequestError.InvalidResponse, SR.net_http_invalid_response);
}
break;
default:
else
{
// Our buffer has partial frame data in it but not enough to complete the read: bail out.
- throw new HttpRequestException(SR.net_http_invalid_response_premature_eof);
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof);
}
}
if (headersLength > _headerBudgetRemaining)
{
_stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.ExcessiveLoad);
- throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection.Pool.Settings.MaxResponseHeadersByteLength));
+ throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _connection.Pool.Settings.MaxResponseHeadersByteLength), httpRequestError: HttpRequestError.ConfigurationLimitExceeded);
}
_headerBudgetRemaining -= (int)headersLength;
else
{
if (NetEventSource.Log.IsEnabled()) Trace($"Server closed response stream before entire header payload could be read. {headersLength:N0} bytes remaining.");
- throw new HttpRequestException(SR.net_http_invalid_response_premature_eof);
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof);
}
}
if (!HeaderDescriptor.TryGet(name, out HeaderDescriptor descriptor))
{
// Invalid header name
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)), httpRequestError: HttpRequestError.InvalidResponse);
}
OnHeader(staticIndex: null, descriptor, staticValue: default, literalValue: value);
}
if (bytesRead == 0 && buffer.Length != 0)
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _responseDataPayloadRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _responseDataPayloadRemaining));
}
totalBytesRead += bytesRead;
if (bytesRead == 0 && buffer.Length != 0)
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _responseDataPayloadRemaining));
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.Format(SR.net_http_invalid_response_premature_eof_bytecount, _responseDataPayloadRemaining));
}
totalBytesRead += bytesRead;
case QuicException e when (e.QuicError == QuicError.StreamAborted):
// Peer aborted the stream
Debug.Assert(e.ApplicationErrorCode.HasValue);
- throw HttpProtocolException.CreateHttp3StreamException((Http3ErrorCode)e.ApplicationErrorCode.Value);
+ throw HttpProtocolException.CreateHttp3StreamException((Http3ErrorCode)e.ApplicationErrorCode.Value, e);
case QuicException e when (e.QuicError == QuicError.ConnectionAborted):
// Our connection was reset. Start aborting the connection.
_connection.Abort(exception);
throw exception;
- case HttpProtocolException:
- // A connection-level protocol error has occurred on our stream.
+ case HttpIOException:
_connection.Abort(ex);
ExceptionDispatchInfo.Throw(ex); // Rethrow.
return; // Never reached.
}
_stream.Abort(QuicAbortDirection.Read, (long)Http3ErrorCode.InternalError);
- throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex));
+ throw new HttpIOException(HttpRequestError.Unknown, SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex));
}
private async ValueTask<bool> ReadNextDataFrameAsync(HttpResponseMessage response, CancellationToken cancellationToken)
_canRetry = true;
}
- throw new IOException(SR.net_http_invalid_response_premature_eof);
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof);
}
const int MinStatusLineLength = 12; // "HTTP/1.x 123"
if (line.Length < MinStatusLineLength || line[8] != ' ')
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)), httpRequestError: HttpRequestError.InvalidResponse);
}
ulong first8Bytes = BitConverter.ToUInt64(line);
}
else
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)), httpRequestError: HttpRequestError.InvalidResponse);
}
}
byte status1 = line[9], status2 = line[10], status3 = line[11];
if (!IsDigit(status1) || !IsDigit(status2) || !IsDigit(status3))
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_code, Encoding.ASCII.GetString(line.Slice(9, 3))));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_code, Encoding.ASCII.GetString(line.Slice(9, 3))), httpRequestError: HttpRequestError.InvalidResponse);
}
response.SetStatusCodeWithoutValidation((HttpStatusCode)(100 * (status1 - '0') + 10 * (status2 - '0') + (status3 - '0')));
{
response.ReasonPhrase = HttpRuleParser.DefaultHttpEncoding.GetString(reasonBytes);
}
- catch (FormatException error)
+ catch (FormatException formatEx)
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_reason, Encoding.ASCII.GetString(reasonBytes.ToArray())), error);
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_reason, Encoding.ASCII.GetString(reasonBytes.ToArray())), formatEx, httpRequestError: HttpRequestError.InvalidResponse);
}
}
}
else
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_line, Encoding.ASCII.GetString(line)), httpRequestError: HttpRequestError.InvalidResponse);
}
}
}
static void ThrowForInvalidHeaderLine(ReadOnlySpan<byte> buffer, int newLineIndex) =>
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_line, Encoding.ASCII.GetString(buffer.Slice(0, newLineIndex))));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_line, Encoding.ASCII.GetString(buffer.Slice(0, newLineIndex))), httpRequestError: HttpRequestError.InvalidResponse);
}
private void AddResponseHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value, HttpResponseMessage response, bool isFromTrailer)
Debug.Assert(added);
static void ThrowForEmptyHeaderName() =>
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, ""));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, ""), httpRequestError: HttpRequestError.InvalidResponse);
static void ThrowForInvalidHeaderName(ReadOnlySpan<byte> name) =>
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_header_name, Encoding.ASCII.GetString(name)), httpRequestError: HttpRequestError.InvalidResponse);
}
private void ThrowExceededAllowedReadLineBytes() =>
- throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _pool.Settings.MaxResponseHeadersByteLength));
+ throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _pool.Settings.MaxResponseHeadersByteLength), httpRequestError: HttpRequestError.ConfigurationLimitExceeded);
private void ProcessKeepAliveHeader(string keepAlive)
{
if (NetEventSource.Log.IsEnabled()) Trace($"Received {bytesRead} bytes.");
if (bytesRead == 0)
{
- throw new IOException(SR.net_http_invalid_response_premature_eof);
+ throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof);
}
}
if (_connectionClose)
{
- throw new HttpRequestException(SR.net_http_authconnectionfailure);
+ throw new HttpRequestException(SR.net_http_authconnectionfailure, httpRequestError: HttpRequestError.UserAuthenticationError);
}
Debug.Assert(response.Content != null);
if (!await responseStream.DrainAsync(_pool.Settings._maxResponseDrainSize).ConfigureAwait(false) ||
_connectionClose) // Draining may have set this
{
- throw new HttpRequestException(SR.net_http_authconnectionfailure);
+ throw new HttpRequestException(SR.net_http_authconnectionfailure, httpRequestError: HttpRequestError.UserAuthenticationError);
}
}
!IsDigit(status2 = value[1]) ||
!IsDigit(status3 = value[2]))
{
- throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_code, System.Text.Encoding.ASCII.GetString(value)));
+ throw new HttpRequestException(SR.Format(SR.net_http_invalid_response_status_code, System.Text.Encoding.ASCII.GetString(value)), httpRequestError: HttpRequestError.InvalidResponse);
}
return 100 * (status1 - '0') + 10 * (status2 - '0') + (status3 - '0');
{
Debug.Assert(desiredVersion == 2 || desiredVersion == 3);
- HttpRequestException ex = new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion), inner);
+ HttpRequestException ex = new(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion), inner, httpRequestError: HttpRequestError.VersionNegotiationError);
if (request.IsExtendedConnectRequest && desiredVersion == 2)
{
ex.Data["HTTP2_ENABLED"] = false;
await connection.InitialSettingsReceived.WaitWithCancellationAsync(cancellationToken).ConfigureAwait(false);
if (!connection.IsConnectEnabled)
{
- HttpRequestException exception = new(SR.net_unsupported_extended_connect);
+ HttpRequestException exception = new(SR.net_unsupported_extended_connect, httpRequestError: HttpRequestError.ExtendedConnectNotSupported);
exception.Data["SETTINGS_ENABLE_CONNECT_PROTOCOL"] = false;
throw exception;
}
// Throw if fallback is not allowed by the version policy.
if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower)
{
- throw new HttpRequestException(SR.Format(SR.net_http_requested_version_server_refused, request.Version, request.VersionPolicy), e);
+ throw new HttpRequestException(SR.Format(SR.net_http_requested_version_server_refused, request.Version, request.VersionPolicy), e, httpRequestError: HttpRequestError.VersionNegotiationError);
}
if (NetEventSource.Log.IsEnabled())
if (tunnelResponse.StatusCode != HttpStatusCode.OK)
{
tunnelResponse.Dispose();
- throw new HttpRequestException(SR.Format(SR.net_http_proxy_tunnel_returned_failure_status_code, _proxyUri, (int)tunnelResponse.StatusCode));
+ throw new HttpRequestException(SR.Format(SR.net_http_proxy_tunnel_returned_failure_status_code, _proxyUri, (int)tunnelResponse.StatusCode), httpRequestError: HttpRequestError.ProxyTunnelError);
}
try
catch (Exception e) when (!(e is OperationCanceledException))
{
Debug.Assert(!(e is HttpRequestException));
- throw new HttpRequestException(SR.net_http_request_aborted, e);
+ throw new HttpRequestException(SR.net_http_proxy_tunnel_error, e, httpRequestError: HttpRequestError.ProxyTunnelError);
}
return stream;
{
HttpRequestException outerEx = await Assert.ThrowsAsync<HttpRequestException>(() => task);
_output.WriteLine(outerEx.InnerException.Message);
+ Assert.Equal(HttpRequestError.HttpProtocolError, outerEx.HttpRequestError);
HttpProtocolException protocolEx = Assert.IsType<HttpProtocolException>(outerEx.InnerException);
+ Assert.Equal(HttpRequestError.HttpProtocolError, protocolEx.HttpRequestError);
Assert.Equal(errorCode, (ProtocolErrors)protocolEx.ErrorCode);
}
private async Task AssertHttpProtocolException(Task task, ProtocolErrors errorCode)
{
HttpProtocolException protocolEx = await Assert.ThrowsAsync<HttpProtocolException>(() => task);
+ Assert.Equal(HttpRequestError.HttpProtocolError, protocolEx.HttpRequestError);
Assert.Equal(errorCode, (ProtocolErrors)protocolEx.ErrorCode);
}
}
[ConditionalFact(nameof(SupportsAlpn))]
+ public async Task Http2_IncorrectServerPreface_RequestFailsWithAppropriateHttpProtocolException()
+ {
+ using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
+
+ Http2LoopbackConnection connection = await server.AcceptConnectionAsync();
+ await connection.ReadSettingsAsync();
+ await connection.SendGoAway(0, ProtocolErrors.INTERNAL_ERROR);
+
+ await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
+ }
+ }
+
+ [ConditionalFact(nameof(SupportsAlpn))]
public async Task Http2_StreamResetByServerAfterHeadersSent_RequestFails()
{
using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
}
else
{
- var ioe = Assert.IsType<IOException>(ex);
+ var ioe = Assert.IsType<HttpIOException>(ex);
var hre = Assert.IsType<HttpRequestException>(ioe.InnerException);
var qex = Assert.IsType<QuicException>(hre.InnerException);
Assert.Equal(QuicError.OperationAborted, qex.QuicError);
request.Headers.Protocol = "foo";
HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(request));
-
- Assert.Equal(false, ex.Data["SETTINGS_ENABLE_CONNECT_PROTOCOL"]);
+ Assert.Equal(HttpRequestError.ExtendedConnectNotSupported, ex.HttpRequestError);
clientCompleted.SetResult();
},
Exception ex = await Assert.ThrowsAnyAsync<Exception>(() => client.SendAsync(request));
clientCompleted.SetResult();
-
if (useSsl)
{
Assert.Equal(false, ex.Data["HTTP2_ENABLED"]);
protected override Version UseVersion => HttpVersion.Version30;
}
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
+ public abstract class SocketsHttpHandler_HttpRequestErrorTest : HttpClientHandlerTestBase
+ {
+ protected SocketsHttpHandler_HttpRequestErrorTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public async Task NameResolutionError()
+ {
+ using HttpClient client = CreateHttpClient();
+ using HttpRequestMessage message = new(HttpMethod.Get, new Uri("https://BadHost"))
+ {
+ Version = UseVersion,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(message));
+
+ // TODO: Some platforms fail to detect NameResolutionError reliably, we should investigate this.
+ // Also, System.Net.Quic does not report DNS resolution errors yet.
+ Assert.True(ex.HttpRequestError is HttpRequestError.NameResolutionError or HttpRequestError.ConnectionError);
+ }
+
+ [Fact]
+ public async Task ConnectionError()
+ {
+ if (UseVersion.Major == 3)
+ {
+ return;
+ }
+ using Socket notListening = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ notListening.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ int port = ((IPEndPoint)notListening.LocalEndPoint).Port;
+ Uri uri = new($"http://localhost:{port}");
+
+ using HttpClient client = CreateHttpClient();
+ using HttpRequestMessage message = new(HttpMethod.Get, uri)
+ {
+ Version = UseVersion,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(message));
+ Assert.Equal(HttpRequestError.ConnectionError, ex.HttpRequestError);
+ }
+
+ [Fact]
+ public async Task SecureConnectionError()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using HttpClientHandler handler = CreateHttpClientHandler();
+ using HttpClient client = CreateHttpClient(handler);
+ GetUnderlyingSocketsHttpHandler(handler).SslOptions = new SslClientAuthenticationOptions()
+ {
+ RemoteCertificateValidationCallback = delegate { return false; },
+ };
+ using HttpRequestMessage message = new(HttpMethod.Get, uri)
+ {
+ Version = UseVersion,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(message));
+ Assert.Equal(HttpRequestError.SecureConnectionError, ex.HttpRequestError);
+ }, async server =>
+ {
+ try
+ {
+ await server.AcceptConnectionAsync(_ => Task.CompletedTask);
+ }
+ catch
+ {
+ }
+ },
+ options: new GenericLoopbackOptions() { UseSsl = true });
+ }
+
+
+ }
+
+ public sealed class SocketsHttpHandler_HttpRequestErrorTest_Http11 : SocketsHttpHandler_HttpRequestErrorTest
+ {
+ public SocketsHttpHandler_HttpRequestErrorTest_Http11(ITestOutputHelper output) : base(output) { }
+ protected override Version UseVersion => HttpVersion.Version11;
+ }
+
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
+ public sealed class SocketsHttpHandler_HttpRequestErrorTest_Http20 : SocketsHttpHandler_HttpRequestErrorTest
+ {
+ public SocketsHttpHandler_HttpRequestErrorTest_Http20(ITestOutputHelper output) : base(output) { }
+ protected override Version UseVersion => HttpVersion.Version20;
+
+ [Fact]
+ public async Task VersionNegitioationError()
+ {
+ await Http11LoopbackServerFactory.Singleton.CreateClientAndServerAsync(async uri =>
+ {
+ using HttpClient client = CreateHttpClient();
+ using HttpRequestMessage message = new(HttpMethod.Get, uri)
+ {
+ Version = UseVersion,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(message));
+ Assert.Equal(HttpRequestError.VersionNegotiationError, ex.HttpRequestError);
+ }, async server =>
+ {
+ try
+ {
+ await server.AcceptConnectionAsync(_ => Task.CompletedTask);
+ }
+ catch
+ {
+ }
+ },
+ options: new GenericLoopbackOptions() { UseSsl = true });
+ }
+ }
+
+ [Collection(nameof(DisableParallelization))]
+ [ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))]
+ public sealed class SocketsHttpHandler_HttpRequestErrorTest_Http30 : SocketsHttpHandler_HttpRequestErrorTest
+ {
+ public SocketsHttpHandler_HttpRequestErrorTest_Http30(ITestOutputHelper output) : base(output) { }
+ protected override Version UseVersion => HttpVersion.Version30;
+ }
+
public class MySsl : SslStream
{
public MySsl(Stream stream) : base(stream)
Link="ProductionCode\System\Net\Http\HttpCompletionOption.cs" />
<Compile Include="..\..\src\System\Net\Http\HttpContent.cs"
Link="ProductionCode\System\Net\Http\HttpContent.cs" />
+ <Compile Include="..\..\src\System\Net\Http\HttpIOException.cs"
+ Link="ProductionCode\System\Net\Http\HttpIOException.cs" />
<Compile Include="..\..\src\System\Net\Http\HttpMessageHandler.cs"
Link="ProductionCode\System\Net\Http\HttpMessageHandler.cs" />
<Compile Include="..\..\src\System\Net\Http\HttpMessageInvoker.cs"
Link="ProductionCode\System\Net\Http\HttpMethod.cs" />
<Compile Include="..\..\src\System\Net\Http\HttpParseResult.cs"
Link="ProductionCode\System\Net\Http\HttpParseResult.cs" />
+ <Compile Include="..\..\src\System\Net\Http\HttpRequestError.cs"
+ Link="ProductionCode\System\Net\Http\HttpRequestError.cs" />
<Compile Include="..\..\src\System\Net\Http\HttpRequestException.cs"
Link="ProductionCode\System\Net\Http\HttpRequestException.cs" />
<Compile Include="..\..\src\System\Net\Http\RequestRetryType.cs"
break;
}
catch (HttpRequestException ex) when
- ((ex.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL") || ex.Data.Contains("HTTP2_ENABLED"))
+ ((ex.HttpRequestError == HttpRequestError.ExtendedConnectNotSupported || ex.Data.Contains("HTTP2_ENABLED"))
&& tryDowngrade
&& (options.HttpVersion == HttpVersion.Version11 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrLower))
{
Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token);
var ex = await Assert.ThrowsAnyAsync<WebSocketException>(() => t);
- Assert.IsType<HttpRequestException>(ex.InnerException);
+ HttpRequestException inner = Assert.IsType<HttpRequestException>(ex.InnerException);
+ Assert.Equal(HttpRequestError.ExtendedConnectNotSupported, inner.HttpRequestError);
Assert.True(ex.InnerException.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL"));
}
},
Task t = cws.ConnectAsync(uri, GetInvoker(), cts.Token);
var ex = await Assert.ThrowsAnyAsync<WebSocketException>(() => t);
- Assert.IsType<HttpRequestException>(ex.InnerException);
+ HttpRequestException inner = Assert.IsType<HttpRequestException>(ex.InnerException);
+ Assert.Equal(HttpRequestError.ExtendedConnectNotSupported, inner.HttpRequestError);
Assert.True(ex.InnerException.Data.Contains("SETTINGS_ENABLE_CONNECT_PROTOCOL"));
}
},
Task t = cws.ConnectAsync(Test.Common.Configuration.WebSockets.SecureRemoteEchoServer, GetInvoker(), cts.Token);
var ex = await Assert.ThrowsAnyAsync<WebSocketException>(() => t);
- Assert.IsType<HttpRequestException>(ex.InnerException);
Assert.True(ex.InnerException.Data.Contains("HTTP2_ENABLED"));
+ HttpRequestException inner = Assert.IsType<HttpRequestException>(ex.InnerException);
+ HttpRequestError expectedError = PlatformDetection.SupportsAlpn ?
+ HttpRequestError.SecureConnectionError :
+ HttpRequestError.VersionNegotiationError;
+ Assert.Equal(expectedError, inner.HttpRequestError);
Assert.Equal(WebSocketState.Closed, cws.State);
}
}