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  
61  /**
62   * HTTP/1.1 server side message exchange handler.
63   *
64   * @since 4.4
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 }