blob: 0e293b71ea5ede7e246b2b4c0fb06c4d4027af62 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.http.impl.bootstrap;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Set;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.concurrent.DefaultThreadFactory;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.http.ExceptionListener;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.CharCodingConfig;
import org.apache.hc.core5.http.config.Http1Config;
import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnection;
import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnectionFactory;
import org.apache.hc.core5.http.impl.io.HttpService;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.io.Closer;
import org.apache.hc.core5.io.ModalCloseable;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
/**
* HTTP/1.1 server side message exchange handler.
*
* @since 4.4
*/
public class HttpServer implements ModalCloseable {
enum Status { READY, ACTIVE, STOPPING }
private final int port;
private final InetAddress ifAddress;
private final SocketConfig socketConfig;
private final ServerSocketFactory serverSocketFactory;
private final HttpService httpService;
private final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory;
private final Callback<SSLParameters> sslSetupHandler;
private final ExceptionListener exceptionListener;
private final ThreadPoolExecutor listenerExecutorService;
private final ThreadGroup workerThreads;
private final WorkerPoolExecutor workerExecutorService;
private final AtomicReference<Status> status;
private volatile ServerSocket serverSocket;
private volatile RequestListener requestListener;
@Internal
public HttpServer(
final int port,
final HttpService httpService,
final InetAddress ifAddress,
final SocketConfig socketConfig,
final ServerSocketFactory serverSocketFactory,
final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory,
final Callback<SSLParameters> sslSetupHandler,
final ExceptionListener exceptionListener) {
this.port = Args.notNegative(port, "Port value is negative");
this.httpService = Args.notNull(httpService, "HTTP service");
this.ifAddress = ifAddress;
this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
this.serverSocketFactory = serverSocketFactory != null ? serverSocketFactory : ServerSocketFactory.getDefault();
this.connectionFactory = connectionFactory != null ? connectionFactory : new DefaultBHttpServerConnectionFactory(
this.serverSocketFactory instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
Http1Config.DEFAULT,
CharCodingConfig.DEFAULT);
this.sslSetupHandler = sslSetupHandler;
this.exceptionListener = exceptionListener != null ? exceptionListener : ExceptionListener.NO_OP;
this.listenerExecutorService = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new SynchronousQueue<>(),
new DefaultThreadFactory("HTTP-listener-" + this.port));
this.workerThreads = new ThreadGroup("HTTP-workers");
this.workerExecutorService = new WorkerPoolExecutor(
0, Integer.MAX_VALUE, 1L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new DefaultThreadFactory("HTTP-worker", this.workerThreads, true));
this.status = new AtomicReference<>(Status.READY);
}
public InetAddress getInetAddress() {
final ServerSocket localSocket = this.serverSocket;
if (localSocket != null) {
return localSocket.getInetAddress();
}
return null;
}
public int getLocalPort() {
final ServerSocket localSocket = this.serverSocket;
if (localSocket != null) {
return localSocket.getLocalPort();
}
return -1;
}
public void start() throws IOException {
if (this.status.compareAndSet(Status.READY, Status.ACTIVE)) {
this.serverSocket = this.serverSocketFactory.createServerSocket(
this.port, this.socketConfig.getBacklogSize(), this.ifAddress);
this.serverSocket.setReuseAddress(this.socketConfig.isSoReuseAddress());
if (this.socketConfig.getRcvBufSize() > 0) {
this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize());
}
if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) {
final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket;
final SSLParameters sslParameters = sslServerSocket.getSSLParameters();
this.sslSetupHandler.execute(sslParameters);
sslServerSocket.setSSLParameters(sslParameters);
}
this.requestListener = new RequestListener(
this.socketConfig,
this.serverSocket,
this.httpService,
this.connectionFactory,
this.exceptionListener,
this.workerExecutorService);
this.listenerExecutorService.execute(this.requestListener);
}
}
public void stop() {
if (this.status.compareAndSet(Status.ACTIVE, Status.STOPPING)) {
this.listenerExecutorService.shutdownNow();
this.workerExecutorService.shutdown();
final RequestListener local = this.requestListener;
if (local != null) {
try {
local.terminate();
} catch (final IOException ex) {
this.exceptionListener.onError(ex);
}
}
this.workerThreads.interrupt();
}
}
public void initiateShutdown() {
stop();
}
public void awaitTermination(final TimeValue waitTime) throws InterruptedException {
Args.notNull(waitTime, "Wait time");
this.workerExecutorService.awaitTermination(waitTime.getDuration(), waitTime.getTimeUnit());
}
@Override
public void close(final CloseMode closeMode) {
initiateShutdown();
if (closeMode == CloseMode.GRACEFUL) {
try {
awaitTermination(TimeValue.ofSeconds(5));
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
final Set<Worker> workers = this.workerExecutorService.getWorkers();
for (final Worker worker: workers) {
Closer.close(worker.getConnection(), CloseMode.GRACEFUL);
}
}
@Override
public void close() {
close(CloseMode.GRACEFUL);
}
}