private readonly HttpConnectionPool _pool;
private readonly HttpAuthority? _origin;
private readonly HttpAuthority _authority;
- private readonly byte[] _altUsedEncodedHeader;
+ private readonly byte[]? _altUsedEncodedHeader;
private QuicConnection? _connection;
private Task? _connectionClosedTask;
public HttpAuthority Authority => _authority;
public HttpConnectionPool Pool => _pool;
public int MaximumRequestHeadersLength => _maximumHeadersLength;
- public byte[] AltUsedEncodedHeaderBytes => _altUsedEncodedHeader;
+ public byte[]? AltUsedEncodedHeaderBytes => _altUsedEncodedHeader;
public Exception? AbortException => Volatile.Read(ref _abortException);
private object SyncObj => _activeRequests;
}
}
- public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAuthority authority, QuicConnection connection)
+ public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAuthority authority, QuicConnection connection, bool includeAltUsedHeader)
{
_pool = pool;
_origin = origin;
_authority = authority;
_connection = connection;
- bool altUsedDefaultPort = pool.Kind == HttpConnectionKind.Http && authority.Port == HttpConnectionPool.DefaultHttpPort || pool.Kind == HttpConnectionKind.Https && authority.Port == HttpConnectionPool.DefaultHttpsPort;
- string altUsedValue = altUsedDefaultPort ? authority.IdnHost : string.Create(CultureInfo.InvariantCulture, $"{authority.IdnHost}:{authority.Port}");
- _altUsedEncodedHeader = QPack.QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReferenceToArray(KnownHeaders.AltUsed.Name, altUsedValue);
+ if (includeAltUsedHeader)
+ {
+ bool altUsedDefaultPort = pool.Kind == HttpConnectionKind.Http && authority.Port == HttpConnectionPool.DefaultHttpPort || pool.Kind == HttpConnectionKind.Https && authority.Port == HttpConnectionPool.DefaultHttpsPort;
+ string altUsedValue = altUsedDefaultPort ? authority.IdnHost : string.Create(CultureInfo.InvariantCulture, $"{authority.IdnHost}:{authority.Port}");
+ _altUsedEncodedHeader = QPack.QPackEncoder.EncodeLiteralHeaderFieldWithoutNameReferenceToArray(KnownHeaders.AltUsed.Name, altUsedValue);
+ }
+
if (HttpTelemetry.Log.IsEnabled())
{
throw new HttpRequestException("QUIC connected but no HTTP/3 indicated via ALPN.", null, RequestRetryType.RetryOnSameOrNextProxy);
}
#endif
-
- http3Connection = new Http3Connection(this, _originAuthority, authority, quicConnection);
+ // if the authority was sent as an option through alt-svc then include alt-used header
+ http3Connection = new Http3Connection(this, _originAuthority, authority, quicConnection, includeAltUsedHeader: _http3Authority == authority);
_http3Connection = http3Connection;
if (NetEventSource.Log.IsEnabled())
}
[Fact]
+ public async Task AltSvcNotUsed_AltUsedHeaderNotPresent()
+ {
+ using Http3LoopbackServer server = CreateHttp3LoopbackServer();
+
+ Http3LoopbackConnection connection = null;
+ HttpRequestData requestData = null;
+ Task serverTask = Task.Run(async () =>
+ {
+ connection = (Http3LoopbackConnection)await server.EstablishGenericConnectionAsync();
+ requestData = await connection.ReadRequestDataAsync(readBody: false);
+ Assert.NotNull(connection);
+ Assert.Equal(0, requestData.GetHeaderValueCount("alt-used"));
+ await connection.SendResponseAsync();
+ });
+
+ using HttpClient client = CreateHttpClient();
+ using HttpRequestMessage request = new()
+ {
+ Method = HttpMethod.Get,
+ RequestUri = server.Address,
+ Version = HttpVersion30,
+ VersionPolicy = HttpVersionPolicy.RequestVersionExact
+ };
+
+ HttpResponseMessage response = await client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10));
+ response.EnsureSuccessStatusCode();
+ Assert.Equal(HttpVersion.Version30, response.Version);
+
+ await serverTask;
+ await connection.DisposeAsync();
+ }
+
+ [Fact]
public async Task Alpn_NonH3_NegotiationFailure()
{
var options = new Http3Options() { Alpn = "h3-29" }; // anything other than "h3"