1
18
19 package io.undertow;
20
21 import io.undertow.connector.ByteBufferPool;
22 import io.undertow.protocols.ssl.UndertowXnioSsl;
23 import io.undertow.server.ConnectorStatistics;
24 import io.undertow.server.DefaultByteBufferPool;
25 import io.undertow.server.HttpHandler;
26 import io.undertow.server.OpenListener;
27 import io.undertow.server.protocol.ajp.AjpOpenListener;
28 import io.undertow.server.protocol.http.AlpnOpenListener;
29 import io.undertow.server.protocol.http.HttpOpenListener;
30 import io.undertow.server.protocol.http2.Http2OpenListener;
31 import io.undertow.server.protocol.http2.Http2UpgradeHandler;
32 import io.undertow.server.protocol.proxy.ProxyProtocolOpenListener;
33 import org.xnio.ChannelListener;
34 import org.xnio.ChannelListeners;
35 import org.xnio.IoUtils;
36 import org.xnio.Option;
37 import org.xnio.OptionMap;
38 import org.xnio.Options;
39 import org.xnio.StreamConnection;
40 import org.xnio.Xnio;
41 import org.xnio.XnioWorker;
42 import org.xnio.channels.AcceptingChannel;
43 import org.xnio.ssl.JsseSslUtils;
44
45 import javax.net.ssl.KeyManager;
46 import javax.net.ssl.SSLContext;
47 import javax.net.ssl.TrustManager;
48 import java.io.IOException;
49 import java.net.Inet4Address;
50 import java.net.InetSocketAddress;
51 import java.net.SocketAddress;
52 import java.security.SecureRandom;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.List;
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.TimeUnit;
58
59
65 public final class Undertow {
66
67 private final int bufferSize;
68 private final int ioThreads;
69 private final int workerThreads;
70 private final boolean directBuffers;
71 private final List<ListenerConfig> listeners = new ArrayList<>();
72 private volatile List<ListenerInfo> listenerInfo;
73 private final HttpHandler rootHandler;
74 private final OptionMap workerOptions;
75 private final OptionMap socketOptions;
76 private final OptionMap serverOptions;
77
78
87 private final boolean internalWorker;
88
89 private ByteBufferPool byteBufferPool;
90 private XnioWorker worker;
91 private List<AcceptingChannel<? extends StreamConnection>> channels;
92 private Xnio xnio;
93
94 private Undertow(Builder builder) {
95 this.byteBufferPool = builder.byteBufferPool;
96 this.bufferSize = byteBufferPool != null ? byteBufferPool.getBufferSize() : builder.bufferSize;
97 this.directBuffers = byteBufferPool != null ? byteBufferPool.isDirect() : builder.directBuffers;
98 this.ioThreads = builder.ioThreads;
99 this.workerThreads = builder.workerThreads;
100 this.listeners.addAll(builder.listeners);
101 this.rootHandler = builder.handler;
102 this.worker = builder.worker;
103 this.internalWorker = builder.worker == null;
104 this.workerOptions = builder.workerOptions.getMap();
105 this.socketOptions = builder.socketOptions.getMap();
106 this.serverOptions = builder.serverOptions.getMap();
107 }
108
109
112 public static Builder builder() {
113 return new Builder();
114 }
115
116 public synchronized void start() {
117 UndertowLogger.ROOT_LOGGER.infof("starting server: %s", Version.getFullVersionString());
118 xnio = Xnio.getInstance(Undertow.class.getClassLoader());
119 channels = new ArrayList<>();
120 try {
121 if (internalWorker) {
122 worker = xnio.createWorker(OptionMap.builder()
123 .set(Options.WORKER_IO_THREADS, ioThreads)
124 .set(Options.CONNECTION_HIGH_WATER, 1000000)
125 .set(Options.CONNECTION_LOW_WATER, 1000000)
126 .set(Options.WORKER_TASK_CORE_THREADS, workerThreads)
127 .set(Options.WORKER_TASK_MAX_THREADS, workerThreads)
128 .set(Options.TCP_NODELAY, true)
129 .set(Options.CORK, true)
130 .addAll(workerOptions)
131 .getMap());
132 }
133
134 OptionMap socketOptions = OptionMap.builder()
135 .set(Options.WORKER_IO_THREADS, worker.getIoThreadCount())
136 .set(Options.TCP_NODELAY, true)
137 .set(Options.REUSE_ADDRESSES, true)
138 .set(Options.BALANCING_TOKENS, 1)
139 .set(Options.BALANCING_CONNECTIONS, 2)
140 .set(Options.BACKLOG, 1000)
141 .addAll(this.socketOptions)
142 .getMap();
143
144 OptionMap serverOptions = OptionMap.builder()
145 .set(UndertowOptions.NO_REQUEST_TIMEOUT, 60 * 1000)
146 .addAll(this.serverOptions)
147 .getMap();
148
149
150 ByteBufferPool buffers = this.byteBufferPool;
151 if (buffers == null) {
152 buffers = new DefaultByteBufferPool(directBuffers, bufferSize, -1, 4);
153 }
154
155 listenerInfo = new ArrayList<>();
156 for (ListenerConfig listener : listeners) {
157 UndertowLogger.ROOT_LOGGER.debugf("Configuring listener with protocol %s for interface %s and port %s", listener.type, listener.host, listener.port);
158 final HttpHandler rootHandler = listener.rootHandler != null ? listener.rootHandler : this.rootHandler;
159 OptionMap socketOptionsWithOverrides = OptionMap.builder().addAll(socketOptions).addAll(listener.overrideSocketOptions).getMap();
160 if (listener.type == ListenerType.AJP) {
161 AjpOpenListener openListener = new AjpOpenListener(buffers, serverOptions);
162 openListener.setRootHandler(rootHandler);
163
164 final ChannelListener<StreamConnection> finalListener;
165 if (listener.useProxyProtocol) {
166 finalListener = new ProxyProtocolOpenListener(openListener, null, buffers, OptionMap.EMPTY);
167 } else {
168 finalListener = openListener;
169 }
170 ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(finalListener);
171 AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides);
172 server.resumeAccepts();
173 channels.add(server);
174 listenerInfo.add(new ListenerInfo("ajp", server.getLocalAddress(), openListener, null, server));
175 } else {
176 OptionMap undertowOptions = OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).addAll(serverOptions).getMap();
177 boolean http2 = serverOptions.get(UndertowOptions.ENABLE_HTTP2, false);
178 if (listener.type == ListenerType.HTTP) {
179 HttpOpenListener openListener = new HttpOpenListener(buffers, undertowOptions);
180 HttpHandler handler = rootHandler;
181 if (http2) {
182 handler = new Http2UpgradeHandler(handler);
183 }
184 openListener.setRootHandler(handler);
185 final ChannelListener<StreamConnection> finalListener;
186 if (listener.useProxyProtocol) {
187 finalListener = new ProxyProtocolOpenListener(openListener, null, buffers, OptionMap.EMPTY);
188 } else {
189 finalListener = openListener;
190 }
191
192 ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(finalListener);
193 AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptionsWithOverrides);
194 server.resumeAccepts();
195 channels.add(server);
196 listenerInfo.add(new ListenerInfo("http", server.getLocalAddress(), openListener, null, server));
197 } else if (listener.type == ListenerType.HTTPS) {
198 OpenListener openListener;
199
200 HttpOpenListener httpOpenListener = new HttpOpenListener(buffers, undertowOptions);
201 httpOpenListener.setRootHandler(rootHandler);
202
203 if (http2) {
204 AlpnOpenListener alpn = new AlpnOpenListener(buffers, undertowOptions, httpOpenListener);
205 Http2OpenListener http2Listener = new Http2OpenListener(buffers, undertowOptions);
206 http2Listener.setRootHandler(rootHandler);
207 alpn.addProtocol(Http2OpenListener.HTTP2, http2Listener, 10);
208 alpn.addProtocol(Http2OpenListener.HTTP2_14, http2Listener, 7);
209 openListener = alpn;
210 } else {
211 openListener = httpOpenListener;
212 }
213
214 UndertowXnioSsl xnioSsl;
215 if (listener.sslContext != null) {
216 xnioSsl = new UndertowXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), listener.sslContext);
217 } else {
218 OptionMap.Builder builder = OptionMap.builder()
219 .addAll(socketOptionsWithOverrides);
220 if (!socketOptionsWithOverrides.contains(Options.SSL_PROTOCOL)) {
221 builder.set(Options.SSL_PROTOCOL, "TLSv1.2");
222 }
223 xnioSsl = new UndertowXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), JsseSslUtils.createSSLContext(listener.keyManagers, listener.trustManagers, new SecureRandom(), builder.getMap()));
224 }
225
226 AcceptingChannel<? extends StreamConnection> sslServer;
227 if (listener.useProxyProtocol) {
228 ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(new ProxyProtocolOpenListener(openListener, xnioSsl, buffers, socketOptionsWithOverrides));
229 sslServer = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), (ChannelListener) acceptListener, socketOptionsWithOverrides);
230 } else {
231 ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(openListener);
232 sslServer = xnioSsl.createSslConnectionServer(worker, new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), (ChannelListener) acceptListener, socketOptionsWithOverrides);
233 }
234
235 sslServer.resumeAccepts();
236 channels.add(sslServer);
237 listenerInfo.add(new ListenerInfo("https", sslServer.getLocalAddress(), openListener, xnioSsl, sslServer));
238 }
239 }
240
241 }
242
243 } catch (Exception e) {
244 if(internalWorker && worker != null) {
245 worker.shutdownNow();
246 }
247 throw new RuntimeException(e);
248 }
249 }
250
251 public synchronized void stop() {
252 UndertowLogger.ROOT_LOGGER.infof("stopping server: %s", Version.getFullVersionString());
253 if (channels != null) {
254 for (AcceptingChannel<? extends StreamConnection> channel : channels) {
255 IoUtils.safeClose(channel);
256 }
257 channels = null;
258 }
259
260
263 if (internalWorker && worker != null) {
264 Integer shutdownTimeoutMillis = serverOptions.get(UndertowOptions.SHUTDOWN_TIMEOUT);
265 worker.shutdown();
266 try {
267 if (shutdownTimeoutMillis == null) {
268 worker.awaitTermination();
269 } else {
270 if (!worker.awaitTermination(shutdownTimeoutMillis, TimeUnit.MILLISECONDS)) {
271 worker.shutdownNow();
272 }
273 }
274 } catch (InterruptedException e) {
275 worker.shutdownNow();
276 Thread.currentThread().interrupt();
277 throw new RuntimeException(e);
278 }
279 worker = null;
280 }
281 xnio = null;
282 listenerInfo = null;
283 }
284
285 public Xnio getXnio() {
286 return xnio;
287 }
288
289 public XnioWorker getWorker() {
290 return worker;
291 }
292
293 public List<ListenerInfo> getListenerInfo() {
294 if (listenerInfo == null) {
295 throw UndertowMessages.MESSAGES.serverNotStarted();
296 }
297 return Collections.unmodifiableList(listenerInfo);
298 }
299
300
301 public enum ListenerType {
302 HTTP,
303 HTTPS,
304 AJP
305 }
306
307 private static class ListenerConfig {
308
309 final ListenerType type;
310 final int port;
311 final String host;
312 final KeyManager[] keyManagers;
313 final TrustManager[] trustManagers;
314 final SSLContext sslContext;
315 final HttpHandler rootHandler;
316 final OptionMap overrideSocketOptions;
317 final boolean useProxyProtocol;
318
319 private ListenerConfig(final ListenerType type, final int port, final String host, KeyManager[] keyManagers, TrustManager[] trustManagers, HttpHandler rootHandler) {
320 this.type = type;
321 this.port = port;
322 this.host = host;
323 this.keyManagers = keyManagers;
324 this.trustManagers = trustManagers;
325 this.rootHandler = rootHandler;
326 this.sslContext = null;
327 this.overrideSocketOptions = OptionMap.EMPTY;
328 this.useProxyProtocol = false;
329 }
330
331 private ListenerConfig(final ListenerType type, final int port, final String host, SSLContext sslContext, HttpHandler rootHandler) {
332 this.type = type;
333 this.port = port;
334 this.host = host;
335 this.rootHandler = rootHandler;
336 this.keyManagers = null;
337 this.trustManagers = null;
338 this.sslContext = sslContext;
339 this.overrideSocketOptions = OptionMap.EMPTY;
340 this.useProxyProtocol = false;
341 }
342
343 private ListenerConfig(final ListenerBuilder listenerBuilder) {
344 this.type = listenerBuilder.type;
345 this.port = listenerBuilder.port;
346 this.host = listenerBuilder.host;
347 this.rootHandler = listenerBuilder.rootHandler;
348 this.keyManagers = listenerBuilder.keyManagers;
349 this.trustManagers = listenerBuilder.trustManagers;
350 this.sslContext = listenerBuilder.sslContext;
351 this.overrideSocketOptions = listenerBuilder.overrideSocketOptions;
352 this.useProxyProtocol = listenerBuilder.useProxyProtocol;
353 }
354 }
355
356 public static final class ListenerBuilder {
357
358 ListenerType type;
359 int port;
360 String host;
361 KeyManager[] keyManagers;
362 TrustManager[] trustManagers;
363 SSLContext sslContext;
364 HttpHandler rootHandler;
365 OptionMap overrideSocketOptions = OptionMap.EMPTY;
366 boolean useProxyProtocol;
367
368 public ListenerBuilder setType(ListenerType type) {
369 this.type = type;
370 return this;
371 }
372
373 public ListenerBuilder setPort(int port) {
374 this.port = port;
375 return this;
376 }
377
378 public ListenerBuilder setHost(String host) {
379 this.host = host;
380 return this;
381 }
382
383 public ListenerBuilder setKeyManagers(KeyManager[] keyManagers) {
384 this.keyManagers = keyManagers;
385 return this;
386 }
387
388 public ListenerBuilder setTrustManagers(TrustManager[] trustManagers) {
389 this.trustManagers = trustManagers;
390 return this;
391 }
392
393 public ListenerBuilder setSslContext(SSLContext sslContext) {
394 this.sslContext = sslContext;
395 return this;
396 }
397
398 public ListenerBuilder setRootHandler(HttpHandler rootHandler) {
399 this.rootHandler = rootHandler;
400 return this;
401 }
402
403 public ListenerBuilder setOverrideSocketOptions(OptionMap overrideSocketOptions) {
404 this.overrideSocketOptions = overrideSocketOptions;
405 return this;
406 }
407
408 public ListenerBuilder setUseProxyProtocol(boolean useProxyProtocol) {
409 this.useProxyProtocol = useProxyProtocol;
410 return this;
411 }
412 }
413
414 public static final class Builder {
415
416 private int bufferSize;
417 private int ioThreads;
418 private int workerThreads;
419 private boolean directBuffers;
420 private final List<ListenerConfig> listeners = new ArrayList<>();
421 private HttpHandler handler;
422 private XnioWorker worker;
423 private ByteBufferPool byteBufferPool;
424
425 private final OptionMap.Builder workerOptions = OptionMap.builder();
426 private final OptionMap.Builder socketOptions = OptionMap.builder();
427 private final OptionMap.Builder serverOptions = OptionMap.builder();
428
429 private Builder() {
430 ioThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2);
431 workerThreads = ioThreads * 8;
432 long maxMemory = Runtime.getRuntime().maxMemory();
433
434 if (maxMemory < 64 * 1024 * 1024) {
435
436 directBuffers = false;
437 bufferSize = 512;
438 } else if (maxMemory < 128 * 1024 * 1024) {
439
440 directBuffers = true;
441 bufferSize = 1024;
442 } else {
443
444
445 directBuffers = true;
446 bufferSize = 1024 * 16 - 20;
447 }
448
449 }
450
451 public Undertow build() {
452 return new Undertow(this);
453 }
454
455 @Deprecated
456 public Builder addListener(int port, String host) {
457 listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, null));
458 return this;
459 }
460
461 @Deprecated
462 public Builder addListener(int port, String host, ListenerType listenerType) {
463 listeners.add(new ListenerConfig(listenerType, port, host, null, null, null));
464 return this;
465 }
466
467 public Builder addListener(ListenerBuilder listenerBuilder) {
468 listeners.add(new ListenerConfig(listenerBuilder));
469 return this;
470 }
471
472 public Builder addHttpListener(int port, String host) {
473 listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, null));
474 return this;
475 }
476
477 public Builder addHttpsListener(int port, String host, KeyManager[] keyManagers, TrustManager[] trustManagers) {
478 listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, keyManagers, trustManagers, null));
479 return this;
480 }
481
482 public Builder addHttpsListener(int port, String host, SSLContext sslContext) {
483 listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, sslContext, null));
484 return this;
485 }
486
487 public Builder addAjpListener(int port, String host) {
488 listeners.add(new ListenerConfig(ListenerType.AJP, port, host, null, null, null));
489 return this;
490 }
491
492 public Builder addHttpListener(int port, String host, HttpHandler rootHandler) {
493 listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, null, null, rootHandler));
494 return this;
495 }
496
497 public Builder addHttpsListener(int port, String host, KeyManager[] keyManagers, TrustManager[] trustManagers, HttpHandler rootHandler) {
498 listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, keyManagers, trustManagers, rootHandler));
499 return this;
500 }
501
502 public Builder addHttpsListener(int port, String host, SSLContext sslContext, HttpHandler rootHandler) {
503 listeners.add(new ListenerConfig(ListenerType.HTTPS, port, host, sslContext, rootHandler));
504 return this;
505 }
506
507 public Builder addAjpListener(int port, String host, HttpHandler rootHandler) {
508 listeners.add(new ListenerConfig(ListenerType.AJP, port, host, null, null, rootHandler));
509 return this;
510 }
511
512 public Builder setBufferSize(final int bufferSize) {
513 this.bufferSize = bufferSize;
514 return this;
515 }
516
517 @Deprecated
518 public Builder setBuffersPerRegion(final int buffersPerRegion) {
519 return this;
520 }
521
522 public Builder setIoThreads(final int ioThreads) {
523 this.ioThreads = ioThreads;
524 return this;
525 }
526
527 public Builder setWorkerThreads(final int workerThreads) {
528 this.workerThreads = workerThreads;
529 return this;
530 }
531
532 public Builder setDirectBuffers(final boolean directBuffers) {
533 this.directBuffers = directBuffers;
534 return this;
535 }
536
537 public Builder setHandler(final HttpHandler handler) {
538 this.handler = handler;
539 return this;
540 }
541
542 public <T> Builder setServerOption(final Option<T> option, final T value) {
543 serverOptions.set(option, value);
544 return this;
545 }
546
547 public <T> Builder setSocketOption(final Option<T> option, final T value) {
548 socketOptions.set(option, value);
549 return this;
550 }
551
552 public <T> Builder setWorkerOption(final Option<T> option, final T value) {
553 workerOptions.set(option, value);
554 return this;
555 }
556
557
569 public <T> Builder setWorker(XnioWorker worker) {
570 this.worker = worker;
571 return this;
572 }
573
574 public <T> Builder setByteBufferPool(ByteBufferPool byteBufferPool) {
575 this.byteBufferPool = byteBufferPool;
576 return this;
577 }
578 }
579
580 public static class ListenerInfo {
581
582 private final String protcol;
583 private final SocketAddress address;
584 private final OpenListener openListener;
585 private final UndertowXnioSsl ssl;
586 private final AcceptingChannel<? extends StreamConnection> channel;
587 private volatile boolean suspended = false;
588
589 public ListenerInfo(String protcol, SocketAddress address, OpenListener openListener, UndertowXnioSsl ssl, AcceptingChannel<? extends StreamConnection> channel) {
590 this.protcol = protcol;
591 this.address = address;
592 this.openListener = openListener;
593 this.ssl = ssl;
594 this.channel = channel;
595 }
596
597 public String getProtcol() {
598 return protcol;
599 }
600
601 public SocketAddress getAddress() {
602 return address;
603 }
604
605 public SSLContext getSslContext() {
606 if(ssl == null) {
607 return null;
608 }
609 return ssl.getSslContext();
610 }
611
612 public void setSslContext(SSLContext sslContext) {
613 if(ssl != null) {
614
615 ssl.updateSSLContext(sslContext);
616 }
617 }
618
619 public synchronized void suspend() {
620 suspended = true;
621 channel.suspendAccepts();
622 CountDownLatch latch = new CountDownLatch(1);
623
624 channel.getIoThread().execute(new Runnable() {
625 @Override
626 public void run() {
627 try {
628 openListener.closeConnections();
629 } finally {
630 latch.countDown();
631 }
632 }
633 });
634 try {
635 latch.await();
636 } catch (InterruptedException e) {
637 throw new RuntimeException(e);
638 }
639 }
640
641 public synchronized void resume() {
642 suspended = false;
643 channel.resumeAccepts();
644 }
645
646 public boolean isSuspended() {
647 return suspended;
648 }
649
650 public ConnectorStatistics getConnectorStatistics() {
651 return openListener.getConnectorStatistics();
652 }
653
654 public <T> void setSocketOption(Option<T>option, T value) throws IOException {
655 channel.setOption(option, value);
656 }
657
658 public void setServerOptions(OptionMap options) {
659 openListener.setUndertowOptions(options);
660 }
661
662 @Override
663 public String toString() {
664 return "ListenerInfo{" +
665 "protcol='" + protcol + '\'' +
666 ", address=" + address +
667 ", sslContext=" + getSslContext() +
668 '}';
669 }
670 }
671
672 }
673