| | | 1 | | using System; |
| | | 2 | | using System.Linq; |
| | | 3 | | using System.Net.Http; |
| | | 4 | | using System.Net.Security; |
| | | 5 | | using Grpc.Core; |
| | | 6 | | using Grpc.Net.Client; |
| | | 7 | | using Grpc.Net.Client.Balancer; |
| | | 8 | | using Grpc.Net.Client.Configuration; |
| | | 9 | | using Microsoft.Extensions.DependencyInjection; |
| | | 10 | | |
| | | 11 | | namespace dotnet_etcd; |
| | | 12 | | |
| | | 13 | | /// <summary> |
| | | 14 | | /// Factory for creating gRPC channels |
| | | 15 | | /// </summary> |
| | | 16 | | public class GrpcChannelFactory |
| | | 17 | | { |
| | 1 | 18 | | private static readonly MethodConfig DefaultGrpcMethodConfig = new() |
| | 1 | 19 | | { |
| | 1 | 20 | | Names = { MethodName.Default }, |
| | 1 | 21 | | RetryPolicy = new RetryPolicy |
| | 1 | 22 | | { |
| | 1 | 23 | | MaxAttempts = 5, |
| | 1 | 24 | | InitialBackoff = TimeSpan.FromSeconds(1), |
| | 1 | 25 | | MaxBackoff = TimeSpan.FromSeconds(5), |
| | 1 | 26 | | BackoffMultiplier = 1.5, |
| | 1 | 27 | | RetryableStatusCodes = { StatusCode.Unavailable } |
| | 1 | 28 | | } |
| | 1 | 29 | | }; |
| | | 30 | | |
| | 1 | 31 | | private static readonly RetryThrottlingPolicy DefaultRetryThrottlingPolicy = new() |
| | 1 | 32 | | { |
| | 1 | 33 | | MaxTokens = 10, |
| | 1 | 34 | | TokenRatio = 0.1 |
| | 1 | 35 | | }; |
| | | 36 | | |
| | | 37 | | private const string StaticHostsPrefix = "static://"; |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Creates a gRPC channel with the specified configuration |
| | | 41 | | /// </summary> |
| | | 42 | | /// <param name="connectionString">The connection string</param> |
| | | 43 | | /// <param name="port">The port to use</param> |
| | | 44 | | /// <param name="serverName">The server name</param> |
| | | 45 | | /// <param name="credentials">The credentials to use</param> |
| | | 46 | | /// <param name="configureChannelOptions">Optional configuration for channel options</param> |
| | | 47 | | /// <param name="configureSslOptions">Optional configuration for SSL options (for custom SSL certificates)</param> |
| | | 48 | | /// <returns>A gRPC channel</returns> |
| | | 49 | | public GrpcChannel CreateChannel( |
| | | 50 | | string connectionString, |
| | | 51 | | int port, |
| | | 52 | | string serverName, |
| | | 53 | | ChannelCredentials credentials, |
| | | 54 | | Action<GrpcChannelOptions> configureChannelOptions = null, |
| | | 55 | | Action<SslClientAuthenticationOptions> configureSslOptions = null) |
| | 14 | 56 | | { |
| | 14 | 57 | | var parser = new ConnectionStringParser(); |
| | 14 | 58 | | var (uris, isDnsConnection) = parser.ParseConnectionString(connectionString, port, credentials); |
| | | 59 | | |
| | 14 | 60 | | var httpHandler = new SocketsHttpHandler |
| | 14 | 61 | | { |
| | 14 | 62 | | KeepAlivePingDelay = TimeSpan.FromSeconds(30), |
| | 14 | 63 | | KeepAlivePingTimeout = TimeSpan.FromSeconds(30), |
| | 14 | 64 | | KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always |
| | 14 | 65 | | }; |
| | | 66 | | |
| | | 67 | | // Configure SSL options if provided - this is the correct way for gRPC .NET |
| | 14 | 68 | | if (configureSslOptions != null) |
| | 1 | 69 | | { |
| | 1 | 70 | | var sslOptions = new SslClientAuthenticationOptions(); |
| | 1 | 71 | | configureSslOptions(sslOptions); |
| | 1 | 72 | | httpHandler.SslOptions = sslOptions; |
| | 1 | 73 | | } |
| | | 74 | | |
| | 14 | 75 | | var options = new GrpcChannelOptions |
| | 14 | 76 | | { |
| | 14 | 77 | | ServiceConfig = new ServiceConfig |
| | 14 | 78 | | { |
| | 14 | 79 | | MethodConfigs = { DefaultGrpcMethodConfig }, |
| | 14 | 80 | | RetryThrottling = DefaultRetryThrottlingPolicy, |
| | 14 | 81 | | LoadBalancingConfigs = { new RoundRobinConfig() } |
| | 14 | 82 | | }, |
| | 14 | 83 | | HttpHandler = httpHandler, |
| | 14 | 84 | | DisposeHttpClient = true, |
| | 14 | 85 | | ThrowOperationCanceledOnCancellation = true, |
| | 14 | 86 | | Credentials = credentials |
| | 14 | 87 | | }; |
| | | 88 | | |
| | 14 | 89 | | configureChannelOptions?.Invoke(options); |
| | | 90 | | |
| | 14 | 91 | | if (isDnsConnection) |
| | 1 | 92 | | { |
| | 1 | 93 | | return GrpcChannel.ForAddress(uris[0].ToString(), options); |
| | | 94 | | } |
| | | 95 | | |
| | 41 | 96 | | var factory = new StaticResolverFactory(addr => uris.Select(i => new BalancerAddress(i.Host, i.Port)).ToArray()) |
| | 13 | 97 | | var services = new ServiceCollection(); |
| | 13 | 98 | | services.AddSingleton<ResolverFactory>(factory); |
| | 13 | 99 | | options.ServiceProvider = services.BuildServiceProvider(); |
| | | 100 | | |
| | 13 | 101 | | return GrpcChannel.ForAddress($"{StaticHostsPrefix}{serverName}", options); |
| | 14 | 102 | | } |
| | | 103 | | } |