• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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