1 #region Copyright notice and license 2 3 // Copyright 2015-2016 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 Grpc.Core.Interceptors; 21 using Grpc.Core.Internal; 22 using Grpc.Core.Utils; 23 24 namespace Grpc.Core 25 { 26 /// <summary> 27 /// Generic base class for client-side stubs. 28 /// </summary> 29 public abstract class ClientBase<T> : ClientBase 30 where T : ClientBase<T> 31 { 32 /// <summary> 33 /// Initializes a new instance of <c>ClientBase</c> class that 34 /// throws <c>NotImplementedException</c> upon invocation of any RPC. 35 /// This constructor is only provided to allow creation of test doubles 36 /// for client classes (e.g. mocking requires a parameterless constructor). 37 /// </summary> ClientBase()38 protected ClientBase() : base() 39 { 40 } 41 42 /// <summary> 43 /// Initializes a new instance of <c>ClientBase</c> class. 44 /// </summary> 45 /// <param name="configuration">The configuration.</param> ClientBase(ClientBaseConfiguration configuration)46 protected ClientBase(ClientBaseConfiguration configuration) : base(configuration) 47 { 48 } 49 50 /// <summary> 51 /// Initializes a new instance of <c>ClientBase</c> class. 52 /// </summary> 53 /// <param name="channel">The channel to use for remote call invocation.</param> ClientBase(Channel channel)54 public ClientBase(Channel channel) : base(channel) 55 { 56 } 57 58 /// <summary> 59 /// Initializes a new instance of <c>ClientBase</c> class. 60 /// </summary> 61 /// <param name="callInvoker">The <c>CallInvoker</c> for remote call invocation.</param> ClientBase(CallInvoker callInvoker)62 public ClientBase(CallInvoker callInvoker) : base(callInvoker) 63 { 64 } 65 66 /// <summary> 67 /// Creates a new client that sets host field for calls explicitly. 68 /// gRPC supports multiple "hosts" being served by a single server. 69 /// By default (if a client was not created by calling this method), 70 /// host <c>null</c> with the meaning "use default host" is used. 71 /// </summary> WithHost(string host)72 public T WithHost(string host) 73 { 74 var newConfiguration = this.Configuration.WithHost(host); 75 return NewInstance(newConfiguration); 76 } 77 78 /// <summary> 79 /// Creates a new instance of client from given <c>ClientBaseConfiguration</c>. 80 /// </summary> NewInstance(ClientBaseConfiguration configuration)81 protected abstract T NewInstance(ClientBaseConfiguration configuration); 82 } 83 84 /// <summary> 85 /// Base class for client-side stubs. 86 /// </summary> 87 public abstract class ClientBase 88 { 89 readonly ClientBaseConfiguration configuration; 90 readonly CallInvoker callInvoker; 91 92 /// <summary> 93 /// Initializes a new instance of <c>ClientBase</c> class that 94 /// throws <c>NotImplementedException</c> upon invocation of any RPC. 95 /// This constructor is only provided to allow creation of test doubles 96 /// for client classes (e.g. mocking requires a parameterless constructor). 97 /// </summary> ClientBase()98 protected ClientBase() : this(new UnimplementedCallInvoker()) 99 { 100 } 101 102 /// <summary> 103 /// Initializes a new instance of <c>ClientBase</c> class. 104 /// </summary> 105 /// <param name="configuration">The configuration.</param> ClientBase(ClientBaseConfiguration configuration)106 protected ClientBase(ClientBaseConfiguration configuration) 107 { 108 this.configuration = GrpcPreconditions.CheckNotNull(configuration, "configuration"); 109 this.callInvoker = configuration.CreateDecoratedCallInvoker(); 110 } 111 112 /// <summary> 113 /// Initializes a new instance of <c>ClientBase</c> class. 114 /// </summary> 115 /// <param name="channel">The channel to use for remote call invocation.</param> ClientBase(Channel channel)116 public ClientBase(Channel channel) : this(new DefaultCallInvoker(channel)) 117 { 118 } 119 120 /// <summary> 121 /// Initializes a new instance of <c>ClientBase</c> class. 122 /// </summary> 123 /// <param name="callInvoker">The <c>CallInvoker</c> for remote call invocation.</param> ClientBase(CallInvoker callInvoker)124 public ClientBase(CallInvoker callInvoker) : this(new ClientBaseConfiguration(callInvoker, null)) 125 { 126 } 127 128 /// <summary> 129 /// Gets the call invoker. 130 /// </summary> 131 protected CallInvoker CallInvoker 132 { 133 get { return this.callInvoker; } 134 } 135 136 /// <summary> 137 /// Gets the configuration. 138 /// </summary> 139 internal ClientBaseConfiguration Configuration 140 { 141 get { return this.configuration; } 142 } 143 144 /// <summary> 145 /// Represents configuration of ClientBase. The class itself is visible to 146 /// subclasses, but contents are marked as internal to make the instances opaque. 147 /// The verbose name of this class was chosen to make name clash in generated code 148 /// less likely. 149 /// </summary> 150 protected internal class ClientBaseConfiguration 151 { 152 private class ClientBaseConfigurationInterceptor : Interceptor 153 { 154 readonly Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor; 155 156 /// <summary> 157 /// Creates a new instance of ClientBaseConfigurationInterceptor given the specified header and host interceptor function. 158 /// </summary> ClientBaseConfigurationInterceptor(Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor)159 public ClientBaseConfigurationInterceptor(Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor) 160 { 161 this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); 162 } 163 164 private ClientInterceptorContext<TRequest, TResponse> GetNewContext<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) 165 where TRequest : class 166 where TResponse : class 167 { 168 var newHostAndCallOptions = interceptor(context.Method, context.Host, context.Options); 169 return new ClientInterceptorContext<TRequest, TResponse>(context.Method, newHostAndCallOptions.Item1, newHostAndCallOptions.Item2); 170 } 171 BlockingUnaryCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)172 public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) 173 { 174 return continuation(request, GetNewContext(context)); 175 } 176 AsyncUnaryCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)177 public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) 178 { 179 return continuation(request, GetNewContext(context)); 180 } 181 AsyncServerStreamingCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)182 public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) 183 { 184 return continuation(request, GetNewContext(context)); 185 } 186 AsyncClientStreamingCall(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)187 public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) 188 { 189 return continuation(GetNewContext(context)); 190 } 191 AsyncDuplexStreamingCall(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)192 public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) 193 { 194 return continuation(GetNewContext(context)); 195 } 196 } 197 198 readonly CallInvoker undecoratedCallInvoker; 199 readonly string host; 200 ClientBaseConfiguration(CallInvoker undecoratedCallInvoker, string host)201 internal ClientBaseConfiguration(CallInvoker undecoratedCallInvoker, string host) 202 { 203 this.undecoratedCallInvoker = GrpcPreconditions.CheckNotNull(undecoratedCallInvoker); 204 this.host = host; 205 } 206 CreateDecoratedCallInvoker()207 internal CallInvoker CreateDecoratedCallInvoker() 208 { 209 return undecoratedCallInvoker.Intercept(new ClientBaseConfigurationInterceptor((method, host, options) => Tuple.Create(this.host, options))); 210 } 211 WithHost(string host)212 internal ClientBaseConfiguration WithHost(string host) 213 { 214 GrpcPreconditions.CheckNotNull(host, nameof(host)); 215 return new ClientBaseConfiguration(this.undecoratedCallInvoker, host); 216 } 217 } 218 } 219 } 220