Skip to content

Commit e9cc7e1

Browse files
JamesNKamcasey
andauthored
Add HTTP version configuration to GrpcChannelOptions (#2514)
Co-authored-by: Andrew Casey <[email protected]>
1 parent 26227c3 commit e9cc7e1

File tree

13 files changed

+175
-121
lines changed

13 files changed

+175
-121
lines changed

perf/benchmarkapps/GrpcClient/Program.cs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -497,13 +497,14 @@ private static ChannelBase CreateChannel(string target)
497497

498498
HttpMessageHandler httpMessageHandler = httpClientHandler;
499499

500+
Version? versionOverride = null;
500501
if (_options.Protocol == "h3")
501502
{
502503
// Stop gRPC channel from creating TCP socket.
503504
httpClientHandler.ConnectCallback = (context, cancellationToken) => throw new InvalidOperationException("Should never be called for H3.");
504505

505506
// Force H3 on all requests.
506-
httpMessageHandler = new Http3DelegatingHandler(httpMessageHandler);
507+
versionOverride = new Version(3, 0);
507508
}
508509

509510
return GrpcChannel.ForAddress(address, new GrpcChannelOptions
@@ -513,7 +514,8 @@ private static ChannelBase CreateChannel(string target)
513514
#else
514515
HttpClient = new HttpClient(httpMessageHandler),
515516
#endif
516-
LoggerFactory = _loggerFactory
517+
LoggerFactory = _loggerFactory,
518+
HttpVersion = versionOverride
517519
});
518520
}
519521
}
@@ -759,21 +761,4 @@ private static bool IsCallCountExceeded()
759761
{
760762
return _options.CallCount != null && _callsStarted > _options.CallCount;
761763
}
762-
763-
private class Http3DelegatingHandler : DelegatingHandler
764-
{
765-
private static readonly Version Http3Version = new Version(3, 0);
766-
767-
public Http3DelegatingHandler(HttpMessageHandler innerHandler)
768-
{
769-
InnerHandler = innerHandler;
770-
}
771-
772-
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
773-
{
774-
request.Version = Http3Version;
775-
request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
776-
return base.SendAsync(request, cancellationToken);
777-
}
778-
}
779764
}

src/Grpc.Net.Client.Web/GrpcWebHandler.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -44,6 +44,11 @@ public sealed class GrpcWebHandler : DelegatingHandler
4444
/// be overridden.
4545
/// </para>
4646
/// </summary>
47+
#if NET5_0_OR_GREATER
48+
[Obsolete("HttpVersion is obsolete and will be removed in a future release. Use GrpcChannelOptions.HttpVersion and GrpcChannelOptions.HttpVersionPolicy instead.")]
49+
#else
50+
[Obsolete("HttpVersion is obsolete and will be removed in a future release. Use GrpcChannelOptions.HttpVersion instead.")]
51+
#endif
4752
public Version? HttpVersion { get; set; }
4853

4954
/// <summary>
@@ -136,22 +141,27 @@ private async Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request
136141
// https://github.com/mono/mono/issues/18718
137142
request.SetOption(WebAssemblyEnableStreamingResponseKey, true);
138143

144+
#pragma warning disable CS0618 // Type or member is obsolete
139145
if (HttpVersion != null)
140146
{
141147
// This doesn't guarantee that the specified version is used. Some handlers will ignore it.
142148
// For example, version in the browser always negotiated by the browser and HttpClient
143149
// uses what the browser has negotiated.
144150
request.Version = HttpVersion;
151+
#if NET5_0_OR_GREATER
152+
request.VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
153+
#endif
145154
}
155+
#pragma warning restore CS0618 // Type or member is obsolete
146156
#if NET5_0_OR_GREATER
147157
else if (request.RequestUri?.Scheme == Uri.UriSchemeHttps
148-
&& request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher
158+
&& request.VersionPolicy == HttpVersionPolicy.RequestVersionExact
149159
&& request.Version == System.Net.HttpVersion.Version20)
150160
{
151-
// If no explicit HttpVersion is set and the request is using TLS then default to HTTP/1.1.
152-
// HTTP/1.1 together with HttpVersionPolicy.RequestVersionOrHigher it will be compatible
153-
// with all endpoints.
154-
request.Version = System.Net.HttpVersion.Version11;
161+
// If no explicit HttpVersion is set and the request is using TLS then change the version policy
162+
// to allow for HTTP/1.1. HttpVersionPolicy.RequestVersionOrLower it will be compatible
163+
// with HTTP/1.1 and HTTP/2.
164+
request.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
155165
}
156166
#endif
157167
#if NETSTANDARD2_0

