• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.netty;
18 
19 import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
20 import static io.grpc.internal.TransportFrameUtil.toHttp2Headers;
21 import static io.grpc.internal.TransportFrameUtil.toRawSerializedHeaders;
22 import static io.netty.channel.ChannelOption.SO_LINGER;
23 import static io.netty.channel.ChannelOption.SO_TIMEOUT;
24 import static io.netty.util.CharsetUtil.UTF_8;
25 
26 import com.google.common.annotations.VisibleForTesting;
27 import com.google.common.base.Preconditions;
28 import io.grpc.InternalChannelz;
29 import io.grpc.InternalMetadata;
30 import io.grpc.Metadata;
31 import io.grpc.Status;
32 import io.grpc.internal.GrpcUtil;
33 import io.grpc.internal.SharedResourceHolder.Resource;
34 import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2InboundHeaders;
35 import io.grpc.netty.NettySocketSupport.NativeSocketOptions;
36 import io.netty.channel.Channel;
37 import io.netty.channel.ChannelConfig;
38 import io.netty.channel.ChannelOption;
39 import io.netty.channel.EventLoopGroup;
40 import io.netty.channel.nio.NioEventLoopGroup;
41 import io.netty.handler.codec.http2.Http2Exception;
42 import io.netty.handler.codec.http2.Http2Headers;
43 import io.netty.util.AsciiString;
44 import io.netty.util.concurrent.DefaultThreadFactory;
45 import java.io.IOException;
46 import java.nio.channels.ClosedChannelException;
47 import java.util.Map;
48 import java.util.Map.Entry;
49 import java.util.concurrent.ThreadFactory;
50 import java.util.concurrent.TimeUnit;
51 
52 /**
53  * Common utility methods.
54  */
55 @VisibleForTesting
56 class Utils {
57 
58   public static final AsciiString STATUS_OK = AsciiString.of("200");
59   public static final AsciiString HTTP_METHOD = AsciiString.of(GrpcUtil.HTTP_METHOD);
60   public static final AsciiString HTTP_GET_METHOD = AsciiString.of("GET");
61   public static final AsciiString HTTPS = AsciiString.of("https");
62   public static final AsciiString HTTP = AsciiString.of("http");
63   public static final AsciiString CONTENT_TYPE_HEADER = AsciiString.of(CONTENT_TYPE_KEY.name());
64   public static final AsciiString CONTENT_TYPE_GRPC = AsciiString.of(GrpcUtil.CONTENT_TYPE_GRPC);
65   public static final AsciiString TE_HEADER = AsciiString.of(GrpcUtil.TE_HEADER.name());
66   public static final AsciiString TE_TRAILERS = AsciiString.of(GrpcUtil.TE_TRAILERS);
67   public static final AsciiString USER_AGENT = AsciiString.of(GrpcUtil.USER_AGENT_KEY.name());
68 
69   public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP =
70       new DefaultEventLoopGroupResource(1, "grpc-default-boss-ELG");
71 
72   public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP =
73       new DefaultEventLoopGroupResource(0, "grpc-default-worker-ELG");
74 
75   @VisibleForTesting
76   static boolean validateHeaders = false;
77 
convertHeaders(Http2Headers http2Headers)78   public static Metadata convertHeaders(Http2Headers http2Headers) {
79     if (http2Headers instanceof GrpcHttp2InboundHeaders) {
80       GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
81       return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
82     }
83     return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
84   }
85 
convertHeadersToArray(Http2Headers http2Headers)86   private static byte[][] convertHeadersToArray(Http2Headers http2Headers) {
87     // The Netty AsciiString class is really just a wrapper around a byte[] and supports
88     // arbitrary binary data, not just ASCII.
89     byte[][] headerValues = new byte[http2Headers.size() * 2][];
90     int i = 0;
91     for (Map.Entry<CharSequence, CharSequence> entry : http2Headers) {
92       headerValues[i++] = bytes(entry.getKey());
93       headerValues[i++] = bytes(entry.getValue());
94     }
95     return toRawSerializedHeaders(headerValues);
96   }
97 
bytes(CharSequence seq)98   private static byte[] bytes(CharSequence seq) {
99     if (seq instanceof AsciiString) {
100       // Fast path - sometimes copy.
101       AsciiString str = (AsciiString) seq;
102       return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
103     }
104     // Slow path - copy.
105     return seq.toString().getBytes(UTF_8);
106   }
107 
convertClientHeaders(Metadata headers, AsciiString scheme, AsciiString defaultPath, AsciiString authority, AsciiString method, AsciiString userAgent)108   public static Http2Headers convertClientHeaders(Metadata headers,
109       AsciiString scheme,
110       AsciiString defaultPath,
111       AsciiString authority,
112       AsciiString method,
113       AsciiString userAgent) {
114     Preconditions.checkNotNull(defaultPath, "defaultPath");
115     Preconditions.checkNotNull(authority, "authority");
116     Preconditions.checkNotNull(method, "method");
117 
118     // Discard any application supplied duplicates of the reserved headers
119     headers.discardAll(CONTENT_TYPE_KEY);
120     headers.discardAll(GrpcUtil.TE_HEADER);
121     headers.discardAll(GrpcUtil.USER_AGENT_KEY);
122 
123     return GrpcHttp2OutboundHeaders.clientRequestHeaders(
124         toHttp2Headers(headers),
125         authority,
126         defaultPath,
127         method,
128         scheme,
129         userAgent);
130   }
131 
convertServerHeaders(Metadata headers)132   public static Http2Headers convertServerHeaders(Metadata headers) {
133     // Discard any application supplied duplicates of the reserved headers
134     headers.discardAll(CONTENT_TYPE_KEY);
135     headers.discardAll(GrpcUtil.TE_HEADER);
136     headers.discardAll(GrpcUtil.USER_AGENT_KEY);
137 
138     return GrpcHttp2OutboundHeaders.serverResponseHeaders(toHttp2Headers(headers));
139   }
140 
convertTrailers(Http2Headers http2Headers)141   public static Metadata convertTrailers(Http2Headers http2Headers) {
142     if (http2Headers instanceof GrpcHttp2InboundHeaders) {
143       GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
144       return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
145     }
146     return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
147   }
148 
convertTrailers(Metadata trailers, boolean headersSent)149   public static Http2Headers convertTrailers(Metadata trailers, boolean headersSent) {
150     if (!headersSent) {
151       return convertServerHeaders(trailers);
152     }
153     return GrpcHttp2OutboundHeaders.serverResponseTrailers(toHttp2Headers(trailers));
154   }
155 
statusFromThrowable(Throwable t)156   public static Status statusFromThrowable(Throwable t) {
157     Status s = Status.fromThrowable(t);
158     if (s.getCode() != Status.Code.UNKNOWN) {
159       return s;
160     }
161     if (t instanceof ClosedChannelException) {
162       // ClosedChannelException is used any time the Netty channel is closed. Proper error
163       // processing requires remembering the error that occurred before this one and using it
164       // instead.
165       //
166       // Netty uses an exception that has no stack trace, while we would never hope to show this to
167       // users, if it happens having the extra information may provide a small hint of where to
168       // look.
169       ClosedChannelException extraT = new ClosedChannelException();
170       extraT.initCause(t);
171       return Status.UNKNOWN.withDescription("channel closed").withCause(extraT);
172     }
173     if (t instanceof IOException) {
174       return Status.UNAVAILABLE.withDescription("io exception").withCause(t);
175     }
176     if (t instanceof Http2Exception) {
177       return Status.INTERNAL.withDescription("http2 exception").withCause(t);
178     }
179     return s;
180   }
181 
182   private static class DefaultEventLoopGroupResource implements Resource<EventLoopGroup> {
183     private final String name;
184     private final int numEventLoops;
185 
DefaultEventLoopGroupResource(int numEventLoops, String name)186     DefaultEventLoopGroupResource(int numEventLoops, String name) {
187       this.name = name;
188       this.numEventLoops = numEventLoops;
189     }
190 
191     @Override
create()192     public EventLoopGroup create() {
193       // Use Netty's DefaultThreadFactory in order to get the benefit of FastThreadLocal.
194       boolean useDaemonThreads = true;
195       ThreadFactory threadFactory = new DefaultThreadFactory(name, useDaemonThreads);
196       int parallelism = numEventLoops == 0
197           ? Runtime.getRuntime().availableProcessors() * 2 : numEventLoops;
198       return new NioEventLoopGroup(parallelism, threadFactory);
199     }
200 
201     @Override
close(EventLoopGroup instance)202     public void close(EventLoopGroup instance) {
203       instance.shutdownGracefully(0, 0, TimeUnit.SECONDS);
204     }
205 
206     @Override
toString()207     public String toString() {
208       return name;
209     }
210   }
211 
getSocketOptions(Channel channel)212   static InternalChannelz.SocketOptions getSocketOptions(Channel channel) {
213     ChannelConfig config = channel.config();
214     InternalChannelz.SocketOptions.Builder b = new InternalChannelz.SocketOptions.Builder();
215 
216     // The API allows returning null but not sure if it can happen in practice.
217     // Let's be paranoid and do null checking just in case.
218     Integer lingerSeconds = config.getOption(SO_LINGER);
219     if (lingerSeconds != null) {
220       b.setSocketOptionLingerSeconds(lingerSeconds);
221     }
222 
223     Integer timeoutMillis = config.getOption(SO_TIMEOUT);
224     if (timeoutMillis != null) {
225       // in java, SO_TIMEOUT only applies to receiving
226       b.setSocketOptionTimeoutMillis(timeoutMillis);
227     }
228 
229     for (Entry<ChannelOption<?>, Object> opt : config.getOptions().entrySet()) {
230       ChannelOption<?> key = opt.getKey();
231       // Constants are pooled, so there should only be one instance of each constant
232       if (key.equals(SO_LINGER) || key.equals(SO_TIMEOUT)) {
233         continue;
234       }
235       Object value = opt.getValue();
236       // zpencer: Can a netty option be null?
237       b.addOption(key.name(), String.valueOf(value));
238     }
239 
240     NativeSocketOptions nativeOptions
241         = NettySocketSupport.getNativeSocketOptions(channel);
242     if (nativeOptions != null) {
243       b.setTcpInfo(nativeOptions.tcpInfo); // may be null
244       for (Entry<String, String> entry : nativeOptions.otherInfo.entrySet()) {
245         b.addOption(entry.getKey(), entry.getValue());
246       }
247     }
248     return b.build();
249   }
250 
Utils()251   private Utils() {
252     // Prevents instantiation
253   }
254 }
255