{
public IPAddress Address { get; set; } = IPAddress.Loopback;
public int ListenBacklog { get; set; } = 1;
- public bool UseSsl { get; set; } = true;
+ public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn;
public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls12;
}
value.Length != 0 ? Encoding.ASCII.GetBytes(value) : Array.Empty<byte>());
// Values for encoding.
- // Unused values are omitted, so entries like ":scheme: http" are not included.
+ // Unused values are omitted.
public const int Authority = 1;
public const int MethodGet = 2;
public const int MethodPost = 3;
public const int PathSlash = 4;
+ public const int SchemeHttp = 6;
public const int SchemeHttps = 7;
public const int AcceptCharset = 15;
public const int AcceptEncoding = 16;
internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable
{
private readonly HttpConnectionPool _pool;
- private readonly SslStream _stream;
+ private readonly Stream _stream;
// NOTE: These are mutable structs; do not make these readonly.
private ArrayBuffer _incomingBuffer;
// this value, so this is not a hard maximum size.
private const int UnflushedOutgoingBufferSize = 32 * 1024;
- public Http2Connection(HttpConnectionPool pool, SslStream stream)
+ public Http2Connection(HttpConnectionPool pool, Stream stream)
{
_pool = pool;
_stream = stream;
WriteIndexedHeader(StaticTable.MethodGet, normalizedMethod.Method);
}
- WriteIndexedHeader(StaticTable.SchemeHttps);
+ WriteIndexedHeader(_stream is SslStream ? StaticTable.SchemeHttps : StaticTable.SchemeHttp);
if (request.HasHeaders && request.Headers.Host != null)
{
private readonly int _port;
private readonly Uri _proxyUri;
internal readonly byte[] _encodedAuthorityHostHeader;
-
+
/// <summary>List of idle connections stored in the pool.</summary>
private readonly List<CachedConnection> _idleConnections = new List<CachedConnection>();
/// <summary>The maximum number of connections allowed to be associated with the pool.</summary>
Debug.Assert(port != 0);
Debug.Assert(sslHostName == null);
Debug.Assert(proxyUri == null);
-
- _http2Enabled = false;
+ _http2Enabled = _poolManager.Settings._allowUnencryptedHttp2;
break;
case HttpConnectionKind.Https:
// Note the IDN hostname should always be ASCII, since it's already been IDNA encoded.
_hostHeaderValueBytes = Encoding.ASCII.GetBytes(hostHeader);
Debug.Assert(Encoding.ASCII.GetString(_hostHeaderValueBytes) == hostHeader);
+ if (sslHostName == null)
+ {
+ _encodedAuthorityHostHeader = HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(StaticTable.Authority, hostHeader);
+ }
}
if (sslHostName != null)
private async ValueTask<(HttpConnectionBase connection, bool isNewConnection, HttpResponseMessage failureResponse)>
GetHttp2ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
- Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel);
+ Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind == HttpConnectionKind.Http);
// See if we have an HTTP2 connection
Http2Connection http2Connection = _http2Connection;
return (null, true, failureResponse);
}
+ if (_kind == HttpConnectionKind.Http)
+ {
+ http2Connection = new Http2Connection(this, stream);
+ await http2Connection.SetupAsync().ConfigureAwait(false);
+
+ Debug.Assert(_http2Connection == null);
+ _http2Connection = http2Connection;
+
+ if (NetEventSource.IsEnabled)
+ {
+ Trace("New unencrypted HTTP2 connection established.");
+ }
+
+ return (_http2Connection, true, null);
+ }
+
sslStream = (SslStream)stream;
if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
{
{
private const string Http2SupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT";
private const string Http2SupportAppCtxSettingName = "System.Net.Http.SocketsHttpHandler.Http2Support";
-
+ private const string Http2UnencryptedSupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT";
+ private const string Http2UnencryptedSupportAppCtxSettingName = "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport";
+
internal DecompressionMethods _automaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression;
internal bool _useCookies = HttpHandlerDefaults.DefaultUseCookies;
internal Version _maxHttpVersion;
+ internal bool _allowUnencryptedHttp2;
+
internal SslClientAuthenticationOptions _sslOptions;
internal IDictionary<string, object> _properties;
public HttpConnectionSettings()
{
- _maxHttpVersion = AllowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11;
+ bool allowHttp2 = AllowHttp2;
+ _maxHttpVersion = allowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11;
+ _allowUnencryptedHttp2 = allowHttp2 && AllowUnencryptedHttp2;
}
/// <summary>Creates a copy of the settings but with some values normalized to suit the implementation.</summary>
_sslOptions = _sslOptions?.ShallowClone(), // shallow clone the options for basic prevention of mutation issues while processing
_useCookies = _useCookies,
_useProxy = _useProxy,
+ _allowUnencryptedHttp2 = _allowUnencryptedHttp2,
};
}
return false;
}
}
+
+ private static bool AllowUnencryptedHttp2
+ {
+ get
+ {
+ // First check for the AppContext switch, giving it priority over the environment variable.
+ if (AppContext.TryGetSwitch(Http2UnencryptedSupportAppCtxSettingName, out bool allowHttp2))
+ {
+ return allowHttp2;
+ }
+
+ // AppContext switch wasn't used. Check the environment variable.
+ string envVar = Environment.GetEnvironmentVariable(Http2UnencryptedSupportEnvironmentVariableSettingName);
+ if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1")))
+ {
+ // Allow HTTP/2.0 protocol for HTTP endpoints.
+ return true;
+ }
+
+ // Default to a maximum of HTTP/1.1.
+ return false;
+ }
+ }
}
}
public HttpClientHandlerTest_Http2(ITestOutputHelper output) : base(output) { }
- [ConditionalFact(nameof(SupportsAlpn))]
+ [Fact]
public async Task Http2_ClientPreface_Sent()
{
using (var server = Http2LoopbackServer.CreateServer())
}
}
- [ConditionalFact(nameof(SupportsAlpn))]
+ [Fact]
public async Task Http2_InitialSettings_SentAndAcked()
{
using (var server = Http2LoopbackServer.CreateServer())
}
}
- [ConditionalFact(nameof(SupportsAlpn))]
+ [Fact]
public async Task Http2_DataSentBeforeServerPreface_ProtocolError()
{
using (var server = Http2LoopbackServer.CreateServer())
}
}
- [ConditionalFact(nameof(SupportsAlpn))]
+ [Fact]
public async Task Http2_NoResponseBody_Success()
{
using (var server = Http2LoopbackServer.CreateServer())
Debug.Assert(useSocketsHttpHandler == IsSocketsHttpHandler(handler), "Unexpected handler.");
}
- TestHelper.EnsureHttp2Feature(handler);
-
if (useHttp2LoopbackServer)
{
+ TestHelper.EnsureHttp2Feature(handler);
handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
}
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
using System.Net.Test.Common;
using System.Threading.Tasks;
using Xunit;
public class PlatformHandler_HttpClientHandler : HttpClientHandlerTestBase
{
+ protected override bool UseSocketsHttpHandler => false;
+
public PlatformHandler_HttpClientHandler(ITestOutputHelper output) : base(output) { }
[Theory]
protected override bool UseSocketsHttpHandler => true;
protected override bool UseHttp2LoopbackServer => true;
}
-
+
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http2 : HttpClientHandler_Cancellation_Test
{
"_maxHttpVersion",
BindingFlags.NonPublic | BindingFlags.Instance);
field_maxHttpVersion.SetValue(_settings, new Version(2, 0));
+
+ // Allow HTTP/2.0 via unencrypted socket if ALPN is not supported on platform.
+ FieldInfo field_allowPlainHttp2 = type_HttpConnectionSettings.GetField(
+ "_allowUnencryptedHttp2",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ field_allowPlainHttp2.SetValue(_settings, !PlatformDetection.SupportsAlpn);
}
public static bool NativeHandlerSupportsSslConfiguration()