src/Grpc.Net.Client/GrpcChannel.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public sealed class GrpcChannel : ChannelBase, IDisposable
7979
internal Dictionary<string, ICompressionProvider> CompressionProviders { get; }
8080
internal string MessageAcceptEncoding { get; }
8181
internal bool Disposed { get; private set; }
82+
internal Version HttpVersion { get; }
83+
#if NET5_0_OR_GREATER
84+
internal HttpVersionPolicy HttpVersionPolicy { get; }
85+
#endif
8286

8387
#if SUPPORT_LOAD_BALANCING
8488
// Load balancing
@@ -175,6 +179,10 @@ internal GrpcChannel(Uri address, GrpcChannelOptions channelOptions) : base(addr
175179
RetryThrottling = serviceConfig.RetryThrottling != null ? CreateChannelRetryThrottling(serviceConfig.RetryThrottling) : null;
176180
_serviceConfigMethods = CreateServiceConfigMethods(serviceConfig);
177181
}
182+
HttpVersion = channelOptions.HttpVersion ?? GrpcProtocolConstants.Http2Version;
183+
#if NET5_0_OR_GREATER
184+
HttpVersionPolicy = channelOptions.HttpVersionPolicy ?? HttpVersionPolicy.RequestVersionExact;
185+
#endif
178186

179187
// Non-HTTP addresses (e.g. dns:///custom-hostname) usually specify a path instead of an authority.
180188
// Only log about a path being present if HTTP or HTTPS.

src/Grpc.Net.Client/GrpcChannelOptions.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -287,6 +287,35 @@ public TimeSpan InitialReconnectBackoff
287287
/// </summary>
288288
public IServiceProvider? ServiceProvider { get; set; }
289289

