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