1 /*
2  * Copyright 2014 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.stub;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import io.grpc.CallOptions;
22 import io.grpc.Channel;
23 import io.grpc.ClientCall;
24 import io.grpc.ClientInterceptor;
25 import io.grpc.ExperimentalApi;
26 import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
27 import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
28 import io.grpc.Metadata;
29 import io.grpc.MethodDescriptor;
30 import io.grpc.Status;
31 import java.util.concurrent.atomic.AtomicReference;
32 
33 /**
34  * Utility functions for binding and receiving headers.
35  */
36 public final class MetadataUtils {
37   // Prevent instantiation
MetadataUtils()38   private MetadataUtils() {}
39 
40   /**
41    * Attaches a set of request headers to a stub.
42    *
43    * @param stub to bind the headers to.
44    * @param extraHeaders the headers to be passed by each call on the returned stub.
45    * @return an implementation of the stub with {@code extraHeaders} bound to each call.
46    */
47   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
attachHeaders(T stub, Metadata extraHeaders)48   public static <T extends AbstractStub<T>> T attachHeaders(T stub, Metadata extraHeaders) {
49     return stub.withInterceptors(newAttachHeadersInterceptor(extraHeaders));
50   }
51 
52   /**
53    * Returns a client interceptor that attaches a set of headers to requests.
54    *
55    * @param extraHeaders the headers to be passed by each call that is processed by the returned
56    *                     interceptor
57    */
newAttachHeadersInterceptor(Metadata extraHeaders)58   public static ClientInterceptor newAttachHeadersInterceptor(Metadata extraHeaders) {
59     return new HeaderAttachingClientInterceptor(extraHeaders);
60   }
61 
62   private static final class HeaderAttachingClientInterceptor implements ClientInterceptor {
63 
64     private final Metadata extraHeaders;
65 
66     // Non private to avoid synthetic class
HeaderAttachingClientInterceptor(Metadata extraHeaders)67     HeaderAttachingClientInterceptor(Metadata extraHeaders) {
68       this.extraHeaders = checkNotNull(extraHeaders, extraHeaders);
69     }
70 
71     @Override
interceptCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next)72     public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
73         MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
74       return new HeaderAttachingClientCall<ReqT, RespT>(next.newCall(method, callOptions));
75     }
76 
77     private final class HeaderAttachingClientCall<ReqT, RespT>
78         extends SimpleForwardingClientCall<ReqT, RespT> {
79 
80       // Non private to avoid synthetic class
HeaderAttachingClientCall(ClientCall<ReqT, RespT> call)81       HeaderAttachingClientCall(ClientCall<ReqT, RespT> call) {
82         super(call);
83       }
84 
85       @Override
start(Listener<RespT> responseListener, Metadata headers)86       public void start(Listener<RespT> responseListener, Metadata headers) {
87         headers.merge(extraHeaders);
88         super.start(responseListener, headers);
89       }
90     }
91   }
92 
93   /**
94    * Captures the last received metadata for a stub. Useful for testing
95    *
96    * @param stub to capture for
97    * @param headersCapture to record the last received headers
98    * @param trailersCapture to record the last received trailers
99    * @return an implementation of the stub that allows to access the last received call's
100    *         headers and trailers via {@code headersCapture} and {@code trailersCapture}.
101    */
102   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
captureMetadata( T stub, AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture)103   public static <T extends AbstractStub<T>> T captureMetadata(
104       T stub,
105       AtomicReference<Metadata> headersCapture,
106       AtomicReference<Metadata> trailersCapture) {
107     return stub.withInterceptors(
108         newCaptureMetadataInterceptor(headersCapture, trailersCapture));
109   }
110 
111   /**
112    * Captures the last received metadata on a channel. Useful for testing.
113    *
114    * @param headersCapture to record the last received headers
115    * @param trailersCapture to record the last received trailers
116    * @return an implementation of the channel with captures installed.
117    */
newCaptureMetadataInterceptor( AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture)118   public static ClientInterceptor newCaptureMetadataInterceptor(
119       AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture) {
120     return new MetadataCapturingClientInterceptor(headersCapture, trailersCapture);
121   }
122 
123   private static final class MetadataCapturingClientInterceptor implements ClientInterceptor {
124 
125     final AtomicReference<Metadata> headersCapture;
126     final AtomicReference<Metadata> trailersCapture;
127 
128     // Non private to avoid synthetic class
MetadataCapturingClientInterceptor( AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture)129     MetadataCapturingClientInterceptor(
130         AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture) {
131       this.headersCapture = checkNotNull(headersCapture, "headersCapture");
132       this.trailersCapture = checkNotNull(trailersCapture, "trailersCapture");
133     }
134 
135     @Override
interceptCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next)136     public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
137         MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
138       return new MetadataCapturingClientCall<ReqT, RespT>(next.newCall(method, callOptions));
139     }
140 
141     private final class MetadataCapturingClientCall<ReqT, RespT>
142         extends SimpleForwardingClientCall<ReqT, RespT> {
143 
144       // Non private to avoid synthetic class
MetadataCapturingClientCall(ClientCall<ReqT, RespT> call)145       MetadataCapturingClientCall(ClientCall<ReqT, RespT> call) {
146         super(call);
147       }
148 
149       @Override
start(ClientCall.Listener<RespT> responseListener, Metadata headers)150       public void start(ClientCall.Listener<RespT> responseListener, Metadata headers) {
151         headersCapture.set(null);
152         trailersCapture.set(null);
153         super.start(new MetadataCapturingClientCallListener(responseListener), headers);
154       }
155 
156       private final class MetadataCapturingClientCallListener
157           extends SimpleForwardingClientCallListener<RespT> {
158 
MetadataCapturingClientCallListener(ClientCall.Listener<RespT> responseListener)159         MetadataCapturingClientCallListener(ClientCall.Listener<RespT> responseListener) {
160           super(responseListener);
161         }
162 
163         @Override
onHeaders(Metadata headers)164         public void onHeaders(Metadata headers) {
165           headersCapture.set(headers);
166           super.onHeaders(headers);
167         }
168 
169         @Override
onClose(Status status, Metadata trailers)170         public void onClose(Status status, Metadata trailers) {
171           trailersCapture.set(trailers);
172           super.onClose(status, trailers);
173         }
174       }
175     }
176   }
177 }
178