290+
/// <summary>
291+
/// Gets or sets the HTTP version to use when making gRPC calls.
292+
/// <para>
293+
/// When a <see cref="Version"/> is specified the value will be set on <see cref="HttpRequestMessage.Version"/>
294+
/// as gRPC calls are made. Changing this property allows the HTTP version of gRPC calls to
295+
/// be overridden.
296+
/// </para>
297+
/// <para>
298+
/// A <c>null</c> value doesn't override the HTTP version of gRPC calls. Defaults to <c>2.0</c>.
299+
/// </para>
300+
/// </summary>
301+
public Version? HttpVersion { get; set; }
302+
303+
#if NET5_0_OR_GREATER
304+
/// <summary>
305+
/// Gets or sets the HTTP policy to use when making gRPC calls.
306+
/// <para>
307+
/// When a <see cref="HttpVersionPolicy"/> is specified the value will be set on <see cref="HttpRequestMessage.VersionPolicy"/>
308+
/// as gRPC calls are made. The policy determines how <see cref="Version"/> is interpreted when
309+
/// the final HTTP version is negotiated with the server. Changing this property allows the HTTP
310+
/// version of gRPC calls to be overridden.
311+
/// </para>
312+
/// <para>
313+
/// A <c>null</c> value doesn't override the HTTP policy of gRPC calls. Defaults to <see cref="HttpVersionPolicy.RequestVersionExact"/>.
314+
/// </para>
315+
/// </summary>
316+
public HttpVersionPolicy? HttpVersionPolicy { get; set; }
317+
#endif
318+
290319
internal T ResolveService<T>(T defaultValue)
291320
{
292321
return (T?)ServiceProvider?.GetService(typeof(T)) ?? defaultValue;

src/Grpc.Net.Client/Internal/GrpcCall.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -957,9 +957,9 @@ private void SetWriter(HttpRequestMessage message, HttpContentClientStreamWriter
957957
private HttpRequestMessage CreateHttpRequestMessage(TimeSpan? timeout)
958958
{
959959
var message = new HttpRequestMessage(HttpMethod.Post, _grpcMethodInfo.CallUri);
960-
message.Version = GrpcProtocolConstants.Http2Version;
960+
message.Version = Channel.HttpVersion;
961961
#if NET5_0_OR_GREATER
962-
message.VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
962+
message.VersionPolicy = Channel.HttpVersionPolicy;
963963
#endif
964964

965965
// Set raw headers on request using name/values. Typed headers allocate additional objects.

test/FunctionalTests/Balancer/ConnectionTests.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ async Task<HelloReply> UnaryMethod(HelloRequest request, ServerCallContext conte
322322
var client = TestClientFactory.Create(channel, endpoint1.Method);
323323

324324
// Act
325-
grpcWebHandler.HttpVersion = new Version(1, 1);
325+
SetHandlerHttpVersion(grpcWebHandler, new Version(1, 1));
326326
var http11CallTasks = new List<Task<HelloReply>>();
327327
for (int i = 0; i < 10; i++)
328328
{
@@ -352,7 +352,7 @@ await TestHelpers.AssertIsTrueRetryAsync(() =>
352352
}
353353

354354
// Act
355-
grpcWebHandler.HttpVersion = new Version(2, 0);
355+
SetHandlerHttpVersion(grpcWebHandler, new Version(2, 0));
356356
var http2CallTasks = new List<Task<HelloReply>>();
357357
for (int i = 0; i < 10; i++)
358358
{
@@ -389,7 +389,7 @@ await TestHelpers.AssertIsTrueRetryAsync(() =>
389389
return activeStreams.Count == 10;
390390
}, "Wait for HTTP/2 connection to end.");
391391

392-
grpcWebHandler.HttpVersion = new Version(1, 1);
392+
SetHandlerHttpVersion(grpcWebHandler, new Version(1, 1));
393393

394394
await Task.Delay(1000);
395395

@@ -412,6 +412,13 @@ await TestHelpers.AssertIsTrueRetryAsync(() =>
412412
Assert.AreEqual(new DnsEndPoint("127.0.0.1", endpoint2.Address.Port), activeStreams[0].EndPoint);
413413
}
414414

415+
private static void SetHandlerHttpVersion(GrpcWebHandler handler, Version version)
416+
{
417+
#pragma warning disable CS0618 // Type or member is obsolete
418+
handler.HttpVersion = version;
419+
#pragma warning restore CS0618 // Type or member is obsolete
420+
}
421+
415422
#if NET7_0_OR_GREATER
416423
[Test]
417424
public async Task Active_UnaryCall_HostOverride_Success()

test/FunctionalTests/Balancer/PickFirstBalancerTests.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,6 @@ namespace Grpc.AspNetCore.FunctionalTests.Balancer;
4747
[TestFixture]
4848
public class PickFirstBalancerTests : FunctionalTestBase
4949
{
50-
private GrpcChannel CreateGrpcWebChannel(TestServerEndpointName endpointName, Version? version)
51-
{
52-
var grpcWebHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb);
53-
grpcWebHandler.HttpVersion = version;
54-
55-
var httpClient = Fixture.CreateClient(endpointName, grpcWebHandler);
56-
var channel = GrpcChannel.ForAddress(httpClient.BaseAddress!, new GrpcChannelOptions
57-
{
58-
HttpClient = httpClient,
59-
LoggerFactory = LoggerFactory
60-
});
61-
62-
return channel;
63-
}
64-
6550
[Test]
6651
public async Task UnaryCall_CallAfterConnectionTimeout_Success()
6752
{

test/FunctionalTests/Client/ClientFactoryTests.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ Task<HelloReply> UnaryCall(HelloRequest request, ServerCallContext context)
104104
{
105105
return TestClientFactory.Create(invoker, method);
106106
})
107-
.AddHttpMessageHandler(() => new Http3Handler())
107+
.ConfigureChannel(options =>
108+
{
109+
options.HttpVersion = HttpVersion.Version30;
110+
options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionExact;
111+
})
108112
.ConfigurePrimaryHttpMessageHandler(() =>
109113
{
110114
return new SocketsHttpHandler
@@ -125,20 +129,5 @@ Task<HelloReply> UnaryCall(HelloRequest request, ServerCallContext context)
125129
// Assert
126130
Assert.AreEqual("Hello world", response1.Message);
127131
}
128-
129-
private class Http3Handler : DelegatingHandler
130-
{
131-
public Http3Handler() { }
132-
public Http3Handler(HttpMessageHandler innerHandler) : base(innerHandler) { }
133-
134-
protected override Task<HttpResponseMessage> SendAsync(
135-
HttpRequestMessage request, CancellationToken cancellationToken)
136-
{
137-
request.Version = HttpVersion.Version30;
138-
request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
139-
140-
return base.SendAsync(request, cancellationToken);
141-
}
142-
}
143132
#endif
144133
}

test/FunctionalTests/Infrastructure/GrpcHttpHelper.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -16,7 +16,6 @@
1616

1717
#endregion
1818

19-
2019
namespace Grpc.AspNetCore.FunctionalTests.Infrastructure;
2120

2221
public static class GrpcHttpHelper
@@ -26,7 +25,7 @@ public static HttpRequestMessage Create(string url, HttpMethod? method = null)
2625
var request = new HttpRequestMessage(method ?? HttpMethod.Post, url);
2726
request.Version = new Version(2, 0);
2827
#if NET5_0_OR_GREATER
29-
request.VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
28+
request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
3029
#endif
3130

3231
return request;

0 commit comments

Comments
 (0)