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