1 /* 2 * Copyright 2018 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.servlet; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Preconditions.checkState; 22 import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; 23 24 import com.google.common.annotations.VisibleForTesting; 25 import com.google.common.util.concurrent.ListenableFuture; 26 import io.grpc.Attributes; 27 import io.grpc.ExperimentalApi; 28 import io.grpc.ForwardingServerBuilder; 29 import io.grpc.Internal; 30 import io.grpc.InternalChannelz.SocketStats; 31 import io.grpc.InternalInstrumented; 32 import io.grpc.InternalLogId; 33 import io.grpc.Metadata; 34 import io.grpc.Server; 35 import io.grpc.ServerBuilder; 36 import io.grpc.ServerStreamTracer; 37 import io.grpc.Status; 38 import io.grpc.internal.GrpcUtil; 39 import io.grpc.internal.InternalServer; 40 import io.grpc.internal.ServerImplBuilder; 41 import io.grpc.internal.ServerListener; 42 import io.grpc.internal.ServerStream; 43 import io.grpc.internal.ServerTransport; 44 import io.grpc.internal.ServerTransportListener; 45 import io.grpc.internal.SharedResourceHolder; 46 import java.io.File; 47 import java.io.IOException; 48 import java.net.SocketAddress; 49 import java.util.Collections; 50 import java.util.List; 51 import java.util.concurrent.ScheduledExecutorService; 52 import javax.annotation.Nullable; 53 import javax.annotation.concurrent.NotThreadSafe; 54 55 /** 56 * Builder to build a gRPC server that can run as a servlet. This is for advanced custom settings. 57 * Normally, users should consider extending the out-of-box {@link GrpcServlet} directly instead. 58 * 59 * <p>The API is experimental. The authors would like to know more about the real usecases. Users 60 * are welcome to provide feedback by commenting on 61 * <a href=https://github.com/grpc/grpc-java/issues/5066>the tracking issue</a>. 62 */ 63 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/5066") 64 @NotThreadSafe 65 public final class ServletServerBuilder extends ForwardingServerBuilder<ServletServerBuilder> { 66 List<? extends ServerStreamTracer.Factory> streamTracerFactories; 67 int maxInboundMessageSize = DEFAULT_MAX_MESSAGE_SIZE; 68 69 private final ServerImplBuilder serverImplBuilder; 70 71 private ScheduledExecutorService scheduler; 72 private boolean internalCaller; 73 private boolean usingCustomScheduler; 74 private InternalServerImpl internalServer; 75 ServletServerBuilder()76 public ServletServerBuilder() { 77 serverImplBuilder = new ServerImplBuilder(this::buildTransportServers); 78 } 79 80 /** 81 * Builds a gRPC server that can run as a servlet. 82 * 83 * <p>The returned server will not be started or bound to a port. 84 * 85 * <p>Users should not call this method directly. Instead users should call 86 * {@link #buildServletAdapter()} which internally will call {@code build()} and {@code start()} 87 * appropriately. 88 * 89 * @throws IllegalStateException if this method is called by users directly 90 */ 91 @Override build()92 public Server build() { 93 checkState(internalCaller, "build() method should not be called directly by an application"); 94 return super.build(); 95 } 96 97 /** 98 * Creates a {@link ServletAdapter}. 99 */ buildServletAdapter()100 public ServletAdapter buildServletAdapter() { 101 return new ServletAdapter(buildAndStart(), streamTracerFactories, maxInboundMessageSize); 102 } 103 buildAndStart()104 private ServerTransportListener buildAndStart() { 105 Server server; 106 try { 107 internalCaller = true; 108 server = build().start(); 109 } catch (IOException e) { 110 // actually this should never happen 111 throw new RuntimeException(e); 112 } finally { 113 internalCaller = false; 114 } 115 116 if (!usingCustomScheduler) { 117 scheduler = SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE); 118 } 119 120 // Create only one "transport" for all requests because it has no knowledge of which request is 121 // associated with which client socket. This "transport" does not do socket connection, the 122 // container does. 123 ServerTransportImpl serverTransport = new ServerTransportImpl(scheduler); 124 ServerTransportListener delegate = 125 internalServer.serverListener.transportCreated(serverTransport); 126 return new ServerTransportListener() { 127 @Override 128 public void streamCreated(ServerStream stream, String method, Metadata headers) { 129 delegate.streamCreated(stream, method, headers); 130 } 131 132 @Override 133 public Attributes transportReady(Attributes attributes) { 134 return delegate.transportReady(attributes); 135 } 136 137 @Override 138 public void transportTerminated() { 139 server.shutdown(); 140 delegate.transportTerminated(); 141 if (!usingCustomScheduler) { 142 SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, scheduler); 143 } 144 } 145 }; 146 } 147 148 @VisibleForTesting 149 InternalServer buildTransportServers( 150 List<? extends ServerStreamTracer.Factory> streamTracerFactories) { 151 checkNotNull(streamTracerFactories, "streamTracerFactories"); 152 this.streamTracerFactories = streamTracerFactories; 153 internalServer = new InternalServerImpl(); 154 return internalServer; 155 } 156 157 @Internal 158 @Override 159 protected ServerBuilder<?> delegate() { 160 return serverImplBuilder; 161 } 162 163 /** 164 * Throws {@code UnsupportedOperationException}. TLS should be configured by the servlet 165 * container. 166 */ 167 @Override 168 public ServletServerBuilder useTransportSecurity(File certChain, File privateKey) { 169 throw new UnsupportedOperationException("TLS should be configured by the servlet container"); 170 } 171 172 @Override 173 public ServletServerBuilder maxInboundMessageSize(int bytes) { 174 checkArgument(bytes >= 0, "bytes must be >= 0"); 175 maxInboundMessageSize = bytes; 176 return this; 177 } 178 179 /** 180 * Provides a custom scheduled executor service to the server builder. 181 * 182 * @return this 183 */ 184 public ServletServerBuilder scheduledExecutorService(ScheduledExecutorService scheduler) { 185 this.scheduler = checkNotNull(scheduler, "scheduler"); 186 usingCustomScheduler = true; 187 return this; 188 } 189 190 private static final class InternalServerImpl implements InternalServer { 191 192 ServerListener serverListener; 193 194 InternalServerImpl() {} 195 196 @Override 197 public void start(ServerListener listener) { 198 serverListener = listener; 199 } 200 201 @Override 202 public void shutdown() { 203 if (serverListener != null) { 204 serverListener.serverShutdown(); 205 } 206 } 207 208 @Override 209 public SocketAddress getListenSocketAddress() { 210 return new SocketAddress() { 211 @Override 212 public String toString() { 213 return "ServletServer"; 214 } 215 }; 216 } 217 218 @Override 219 public InternalInstrumented<SocketStats> getListenSocketStats() { 220 // sockets are managed by the servlet container, grpc is ignorant of that 221 return null; 222 } 223 224 @Override 225 public List<? extends SocketAddress> getListenSocketAddresses() { 226 return Collections.emptyList(); 227 } 228 229 @Nullable 230 @Override 231 public List<InternalInstrumented<SocketStats>> getListenSocketStatsList() { 232 return null; 233 } 234 } 235 236 @VisibleForTesting 237 static final class ServerTransportImpl implements ServerTransport { 238 239 private final InternalLogId logId = InternalLogId.allocate(ServerTransportImpl.class, null); 240 private final ScheduledExecutorService scheduler; 241 242 ServerTransportImpl(ScheduledExecutorService scheduler) { 243 this.scheduler = checkNotNull(scheduler, "scheduler"); 244 } 245 246 @Override 247 public void shutdown() {} 248 249 @Override 250 public void shutdownNow(Status reason) {} 251 252 @Override 253 public ScheduledExecutorService getScheduledExecutorService() { 254 return scheduler; 255 } 256 257 @Override 258 public ListenableFuture<SocketStats> getStats() { 259 // does not support instrumentation 260 return null; 261 } 262 263 @Override 264 public InternalLogId getLogId() { 265 return logId; 266 } 267 } 268 } 269