Skip to content

Commit 072a616

Browse files
authored
Split non-server gRPC client factory into separate package (#282)
1 parent da28d99 commit 072a616

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+984
-533
lines changed

Grpc.AspNetCore.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.Client", "src\Grpc
150150
EndProject
151151
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.Client.Tests", "test\Grpc.Net.Client.Tests\Grpc.Net.Client.Tests.csproj", "{7C299E2C-A5FE-439D-A346-2A676302BD04}"
152152
EndProject
153+
EndProject
154+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.ClientFactory", "src\Grpc.Net.ClientFactory\Grpc.Net.ClientFactory.csproj", "{F68DBD76-196A-4F55-BBEB-97278655017F}"
155+
EndProject
156+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.ClientFactory.Tests", "test\Grpc.Net.ClientFactory.Tests\Grpc.Net.ClientFactory.Tests.csproj", "{0C98BD64-8A57-46A1-B47B-EAF260DEE25A}"
157+
EndProject
153158
Global
154159
GlobalSection(SolutionConfigurationPlatforms) = preSolution
155160
Debug|Any CPU = Debug|Any CPU
@@ -248,6 +253,14 @@ Global
248253
{7C299E2C-A5FE-439D-A346-2A676302BD04}.Debug|Any CPU.Build.0 = Debug|Any CPU
249254
{7C299E2C-A5FE-439D-A346-2A676302BD04}.Release|Any CPU.ActiveCfg = Release|Any CPU
250255
{7C299E2C-A5FE-439D-A346-2A676302BD04}.Release|Any CPU.Build.0 = Release|Any CPU
256+
{F68DBD76-196A-4F55-BBEB-97278655017F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
257+
{F68DBD76-196A-4F55-BBEB-97278655017F}.Debug|Any CPU.Build.0 = Debug|Any CPU
258+
{F68DBD76-196A-4F55-BBEB-97278655017F}.Release|Any CPU.ActiveCfg = Release|Any CPU
259+
{F68DBD76-196A-4F55-BBEB-97278655017F}.Release|Any CPU.Build.0 = Release|Any CPU
260+
{0C98BD64-8A57-46A1-B47B-EAF260DEE25A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
261+
{0C98BD64-8A57-46A1-B47B-EAF260DEE25A}.Debug|Any CPU.Build.0 = Debug|Any CPU
262+
{0C98BD64-8A57-46A1-B47B-EAF260DEE25A}.Release|Any CPU.ActiveCfg = Release|Any CPU
263+
{0C98BD64-8A57-46A1-B47B-EAF260DEE25A}.Release|Any CPU.Build.0 = Release|Any CPU
251264
EndGlobalSection
252265
GlobalSection(SolutionProperties) = preSolution
253266
HideSolutionNode = FALSE
@@ -290,6 +303,8 @@ Global
290303
{4306E048-9D81-44A8-8069-2C294289FC00} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
291304
{095F2B46-16DC-4A2E-A075-A0373D902294} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
292305
{7C299E2C-A5FE-439D-A346-2A676302BD04} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
306+
{F68DBD76-196A-4F55-BBEB-97278655017F} = {8C62055F-8CD7-4859-9001-634D544DF2AE}
307+
{0C98BD64-8A57-46A1-B47B-EAF260DEE25A} = {CECC4AE8-9C4E-4727-939B-517CC2E58D65}
293308
EndGlobalSection
294309
GlobalSection(ExtensibilityGlobals) = postSolution
295310
SolutionGuid = {CD5C2B19-49B4-480A-990C-36D98A719B07}

src/Grpc.AspNetCore.Server.ClientFactory/Grpc.AspNetCore.Server.ClientFactory.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1010

1111
<ProjectReference Include="..\Grpc.AspNetCore.Server\Grpc.AspNetCore.Server.csproj" />
12-
<ProjectReference Include="..\Grpc.Net.Client\Grpc.Net.Client.csproj" />
12+
<ProjectReference Include="..\Grpc.Net.ClientFactory\Grpc.Net.ClientFactory.csproj" />
1313
<PackageReference Include="Grpc.Core" Version="$(GrpcPackageVersion)" />
1414
</ItemGroup>
1515
</Project>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
using System;
20+
using System.Net.Http;
21+
using Grpc.AspNetCore.Server.Features;
22+
using Grpc.Core;
23+
using Grpc.Net.ClientFactory;
24+
using Microsoft.AspNetCore.Http;
25+
using Microsoft.Extensions.Options;
26+
27+
namespace Microsoft.Extensions.DependencyInjection
28+
{
29+
/// <summary>
30+
/// Extension methods for configuring an <see cref="IHttpClientBuilder"/>.
31+
/// </summary>
32+
public static class GrpcServerHttpClientBuilderExtensions
33+
{
34+
/// <summary>
35+
/// Configures the server to propagate values from a call's <see cref="ServerCallContext"/>
36+
/// onto the gRPC client.
37+
/// </summary>
38+
/// <param name="builder">The <see cref="IHttpClientBuilder"/>.</param>
39+
/// <returns>An <see cref="IHttpClientBuilder"/> that can be used to configure the client.</returns>
40+
public static IHttpClientBuilder EnableCallContextPropagation(this IHttpClientBuilder builder)
41+
{
42+
if (builder == null)
43+
{
44+
throw new ArgumentNullException(nameof(builder));
45+
}
46+
47+
builder.Services.AddHttpContextAccessor();
48+
builder.Services.AddTransient<IConfigureOptions<GrpcClientFactoryOptions>>(services =>
49+
{
50+
return new ConfigureNamedOptions<GrpcClientFactoryOptions>(builder.Name, (options) =>
51+
{
52+
options.CallInvokerActions.Add(client =>
53+
{
54+
var httpContextAccessor = services.GetRequiredService<IHttpContextAccessor>();
55+
56+
var httpContext = httpContextAccessor.HttpContext;
57+
if (httpContext == null)
58+
{
59+
throw new InvalidOperationException("Unable to propagate server context values to the client. Can't find the current HttpContext.");
60+
}
61+
62+
var serverCallContext = httpContext.Features.Get<IServerCallContextFeature>()?.ServerCallContext;
63+
if (serverCallContext == null)
64+
{
65+
throw new InvalidOperationException("Unable to propagate server context values to the client. Can't find the current gRPC ServerCallContext.");
66+
}
67+
68+
client.CancellationToken = serverCallContext.CancellationToken;
69+
client.Deadline = serverCallContext.Deadline;
70+
});
71+
});
72+
});
73+
74+
return builder;
75+
}
76+
}
77+
}

src/Grpc.Net.Client/HttpClientCallInvoker.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#endregion
1818

1919
using System;
20+
using System.Net.Http;
2021
using System.Threading;
2122
using Grpc.Core;
2223
using Grpc.Net.Client.Internal;
@@ -30,7 +31,7 @@ namespace Grpc.Net.Client
3031
/// </summary>
3132
public sealed class HttpClientCallInvoker : CallInvoker
3233
{
33-
private readonly System.Net.Http.HttpClient _client;
34+
private readonly HttpClient _client;
3435
private readonly ILoggerFactory _loggerFactory;
3536

3637
// Override the current time for unit testing
@@ -41,7 +42,7 @@ public sealed class HttpClientCallInvoker : CallInvoker
4142
/// </summary>
4243
/// <param name="client">The HttpClient to use for gRPC requests.</param>
4344
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
44-
public HttpClientCallInvoker(System.Net.Http.HttpClient client, ILoggerFactory? loggerFactory)
45+
public HttpClientCallInvoker(HttpClient client, ILoggerFactory? loggerFactory)
4546
{
4647
if (client == null)
4748
{

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,29 +109,29 @@ public bool IsCancellationRequested
109109
get { return _callCts.IsCancellationRequested; }
110110
}
111111

112-
public void StartUnary(System.Net.Http.HttpClient client, TRequest request)
112+
public void StartUnary(HttpClient client, TRequest request)
113113
{
114114
var message = CreateHttpRequestMessage();
115115
SetMessageContent(request, message);
116116
StartSend(client, message);
117117
}
118118

119-
public void StartClientStreaming(System.Net.Http.HttpClient client)
119+
public void StartClientStreaming(HttpClient client)
120120
{
121121
var message = CreateHttpRequestMessage();
122122
ClientStreamWriter = CreateWriter(message);
123123
StartSend(client, message);
124124
}
125125

126-
public void StartServerStreaming(System.Net.Http.HttpClient client, TRequest request)
126+
public void StartServerStreaming(HttpClient client, TRequest request)
127127
{
128128
var message = CreateHttpRequestMessage();
129129
SetMessageContent(request, message);
130130
StartSend(client, message);
131131
ClientStreamReader = new HttpContentClientStreamReader<TRequest, TResponse>(this);
132132
}
133133

134-
public void StartDuplexStreaming(System.Net.Http.HttpClient client)
134+
public void StartDuplexStreaming(HttpClient client)
135135
{
136136
var message = CreateHttpRequestMessage();
137137
ClientStreamWriter = CreateWriter(message);
@@ -326,7 +326,7 @@ private void ValidateHeaders()
326326
{
327327
_headerValidationError = "Bad gRPC response. Expected HTTP status code 200. Got status code: " + (int)HttpResponse.StatusCode;
328328
}
329-
else if (HttpResponse.Content.Headers.ContentType == null)
329+
else if (HttpResponse.Content?.Headers.ContentType == null)
330330
{
331331
_headerValidationError = "Bad gRPC response. Response did not have a content-type header.";
332332
}
@@ -405,7 +405,7 @@ private void CancelCall()
405405
return null;
406406
}
407407

408-
private void StartSend(System.Net.Http.HttpClient client, HttpRequestMessage message)
408+
private void StartSend(HttpClient client, HttpRequestMessage message)
409409
{
410410
using (StartScope())
411411
{
@@ -423,7 +423,7 @@ private void StartSend(System.Net.Http.HttpClient client, HttpRequestMessage mes
423423
}
424424
}
425425

426-
private async Task SendAsync(System.Net.Http.HttpClient client, HttpRequestMessage message)
426+
private async Task SendAsync(HttpClient client, HttpRequestMessage message)
427427
{
428428
Log.StartingCall(Logger, Method.Type, message.RequestUri);
429429

src/Grpc.Net.Client/Properties/AssemblyInfo.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
"27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" +
3131
"71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")]
3232

33+
[assembly: InternalsVisibleTo("Grpc.Net.ClientFactory.Tests,PublicKey=" +
34+
"00240000048000009400000006020000002400005253413100040000010001002f5797a92c6fcde81bd4098f43" +
35+
"0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" +
36+
"27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" +
37+
"71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")]
38+
3339
[assembly: InternalsVisibleTo("Grpc.AspNetCore.Server.ClientFactory.Tests,PublicKey=" +
3440
"00240000048000009400000006020000002400005253413100040000010001002f5797a92c6fcde81bd4098f43" +
3541
"0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" +
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
10+
11+
<ProjectReference Include="..\Grpc.Net.Client\Grpc.Net.Client.csproj" />
12+
<PackageReference Include="Grpc.Core" Version="$(GrpcPackageVersion)" />
13+
</ItemGroup>
14+
</Project>

src/Grpc.AspNetCore.Server.ClientFactory/GrpcClientFactory.cs renamed to src/Grpc.Net.ClientFactory/GrpcClientFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
using Grpc.Core;
2020

21-
namespace Grpc.AspNetCore.Server.ClientFactory
21+
namespace Grpc.Net.ClientFactory
2222
{
2323
/// <summary>
2424
/// A factory abstraction for a component that can create gRPC client instances with custom

src/Grpc.AspNetCore.Server.ClientFactory/GrpcClientOptions.cs renamed to src/Grpc.Net.ClientFactory/GrpcClientFactoryOptions.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,31 @@
1717
#endregion
1818

1919
using System;
20-
using System.Security.Cryptography.X509Certificates;
20+
using System.Collections.Generic;
21+
using Grpc.Core.Interceptors;
22+
using Grpc.Net.Client;
2123

22-
namespace Grpc.AspNetCore.Server.ClientFactory
24+
namespace Grpc.Net.ClientFactory
2325
{
2426
/// <summary>
2527
/// Options used to configure a gRPC client.
2628
/// </summary>
27-
public class GrpcClientOptions
29+
public class GrpcClientFactoryOptions
2830
{
2931
/// <summary>
3032
/// The base address to use when making gRPC calls.
3133
/// </summary>
3234
public Uri? BaseAddress { get; set; }
3335

3436
/// <summary>
35-
/// The client certificate to use when making gRPC calls.
37+
/// Gets a list of operations used to configure an <see cref="HttpClientCallInvoker"/>.
3638
/// </summary>
37-
public X509Certificate? Certificate { get; set; }
39+
public IList<Action<HttpClientCallInvoker>> CallInvokerActions { get; } = new List<Action<HttpClientCallInvoker>>();
3840

3941
/// <summary>
40-
/// A flag that indicates whether the call cancellation token should be propagated to client calls. Defaults to true.
42+
/// Gets a list of <see cref="Interceptor"/> instances used to configure a gRPC client pipeline.
4143
/// </summary>
42-
public bool PropagateCancellationToken { get; set; } = true;
43-
44-
/// <summary>
45-
/// A flag that indicates whether the call deadline should be propagated to client calls. Defaults to true.
46-
/// </summary>
47-
public bool PropagateDeadline { get; set; } = true;
44+
public IList<Interceptor> Interceptors { get; } = new List<Interceptor>();
4845

4946
// This property is set internally. It is used to check whether named configuration was explicitly set by the user
5047
internal bool ExplicitlySet { get; set; }

src/Grpc.AspNetCore.Server.ClientFactory/GrpcClientServiceExtensions.cs renamed to src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
using System;
2020
using System.Net.Http;
21-
using Grpc.AspNetCore.Server.ClientFactory;
22-
using Grpc.AspNetCore.Server.ClientFactory.Internal;
21+
using Grpc.Net.ClientFactory;
22+
using Grpc.Net.ClientFactory.Internal;
2323
using Grpc.Core;
2424
using Microsoft.Extensions.DependencyInjection.Extensions;
2525
using Microsoft.Extensions.Options;
@@ -54,7 +54,7 @@ public static class GrpcClientServiceExtensions
5454
/// <typeparamref name="TClient"/> as the service type.
5555
/// </para>
5656
/// </remarks>
57-
public static IHttpClientBuilder AddGrpcClient<TClient>(this IServiceCollection services, Action<GrpcClientOptions> configureClient)
57+
public static IHttpClientBuilder AddGrpcClient<TClient>(this IServiceCollection services, Action<GrpcClientFactoryOptions> configureClient)
5858
where TClient : ClientBase
5959
{
6060
var name = TypeNameHelper.GetTypeDisplayName(typeof(TClient), fullName: false);
@@ -86,13 +86,13 @@ public static IHttpClientBuilder AddGrpcClient<TClient>(this IServiceCollection
8686
/// <typeparamref name="TClient"/> as the service type.
8787
/// </para>
8888
/// </remarks>
89-
public static IHttpClientBuilder AddGrpcClient<TClient>(this IServiceCollection services, string name, Action<GrpcClientOptions> configureClient)
89+
public static IHttpClientBuilder AddGrpcClient<TClient>(this IServiceCollection services, string name, Action<GrpcClientFactoryOptions> configureClient)
9090
where TClient : ClientBase
9191
{
9292
return services.AddGrpcClientCore<TClient>(name, configureClient);
9393
}
9494

95-
private static IHttpClientBuilder AddGrpcClientCore<TClient>(this IServiceCollection services, string name, Action<GrpcClientOptions> configureClient)
95+
private static IHttpClientBuilder AddGrpcClientCore<TClient>(this IServiceCollection services, string name, Action<GrpcClientFactoryOptions> configureClient)
9696
where TClient : ClientBase
9797
{
9898
if (services == null)
@@ -110,40 +110,23 @@ private static IHttpClientBuilder AddGrpcClientCore<TClient>(this IServiceCollec
110110
throw new ArgumentNullException(nameof(configureClient));
111111
}
112112

113-
// HttpContextAccessor is used to resolve the cancellation token, deadline and other request details to use with nested gRPC requests
114-
services.AddHttpContextAccessor();
115113
services.TryAddSingleton<GrpcClientFactory, DefaultGrpcClientFactory>();
116114

117115
services.TryAdd(ServiceDescriptor.Transient(typeof(INamedTypedHttpClientFactory<TClient>), typeof(GrpcHttpClientFactory<TClient>)));
118116
services.TryAdd(ServiceDescriptor.Transient(typeof(GrpcHttpClientFactory<TClient>.Cache), typeof(GrpcHttpClientFactory<TClient>.Cache)));
119117

120118
Action<IServiceProvider, HttpClient> configureTypedClient = (s, httpClient) =>
121119
{
122-
var os = s.GetRequiredService<IOptionsMonitor<GrpcClientOptions>>();
120+
var os = s.GetRequiredService<IOptionsMonitor<GrpcClientFactoryOptions>>();
123121
var clientOptions = os.Get(name);
124122

125123
httpClient.BaseAddress = clientOptions.BaseAddress;
126124
};
127125

128-
Func<IServiceProvider, HttpMessageHandler> configurePrimaryHttpMessageHandler = s =>
129-
{
130-
var os = s.GetRequiredService<IOptionsMonitor<GrpcClientOptions>>();
131-
var clientOptions = os.Get(name);
132-
133-
var handler = new HttpClientHandler();
134-
if (clientOptions.Certificate != null)
135-
{
136-
handler.ClientCertificates.Add(clientOptions.Certificate);
137-
}
138-
139-
return handler;
140-
};
141-
142126
services.Configure(name, configureClient);
143-
services.Configure<GrpcClientOptions>(name, options => options.ExplicitlySet = true);
127+
services.Configure<GrpcClientFactoryOptions>(name, options => options.ExplicitlySet = true);
144128

145129
IHttpClientBuilder clientBuilder = services.AddGrpcHttpClient<TClient>(name, configureTypedClient);
146-
clientBuilder.ConfigurePrimaryHttpMessageHandler(configurePrimaryHttpMessageHandler);
147130

148131
return clientBuilder;
149132
}

0 commit comments

Comments
 (0)