1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.core5.http.impl.bootstrap;
28
29 import java.io.IOException;
30 import java.net.InetAddress;
31 import java.net.ServerSocket;
32 import java.util.Set;
33 import java.util.concurrent.SynchronousQueue;
34 import java.util.concurrent.ThreadPoolExecutor;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.atomic.AtomicReference;
37
38 import javax.net.ServerSocketFactory;
39 import javax.net.ssl.SSLParameters;
40 import javax.net.ssl.SSLServerSocket;
41 import javax.net.ssl.SSLServerSocketFactory;
42
43 import org.apache.hc.core5.annotation.Internal;
44 import org.apache.hc.core5.concurrent.DefaultThreadFactory;
45 import org.apache.hc.core5.function.Callback;
46 import org.apache.hc.core5.http.ExceptionListener;
47 import org.apache.hc.core5.http.URIScheme;
48 import org.apache.hc.core5.http.config.CharCodingConfig;
49 import org.apache.hc.core5.http.config.Http1Config;
50 import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection;
51 import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnectionFactory;
52 import org.apache.hc.core5.http.impl.io.HttpService;
53 import org.apache.hc.core5.http.io.HttpConnectionFactory;
54 import org.apache.hc.core5.http.io.SocketConfig;
55 import org.apache.hc.core5.io.CloseMode;
56 import org.apache.hc.core5.io.Closer;
57 import org.apache.hc.core5.io.ModalCloseable;
58 import org.apache.hc.core5.util.Args;
59 import org.apache.hc.core5.util.TimeValue;
60
61
62
63
64
65
66 public class HttpServer implements ModalCloseable {
67
68 enum Status { READY, ACTIVE, STOPPING }
69
70 private final int port;
71 private final InetAddress ifAddress;
72 private final SocketConfig socketConfig;
73 private final ServerSocketFactory serverSocketFactory;
74 private final HttpService httpService;
75 private final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory;
76 private final Callback<SSLParameters> sslSetupHandler;
77 private final ExceptionListener exceptionListener;
78 private final ThreadPoolExecutor listenerExecutorService;
79 private final ThreadGroup workerThreads;
80 private final WorkerPoolExecutor workerExecutorService;
81 private final AtomicReference<Status> status;
82
83 private volatile ServerSocket serverSocket;
84 private volatile RequestListener requestListener;
85
86 @Internal
87 public HttpServer(
88 final int port,
89 final HttpService httpService,
90 final InetAddress ifAddress,
91 final SocketConfig socketConfig,
92 final ServerSocketFactory serverSocketFactory,
93 final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory,
94 final Callback<SSLParameters> sslSetupHandler,
95 final ExceptionListener exceptionListener) {
96 this.port = Args.notNegative(port, "Port value is negative");
97 this.httpService = Args.notNull(httpService, "HTTP service");
98 this.ifAddress = ifAddress;
99 this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
100 this.serverSocketFactory = serverSocketFactory != null ? serverSocketFactory : ServerSocketFactory.getDefault();
101 this.connectionFactory = connectionFactory != null ? connectionFactory : new DefaultBHttpServerConnectionFactory(
102 this.serverSocketFactory instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
103 Http1Config.DEFAULT,
104 CharCodingConfig.DEFAULT);
105 this.sslSetupHandler = sslSetupHandler;
106 this.exceptionListener = exceptionListener != null ? exceptionListener : ExceptionListener.NO_OP;
107 this.listenerExecutorService = new ThreadPoolExecutor(
108 1, 1, 0L, TimeUnit.MILLISECONDS,
109 new SynchronousQueue<Runnable>(),
110 new DefaultThreadFactory("HTTP-listener-" + this.port));
111 this.workerThreads = new ThreadGroup("HTTP-workers");
112 this.workerExecutorService = new WorkerPoolExecutor(
113 0, Integer.MAX_VALUE, 1L, TimeUnit.SECONDS,
114 new SynchronousQueue<Runnable>(),
115 new DefaultThreadFactory("HTTP-worker", this.workerThreads, true));
116 this.status = new AtomicReference<>(Status.READY);
117 }
118
119 public InetAddress getInetAddress() {
120 final ServerSocket localSocket = this.serverSocket;
121 if (localSocket != null) {
122 return localSocket.getInetAddress();
123 }
124 return null;
125 }
126
127 public int getLocalPort() {
128 final ServerSocket localSocket = this.serverSocket;
129 if (localSocket != null) {
130 return localSocket.getLocalPort();
131 }
132 return -1;
133 }
134
135 public void start() throws IOException {
136 if (this.status.compareAndSet(Status.READY, Status.ACTIVE)) {
137 this.serverSocket = this.serverSocketFactory.createServerSocket(
138 this.port, this.socketConfig.getBacklogSize(), this.ifAddress);
139 this.serverSocket.setReuseAddress(this.socketConfig.isSoReuseAddress());
140 if (this.socketConfig.getRcvBufSize() > 0) {
141 this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize());
142 }
143 if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) {
144 final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket;
145 final SSLParameters sslParameters = sslServerSocket.getSSLParameters();
146 this.sslSetupHandler.execute(sslParameters);
147 sslServerSocket.setSSLParameters(sslParameters);
148 }
149 this.requestListener = new RequestListener(
150 this.socketConfig,
151 this.serverSocket,
152 this.httpService,
153 this.connectionFactory,
154 this.exceptionListener,
155 this.workerExecutorService);
156 this.listenerExecutorService.execute(this.requestListener);
157 }
158 }
159
160 public void stop() {
161 if (this.status.compareAndSet(Status.ACTIVE, Status.STOPPING)) {
162 this.listenerExecutorService.shutdownNow();
163 this.workerExecutorService.shutdown();
164 final RequestListener local = this.requestListener;
165 if (local != null) {
166 try {
167 local.terminate();
168 } catch (final IOException ex) {
169 this.exceptionListener.onError(ex);
170 }
171 }
172 this.workerThreads.interrupt();
173 }
174 }
175
176 public void initiateShutdown() {
177 stop();
178 }
179
180 public void awaitTermination(final TimeValue waitTime) throws InterruptedException {
181 Args.notNull(waitTime, "Wait time");
182 this.workerExecutorService.awaitTermination(waitTime.getDuration(), waitTime.getTimeUnit());
183 }
184
185 @Override
186 public void close(final CloseMode closeMode) {
187 initiateShutdown();
188 if (closeMode == CloseMode.GRACEFUL) {
189 try {
190 awaitTermination(TimeValue.ofSeconds(5));
191 } catch (final InterruptedException ex) {
192 Thread.currentThread().interrupt();
193 }
194 }
195 final Set<Worker> workers = this.workerExecutorService.getWorkers();
196 for (final Worker worker: workers) {
197 Closer.close(worker.getConnection(), CloseMode.GRACEFUL);
198 }
199 }
200
201 @Override
202 public void close() {
203 close(CloseMode.GRACEFUL);
204 }
205
206 }