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