1 #region Copyright notice and license 2 3 // Copyright 2018 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.Linq; 21 using Grpc.Core.Utils; 22 23 namespace Grpc.Core.Interceptors 24 { 25 /// <summary> 26 /// Extends the CallInvoker class to provide the interceptor facility on the client side. 27 /// </summary> 28 public static class CallInvokerExtensions 29 { 30 /// <summary> 31 /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts 32 /// the invoker with the given interceptor. 33 /// </summary> 34 /// <param name="invoker">The underlying invoker to intercept.</param> 35 /// <param name="interceptor">The interceptor to intercept calls to the invoker with.</param> 36 /// <remarks> 37 /// Multiple interceptors can be added on top of each other by calling 38 /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". 39 /// Interceptors can be later added to an existing intercepted CallInvoker, effectively 40 /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that 41 /// in this case, the last interceptor added will be the first to take control. 42 /// </remarks> Intercept(this CallInvoker invoker, Interceptor interceptor)43 public static CallInvoker Intercept(this CallInvoker invoker, Interceptor interceptor) 44 { 45 return new InterceptingCallInvoker(invoker, interceptor); 46 } 47 48 /// <summary> 49 /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts 50 /// the invoker with the given interceptors. 51 /// </summary> 52 /// <param name="invoker">The channel to intercept.</param> 53 /// <param name="interceptors"> 54 /// An array of interceptors to intercept the calls to the invoker with. 55 /// Control is passed to the interceptors in the order specified. 56 /// </param> 57 /// <remarks> 58 /// Multiple interceptors can be added on top of each other by calling 59 /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". 60 /// Interceptors can be later added to an existing intercepted CallInvoker, effectively 61 /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that 62 /// in this case, the last interceptor added will be the first to take control. 63 /// </remarks> Intercept(this CallInvoker invoker, params Interceptor[] interceptors)64 public static CallInvoker Intercept(this CallInvoker invoker, params Interceptor[] interceptors) 65 { 66 GrpcPreconditions.CheckNotNull(invoker, nameof(invoker)); 67 GrpcPreconditions.CheckNotNull(interceptors, nameof(interceptors)); 68 69 foreach (var interceptor in interceptors.Reverse()) 70 { 71 invoker = Intercept(invoker, interceptor); 72 } 73 74 return invoker; 75 } 76 77 /// <summary> 78 /// Returns a <see cref="Grpc.Core.CallInvoker" /> instance that intercepts 79 /// the invoker with the given interceptor. 80 /// </summary> 81 /// <param name="invoker">The underlying invoker to intercept.</param> 82 /// <param name="interceptor"> 83 /// An interceptor delegate that takes the request metadata to be sent with an outgoing call 84 /// and returns a <see cref="Grpc.Core.Metadata" /> instance that will replace the existing 85 /// invocation metadata. 86 /// </param> 87 /// <remarks> 88 /// Multiple interceptors can be added on top of each other by 89 /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that 90 /// in this case, the last interceptor added will be the first to take control. 91 /// </remarks> Intercept(this CallInvoker invoker, Func<Metadata, Metadata> interceptor)92 public static CallInvoker Intercept(this CallInvoker invoker, Func<Metadata, Metadata> interceptor) 93 { 94 return new InterceptingCallInvoker(invoker, new MetadataInterceptor(interceptor)); 95 } 96 97 private class MetadataInterceptor : Interceptor 98 { 99 readonly Func<Metadata, Metadata> interceptor; 100 101 /// <summary> 102 /// Creates a new instance of MetadataInterceptor given the specified interceptor function. 103 /// </summary> MetadataInterceptor(Func<Metadata, Metadata> interceptor)104 public MetadataInterceptor(Func<Metadata, Metadata> interceptor) 105 { 106 this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); 107 } 108 109 private ClientInterceptorContext<TRequest, TResponse> GetNewContext<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context) 110 where TRequest : class 111 where TResponse : class 112 { 113 var metadata = context.Options.Headers ?? new Metadata(); 114 return new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, context.Options.WithHeaders(interceptor(metadata))); 115 } 116 BlockingUnaryCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation)117 public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) 118 { 119 return continuation(request, GetNewContext(context)); 120 } 121 AsyncUnaryCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)122 public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation) 123 { 124 return continuation(request, GetNewContext(context)); 125 } 126 AsyncServerStreamingCall(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)127 public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) 128 { 129 return continuation(request, GetNewContext(context)); 130 } 131 AsyncClientStreamingCall(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)132 public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) 133 { 134 return continuation(GetNewContext(context)); 135 } 136 AsyncDuplexStreamingCall(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)137 public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context, AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) 138 { 139 return continuation(GetNewContext(context)); 140 } 141 } 142 } 143 } 144