View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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  import org.apache.hc.core5.util.Timeout;
61  
62  /**
63   * HTTP/1.1 server side message exchange handler.
64   *
65   * @since 4.4
66   */
67  public class HttpServer implements ModalCloseable {
68  
69      enum Status { READY, ACTIVE, STOPPING }
70  
71      private final int port;
72      private final InetAddress ifAddress;
73      private final SocketConfig socketConfig;
74      private final ServerSocketFactory serverSocketFactory;
75      private final HttpService httpService;
76      private final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory;
77      private final Callback<SSLParameters> sslSetupHandler;
78      private final ExceptionListener exceptionListener;
79      private final ThreadPoolExecutor listenerExecutorService;
80      private final ThreadGroup workerThreads;
81      private final WorkerPoolExecutor workerExecutorService;
82      private final AtomicReference<Status> status;
83  
84      private volatile ServerSocket serverSocket;
85      private volatile RequestListener requestListener;
86  
87      @Internal
88      public HttpServer(
89              final int port,
90              final HttpService httpService,
91              final InetAddress ifAddress,
92              final SocketConfig socketConfig,
93              final ServerSocketFactory serverSocketFactory,
94              final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory,
95              final Callback<SSLParameters> sslSetupHandler,
96              final ExceptionListener exceptionListener) {
97          this.port = Args.notNegative(port, "Port value is negative");
98          this.httpService = Args.notNull(httpService, "HTTP service");
99          this.ifAddress = ifAddress;
100         this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
101         this.serverSocketFactory = serverSocketFactory != null ? serverSocketFactory : ServerSocketFactory.getDefault();
102         this.connectionFactory = connectionFactory != null ? connectionFactory : new DefaultBHttpServerConnectionFactory(
103                 this.serverSocketFactory instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
104                 Http1Config.DEFAULT,
105                 CharCodingConfig.DEFAULT);
106         this.sslSetupHandler = sslSetupHandler;
107         this.exceptionListener = exceptionListener != null ? exceptionListener : ExceptionListener.NO_OP;
108         this.listenerExecutorService = new ThreadPoolExecutor(
109                 1, 1, 0L, TimeUnit.MILLISECONDS,
110                 new SynchronousQueue<>(),
111                 new DefaultThreadFactory("HTTP-listener-" + this.port));
112         this.workerThreads = new ThreadGroup("HTTP-workers");
113         this.workerExecutorService = new WorkerPoolExecutor(
114                 0, Integer.MAX_VALUE, 1L, TimeUnit.SECONDS,
115                 new SynchronousQueue<>(),
116                 new DefaultThreadFactory("HTTP-worker", this.workerThreads, true));
117         this.status = new AtomicReference<>(Status.READY);
118     }
119 
120     public InetAddress getInetAddress() {
121         final ServerSocket localSocket = this.serverSocket;
122         if (localSocket != null) {
123             return localSocket.getInetAddress();
124         }
125         return null;
126     }
127 
128     public int getLocalPort() {
129         final ServerSocket localSocket = this.serverSocket;
130         if (localSocket != null) {
131             return localSocket.getLocalPort();
132         }
133         return -1;
134     }
135 
136     public void start() throws IOException {
137         if (this.status.compareAndSet(Status.READY, Status.ACTIVE)) {
138             this.serverSocket = this.serverSocketFactory.createServerSocket(
139                     this.port, this.socketConfig.getBacklogSize(), this.ifAddress);
140             this.serverSocket.setReuseAddress(this.socketConfig.isSoReuseAddress());
141             if (this.socketConfig.getRcvBufSize() > 0) {
142                 this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize());
143             }
144             if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) {
145                 final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket;
146                 final SSLParameters sslParameters = sslServerSocket.getSSLParameters();
147                 this.sslSetupHandler.execute(sslParameters);
148                 sslServerSocket.setSSLParameters(sslParameters);
149             }
150             this.requestListener = new RequestListener(
151                     this.socketConfig,
152                     this.serverSocket,
153                     this.httpService,
154                     this.connectionFactory,
155                     this.exceptionListener,
156                     this.workerExecutorService);
157             this.listenerExecutorService.execute(this.requestListener);
158         }
159     }
160 
161     public void stop() {
162         if (this.status.compareAndSet(Status.ACTIVE, Status.STOPPING)) {
163             this.listenerExecutorService.shutdownNow();
164             this.workerExecutorService.shutdown();
165             final RequestListener local = this.requestListener;
166             if (local != null) {
167                 try {
168                     local.terminate();
169                 } catch (final IOException ex) {
170                     this.exceptionListener.onError(ex);
171                 }
172             }
173             this.workerThreads.interrupt();
174         }
175     }
176 
177     public void initiateShutdown() {
178         stop();
179     }
180 
181     public void awaitTermination(final TimeValue waitTime) throws InterruptedException {
182         Args.notNull(waitTime, "Wait time");
183         this.workerExecutorService.awaitTermination(waitTime.getDuration(), waitTime.getTimeUnit());
184     }
185 
186     @Override
187     public void close(final CloseMode closeMode) {
188         close(closeMode, Timeout.ofSeconds(5));
189     }
190 
191     /**
192      * Closes this process or endpoint and releases any system resources associated
193      * with it. If the endpoint or the process is already closed then invoking this
194      * method has no effect.
195      *
196      * @param closeMode How to close the receiver.
197      * @param timeout  How long to wait for the HttpServer to close gracefully.
198      * @since 5.2
199      */
200     public void close(final CloseMode closeMode, final Timeout timeout) {
201         initiateShutdown();
202         if (closeMode == CloseMode.GRACEFUL) {
203             try {
204                 awaitTermination(timeout);
205             } catch (final InterruptedException ex) {
206                 Thread.currentThread().interrupt();
207             }
208         }
209         final Set<Worker> workers = this.workerExecutorService.getWorkers();
210         for (final Worker worker: workers) {
211             Closer.close(worker.getConnection(), CloseMode.GRACEFUL);
212         }
213     }
214 
215     @Override
216     public void close() {
217         close(CloseMode.GRACEFUL);
218     }
219 
220 }