#region Copyright notice and license // Copyright 2018 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #endregion using System; using System.Linq; using Grpc.Core.Utils; namespace Grpc.Core.Interceptors { /// /// Extends the CallInvoker class to provide the interceptor facility on the client side. /// public static class CallInvokerExtensions { /// /// Returns a instance that intercepts /// the invoker with the given interceptor. /// /// The underlying invoker to intercept. /// The interceptor to intercept calls to the invoker with. /// /// Multiple interceptors can be added on top of each other by calling /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". /// Interceptors can be later added to an existing intercepted CallInvoker, effectively /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that /// in this case, the last interceptor added will be the first to take control. /// public static CallInvoker Intercept(this CallInvoker invoker, Interceptor interceptor) { return new InterceptingCallInvoker(invoker, interceptor); } /// /// Returns a instance that intercepts /// the invoker with the given interceptors. /// /// The channel to intercept. /// /// An array of interceptors to intercept the calls to the invoker with. /// Control is passed to the interceptors in the order specified. /// /// /// Multiple interceptors can be added on top of each other by calling /// "invoker.Intercept(a, b, c)". The order of invocation will be "a", "b", and then "c". /// Interceptors can be later added to an existing intercepted CallInvoker, effectively /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that /// in this case, the last interceptor added will be the first to take control. /// public static CallInvoker Intercept(this CallInvoker invoker, params Interceptor[] interceptors) { GrpcPreconditions.CheckNotNull(invoker, nameof(invoker)); GrpcPreconditions.CheckNotNull(interceptors, nameof(interceptors)); foreach (var interceptor in interceptors.Reverse()) { invoker = Intercept(invoker, interceptor); } return invoker; } /// /// Returns a instance that intercepts /// the invoker with the given interceptor. /// /// The underlying invoker to intercept. /// /// An interceptor delegate that takes the request metadata to be sent with an outgoing call /// and returns a instance that will replace the existing /// invocation metadata. /// /// /// Multiple interceptors can be added on top of each other by /// building a chain like "invoker.Intercept(c).Intercept(b).Intercept(a)". Note that /// in this case, the last interceptor added will be the first to take control. /// public static CallInvoker Intercept(this CallInvoker invoker, Func interceptor) { return new InterceptingCallInvoker(invoker, new MetadataInterceptor(interceptor)); } private class MetadataInterceptor : Interceptor { readonly Func interceptor; /// /// Creates a new instance of MetadataInterceptor given the specified interceptor function. /// public MetadataInterceptor(Func interceptor) { this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); } private ClientInterceptorContext GetNewContext(ClientInterceptorContext context) where TRequest : class where TResponse : class { var metadata = context.Options.Headers ?? new Metadata(); return new ClientInterceptorContext(context.Method, context.Host, context.Options.WithHeaders(interceptor(metadata))); } public override TResponse BlockingUnaryCall(TRequest request, ClientInterceptorContext context, BlockingUnaryCallContinuation continuation) { return continuation(request, GetNewContext(context)); } public override AsyncUnaryCall AsyncUnaryCall(TRequest request, ClientInterceptorContext context, AsyncUnaryCallContinuation continuation) { return continuation(request, GetNewContext(context)); } public override AsyncServerStreamingCall AsyncServerStreamingCall(TRequest request, ClientInterceptorContext context, AsyncServerStreamingCallContinuation continuation) { return continuation(request, GetNewContext(context)); } public override AsyncClientStreamingCall AsyncClientStreamingCall(ClientInterceptorContext context, AsyncClientStreamingCallContinuation continuation) { return continuation(GetNewContext(context)); } public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall(ClientInterceptorContext context, AsyncDuplexStreamingCallContinuation continuation) { return continuation(GetNewContext(context)); } } } }