1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

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 /**
60  * Convenience class used to build an Undertow server.
61  * <p>
62  *
63  * @author Stuart Douglas
64  */

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     /**
79      * Will be true when a {@link XnioWorker} instance was NOT provided to the {@link Builder}.
80      * When true, a new worker will be created during {@link Undertow#start()},
81      * and shutdown when {@link Undertow#stop()} is called.
82      * <p>
83      * Will be false when a {@link XnioWorker} instance was provided to the {@link Builder}.
84      * When false, the provided {@link #worker} will be used instead of creating a new one in {@link Undertow#start()}.
85      * Also, when false, the {@link #worker} will NOT be shutdown when {@link Undertow#stop()} is called.
86      */

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     /**
110      * @return A builder that can be used to create an Undertow server instance
111      */

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         /*
261          * Only shutdown the worker if it was created during start()
262          */

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             //smaller than 64mb of ram we use 512b buffers
434             if (maxMemory < 64 * 1024 * 1024) {
435                 //use 512b buffers
436                 directBuffers = false;
437                 bufferSize = 512;
438             } else if (maxMemory < 128 * 1024 * 1024) {
439                 //use 1k buffers
440                 directBuffers = true;
441                 bufferSize = 1024;
442             } else {
443                 //use 16k buffers for best performance
444                 //as 16k is generally the max amount of data that can be sent in a single write() call
445                 directBuffers = true;
446                 bufferSize = 1024 * 16 - 20; //the 20 is to allow some space for protocol headers, see UNDERTOW-1209
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, nullnullnull));
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, nullnullnull));
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, nullnullnull));
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, nullnullnull));
489             return this;
490         }
491
492         public Builder addHttpListener(int port, String host, HttpHandler rootHandler) {
493             listeners.add(new ListenerConfig(ListenerType.HTTP, port, host, nullnull, 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, nullnull, 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         /**
558          * When null (the default), a new {@link XnioWorker} will be created according
559          * to the various worker-related configuration (ioThreads, workerThreads, workerOptions)
560          * when {@link Undertow#start()} is called.
561          * Additionally, this newly created worker will be shutdown when {@link Undertow#stop()} is called.
562          * <br>
563          * <p>
564          * When non-null, the provided {@link XnioWorker} will be reused instead of creating a new {@link XnioWorker}
565          * when {@link Undertow#start()} is called.
566          * Additionally, the provided {@link XnioWorker} will NOT be shutdown when {@link Undertow#stop()} is called.
567          * Essentially, the lifecycle of the provided worker must be maintained outside of the {@link Undertow} instance.
568          */

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                 //just ignore it if this is not a SSL listener
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             //the channel may be in the middle of an accept, we need to close from the IO thread
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