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