• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19982

16 Sep 2025 03:47AM UTC coverage: 88.57% (+0.009%) from 88.561%
#19982

push

github

web-flow
servlet: configurable methodNameResolver (#12333)

Introduces configuring a method name resolver in `ServletServerBuilder` for customizing the servlet context root path for request paths.

34839 of 39335 relevant lines covered (88.57%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.87
/../servlet/src/main/java/io/grpc/servlet/ServletServerBuilder.java
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 java.util.function.Function;
53
import javax.annotation.Nullable;
54
import javax.annotation.concurrent.NotThreadSafe;
55
import javax.servlet.http.HttpServletRequest;
56

57
/**
58
 * Builder to build a gRPC server that can run as a servlet. This is for advanced custom settings.
59
 * Normally, users should consider extending the out-of-box {@link GrpcServlet} directly instead.
60
 *
61
 * <p>The API is experimental. The authors would like to know more about the real usecases. Users
62
 * are welcome to provide feedback by commenting on
63
 * <a href=https://github.com/grpc/grpc-java/issues/5066>the tracking issue</a>.
64
 */
65
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/5066")
66
@NotThreadSafe
67
public final class ServletServerBuilder extends ForwardingServerBuilder<ServletServerBuilder> {
68
  List<? extends ServerStreamTracer.Factory> streamTracerFactories;
69
  private Function<HttpServletRequest, String> methodNameResolver =
1✔
70
      ServletAdapter.DEFAULT_METHOD_NAME_RESOLVER;
71
  int maxInboundMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
1✔
72

73
  private final ServerImplBuilder serverImplBuilder;
74

75
  private ScheduledExecutorService scheduler;
76
  private boolean internalCaller;
77
  private boolean usingCustomScheduler;
78
  private InternalServerImpl internalServer;
79

80
  public ServletServerBuilder() {
1✔
81
    serverImplBuilder = new ServerImplBuilder(this::buildTransportServers);
1✔
82
  }
1✔
83

84
  /**
85
   * Builds a gRPC server that can run as a servlet.
86
   *
87
   * <p>The returned server will not be started or bound to a port.
88
   *
89
   * <p>Users should not call this method directly. Instead users should call
90
   * {@link #buildServletAdapter()} which internally will call {@code build()} and {@code start()}
91
   * appropriately.
92
   *
93
   * @throws IllegalStateException if this method is called by users directly
94
   */
95
  @Override
96
  public Server build() {
97
    checkState(internalCaller, "build() method should not be called directly by an application");
1✔
98
    return super.build();
1✔
99
  }
100

101
  /**
102
   * Creates a {@link ServletAdapter}.
103
   */
104
  public ServletAdapter buildServletAdapter() {
105
    return new ServletAdapter(buildAndStart(), streamTracerFactories, methodNameResolver,
1✔
106
        maxInboundMessageSize);
107
  }
108

109
  /**
110
   * Creates a {@link GrpcServlet}.
111
   */
112
  public GrpcServlet buildServlet() {
113
    return new GrpcServlet(buildServletAdapter());
×
114
  }
115

116
  private ServerTransportListener buildAndStart() {
117
    Server server;
118
    try {
119
      internalCaller = true;
1✔
120
      server = build().start();
1✔
121
    } catch (IOException e) {
×
122
      // actually this should never happen
123
      throw new RuntimeException(e);
×
124
    } finally {
125
      internalCaller = false;
1✔
126
    }
127

128
    if (!usingCustomScheduler) {
1✔
129
      scheduler = SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE);
1✔
130
    }
131

132
    // Create only one "transport" for all requests because it has no knowledge of which request is
133
    // associated with which client socket. This "transport" does not do socket connection, the
134
    // container does.
135
    ServerTransportImpl serverTransport = new ServerTransportImpl(scheduler);
1✔
136
    ServerTransportListener delegate =
1✔
137
        internalServer.serverListener.transportCreated(serverTransport);
1✔
138
    return new ServerTransportListener() {
1✔
139
      @Override
140
      public void streamCreated(ServerStream stream, String method, Metadata headers) {
141
        delegate.streamCreated(stream, method, headers);
1✔
142
      }
1✔
143

144
      @Override
145
      public Attributes transportReady(Attributes attributes) {
146
        return delegate.transportReady(attributes);
1✔
147
      }
148

149
      @Override
150
      public void transportTerminated() {
151
        server.shutdown();
1✔
152
        delegate.transportTerminated();
1✔
153
        if (!usingCustomScheduler) {
1✔
154
          SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, scheduler);
1✔
155
        }
156
      }
1✔
157
    };
158
  }
159

160
  @VisibleForTesting
161
  InternalServer buildTransportServers(
162
      List<? extends ServerStreamTracer.Factory> streamTracerFactories) {
163
    checkNotNull(streamTracerFactories, "streamTracerFactories");
1✔
164
    this.streamTracerFactories = streamTracerFactories;
1✔
165
    internalServer = new InternalServerImpl();
1✔
166
    return internalServer;
1✔
167
  }
168

169
  @Internal
170
  @Override
171
  protected ServerBuilder<?> delegate() {
172
    return serverImplBuilder;
1✔
173
  }
174

175
  /**
176
   * Throws {@code UnsupportedOperationException}. TLS should be configured by the servlet
177
   * container.
178
   */
179
  @Override
180
  public ServletServerBuilder useTransportSecurity(File certChain, File privateKey) {
181
    throw new UnsupportedOperationException("TLS should be configured by the servlet container");
×
182
  }
183

184
  /**
185
   * Specifies how to determine gRPC method name from servlet request.
186
   *
187
   * <p>The default strategy is using {@link HttpServletRequest#getRequestURI()} without the leading
188
   * slash.</p>
189
   */
190
  public ServletServerBuilder methodNameResolver(
191
      Function<HttpServletRequest, String> methodResolver) {
192
    this.methodNameResolver = checkNotNull(methodResolver);
×
193
    return this;
×
194
  }
195

196
  @Override
197
  public ServletServerBuilder maxInboundMessageSize(int bytes) {
198
    checkArgument(bytes >= 0, "bytes must be >= 0");
1✔
199
    maxInboundMessageSize = bytes;
1✔
200
    return this;
1✔
201
  }
202

203
  /**
204
   * Provides a custom scheduled executor service to the server builder.
205
   *
206
   * @return this
207
   */
208
  public ServletServerBuilder scheduledExecutorService(ScheduledExecutorService scheduler) {
209
    this.scheduler = checkNotNull(scheduler, "scheduler");
1✔
210
    usingCustomScheduler = true;
1✔
211
    return this;
1✔
212
  }
213

214
  private static final class InternalServerImpl implements InternalServer {
215

216
    ServerListener serverListener;
217

218
    InternalServerImpl() {}
1✔
219

220
    @Override
221
    public void start(ServerListener listener) {
222
      serverListener = listener;
1✔
223
    }
1✔
224

225
    @Override
226
    public void shutdown() {
227
      if (serverListener != null) {
1✔
228
        serverListener.serverShutdown();
1✔
229
      }
230
    }
1✔
231

232
    @Override
233
    public SocketAddress getListenSocketAddress() {
234
      return new SocketAddress() {
1✔
235
        @Override
236
        public String toString() {
237
          return "ServletServer";
×
238
        }
239
      };
240
    }
241

242
    @Override
243
    public InternalInstrumented<SocketStats> getListenSocketStats() {
244
      // sockets are managed by the servlet container, grpc is ignorant of that
245
      return null;
×
246
    }
247

248
    @Override
249
    public List<? extends SocketAddress> getListenSocketAddresses() {
250
      return Collections.emptyList();
1✔
251
    }
252

253
    @Nullable
254
    @Override
255
    public List<InternalInstrumented<SocketStats>> getListenSocketStatsList() {
256
      return null;
×
257
    }
258
  }
259

260
  @VisibleForTesting
261
  static final class ServerTransportImpl implements ServerTransport {
262

263
    private final InternalLogId logId = InternalLogId.allocate(ServerTransportImpl.class, null);
1✔
264
    private final ScheduledExecutorService scheduler;
265

266
    ServerTransportImpl(ScheduledExecutorService scheduler) {
1✔
267
      this.scheduler = checkNotNull(scheduler, "scheduler");
1✔
268
    }
1✔
269

270
    @Override
271
    public void shutdown() {}
1✔
272

273
    @Override
274
    public void shutdownNow(Status reason) {}
1✔
275

276
    @Override
277
    public ScheduledExecutorService getScheduledExecutorService() {
278
      return scheduler;
1✔
279
    }
280

281
    @Override
282
    public ListenableFuture<SocketStats> getStats() {
283
      // does not support instrumentation
284
      return null;
×
285
    }
286

287
    @Override
288
    public InternalLogId getLogId() {
289
      return logId;
1✔
290
    }
291
  }
292
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc