blob: 9711772edaad6a2f36371b22a923a6de220200be [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.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.hc.core5.function.Callback;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.ExceptionListener;
import org.apache.hc.core5.http.HttpResponseFactory;
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.config.NamedElementChain;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.impl.Http1StreamListener;
import org.apache.hc.core5.http.impl.HttpProcessors;
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.DefaultClassicHttpResponseFactory;
import org.apache.hc.core5.http.impl.io.HttpService;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.HttpFilterHandler;
import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.HttpServerRequestHandler;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.ssl.DefaultTlsSetupHandler;
import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator;
import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler;
import org.apache.hc.core5.http.io.support.HttpServerExpectationFilter;
import org.apache.hc.core5.http.io.support.HttpServerFilterChainElement;
import org.apache.hc.core5.http.io.support.HttpServerFilterChainRequestHandler;
import org.apache.hc.core5.http.io.support.TerminalServerFilter;
import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.LookupRegistry;
import org.apache.hc.core5.http.protocol.RequestHandlerRegistry;
import org.apache.hc.core5.http.protocol.UriPatternType;
import org.apache.hc.core5.net.InetAddressUtils;
import org.apache.hc.core5.util.Args;
/**
* {@link HttpServer} bootstrap.
*
* @since 4.4
*/
public class ServerBootstrap {
private final List<HandlerEntry<HttpRequestHandler>> handlerList;
private final List<FilterEntry<HttpFilterHandler>> filters;
private String canonicalHostName;
private LookupRegistry<HttpRequestHandler> lookupRegistry;
private int listenerPort;
private InetAddress localAddress;
private SocketConfig socketConfig;
private Http1Config http1Config;
private CharCodingConfig charCodingConfig;
private HttpProcessor httpProcessor;
private ConnectionReuseStrategy connStrategy;
private HttpResponseFactory<ClassicHttpResponse> responseFactory;
private ServerSocketFactory serverSocketFactory;
private SSLContext sslContext;
private Callback<SSLParameters> sslSetupHandler;
private HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory;
private ExceptionListener exceptionListener;
private Http1StreamListener streamListener;
private ServerBootstrap() {
this.handlerList = new ArrayList<>();
this.filters = new ArrayList<>();
}
public static ServerBootstrap bootstrap() {
return new ServerBootstrap();
}
/**
* Sets canonical name (fully qualified domain name) of the server.
*
* @since 5.0
*/
public final ServerBootstrap setCanonicalHostName(final String canonicalHostName) {
this.canonicalHostName = canonicalHostName;
return this;
}
/**
* Sets listener port number.
*/
public final ServerBootstrap setListenerPort(final int listenerPort) {
this.listenerPort = listenerPort;
return this;
}
/**
* Assigns local interface for the listener.
*/
public final ServerBootstrap setLocalAddress(final InetAddress localAddress) {
this.localAddress = localAddress;
return this;
}
/**
* Sets socket configuration.
*/
public final ServerBootstrap setSocketConfig(final SocketConfig socketConfig) {
this.socketConfig = socketConfig;
return this;
}
/**
* Sets connection configuration.
*/
public final ServerBootstrap setHttp1Config(final Http1Config http1Config) {
this.http1Config = http1Config;
return this;
}
/**
* Sets connection configuration.
*/
public final ServerBootstrap setCharCodingConfig(final CharCodingConfig charCodingConfig) {
this.charCodingConfig = charCodingConfig;
return this;
}
/**
* Assigns {@link HttpProcessor} instance.
*/
public final ServerBootstrap setHttpProcessor(final HttpProcessor httpProcessor) {
this.httpProcessor = httpProcessor;
return this;
}
/**
* Assigns {@link ConnectionReuseStrategy} instance.
*/
public final ServerBootstrap setConnectionReuseStrategy(final ConnectionReuseStrategy connStrategy) {
this.connStrategy = connStrategy;
return this;
}
/**
* Assigns {@link HttpResponseFactory} instance.
*/
public final ServerBootstrap setResponseFactory(final HttpResponseFactory<ClassicHttpResponse> responseFactory) {
this.responseFactory = responseFactory;
return this;
}
/**
* Assigns {@link LookupRegistry} instance.
*/
public final ServerBootstrap setLookupRegistry(final LookupRegistry<HttpRequestHandler> lookupRegistry) {
this.lookupRegistry = lookupRegistry;
return this;
}
/**
* Registers the given {@link HttpRequestHandler} as a default handler for URIs
* matching the given pattern.
*
* @param uriPattern the pattern to register the handler for.
* @param requestHandler the handler.
*/
public final ServerBootstrap register(final String uriPattern, final HttpRequestHandler requestHandler) {
Args.notBlank(uriPattern, "URI pattern");
Args.notNull(requestHandler, "Supplier");
handlerList.add(new HandlerEntry<>(null, uriPattern, requestHandler));
return this;
}
/**
* Registers the given {@link HttpRequestHandler} as a handler for URIs
* matching the given host and the pattern.
*
* @param hostname
* @param uriPattern the pattern to register the handler for.
* @param requestHandler the handler.
*/
public final ServerBootstrap registerVirtual(final String hostname, final String uriPattern, final HttpRequestHandler requestHandler) {
Args.notBlank(hostname, "Hostname");
Args.notBlank(uriPattern, "URI pattern");
Args.notNull(requestHandler, "Supplier");
handlerList.add(new HandlerEntry<>(hostname, uriPattern, requestHandler));
return this;
}
/**
* Assigns {@link HttpConnectionFactory} instance.
*/
public final ServerBootstrap setConnectionFactory(
final HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactory) {
this.connectionFactory = connectionFactory;
return this;
}
/**
* Assigns {@link javax.net.ServerSocketFactory} instance.
*/
public final ServerBootstrap setServerSocketFactory(final ServerSocketFactory serverSocketFactory) {
this.serverSocketFactory = serverSocketFactory;
return this;
}
/**
* Assigns {@link javax.net.ssl.SSLContext} instance.
* <p>
* Please note this value can be overridden by the {@link #setServerSocketFactory(
* javax.net.ServerSocketFactory)} method.
*/
public final ServerBootstrap setSslContext(final SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
/**
* Assigns {@link Callback} for {@link SSLParameters}.
*/
public final ServerBootstrap setSslSetupHandler(final Callback<SSLParameters> sslSetupHandler) {
this.sslSetupHandler = sslSetupHandler;
return this;
}
/**
* Assigns {@link ExceptionListener} instance.
*/
public final ServerBootstrap setExceptionListener(final ExceptionListener exceptionListener) {
this.exceptionListener = exceptionListener;
return this;
}
/**
* Assigns {@link ExceptionListener} instance.
*/
public final ServerBootstrap setStreamListener(final Http1StreamListener streamListener) {
this.streamListener = streamListener;
return this;
}
/**
* Adds the filter before the filter with the given name.
*/
public final ServerBootstrap addFilterBefore(final String existing, final String name, final HttpFilterHandler filterHandler) {
Args.notBlank(existing, "Existing");
Args.notBlank(name, "Name");
Args.notNull(filterHandler, "Filter handler");
filters.add(new FilterEntry<>(FilterEntry.Postion.BEFORE, name, filterHandler, existing));
return this;
}
/**
* Adds the filter after the filter with the given name.
*/
public final ServerBootstrap addFilterAfter(final String existing, final String name, final HttpFilterHandler filterHandler) {
Args.notBlank(existing, "Existing");
Args.notBlank(name, "Name");
Args.notNull(filterHandler, "Filter handler");
filters.add(new FilterEntry<>(FilterEntry.Postion.AFTER, name, filterHandler, existing));
return this;
}
/**
* Replace an existing filter with the given name with new filter.
*/
public final ServerBootstrap replaceFilter(final String existing, final HttpFilterHandler filterHandler) {
Args.notBlank(existing, "Existing");
Args.notNull(filterHandler, "Filter handler");
filters.add(new FilterEntry<>(FilterEntry.Postion.REPLACE, existing, filterHandler, existing));
return this;
}
/**
* Add an filter to the head of the processing list.
*/
public final ServerBootstrap addFilterFirst(final String name, final HttpFilterHandler filterHandler) {
Args.notNull(name, "Name");
Args.notNull(filterHandler, "Filter handler");
filters.add(new FilterEntry<>(FilterEntry.Postion.FIRST, name, filterHandler, null));
return this;
}
/**
* Add an filter to the tail of the processing list.
*/
public final ServerBootstrap addFilterLast(final String name, final HttpFilterHandler filterHandler) {
Args.notNull(name, "Name");
Args.notNull(filterHandler, "Filter handler");
filters.add(new FilterEntry<>(FilterEntry.Postion.LAST, name, filterHandler, null));
return this;
}
public HttpServer create() {
final RequestHandlerRegistry<HttpRequestHandler> handlerRegistry = new RequestHandlerRegistry<>(
canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(),
() -> lookupRegistry != null ? lookupRegistry :
UriPatternType.<HttpRequestHandler>newMatcher(UriPatternType.URI_PATTERN));
for (final HandlerEntry<HttpRequestHandler> entry: handlerList) {
handlerRegistry.register(entry.hostname, entry.uriPattern, entry.handler);
}
final HttpServerRequestHandler requestHandler;
if (!filters.isEmpty()) {
final NamedElementChain<HttpFilterHandler> filterChainDefinition = new NamedElementChain<>();
filterChainDefinition.addLast(
new TerminalServerFilter(
handlerRegistry,
this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE),
StandardFilter.MAIN_HANDLER.name());
filterChainDefinition.addFirst(
new HttpServerExpectationFilter(),
StandardFilter.EXPECT_CONTINUE.name());
for (final FilterEntry<HttpFilterHandler> entry: filters) {
switch (entry.postion) {
case AFTER:
filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name);
break;
case BEFORE:
filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name);
break;
case REPLACE:
filterChainDefinition.replace(entry.existing, entry.filterHandler);
break;
case FIRST:
filterChainDefinition.addFirst(entry.filterHandler, entry.name);
break;
case LAST:
// Don't add last, after TerminalServerFilter, as that does not delegate to the chain
// Instead, add the filter just before it, making it effectively the last filter
filterChainDefinition.addBefore(StandardFilter.MAIN_HANDLER.name(), entry.filterHandler, entry.name);
break;
}
}
NamedElementChain<HttpFilterHandler>.Node current = filterChainDefinition.getLast();
HttpServerFilterChainElement filterChain = null;
while (current != null) {
filterChain = new HttpServerFilterChainElement(current.getValue(), filterChain);
current = current.getPrevious();
}
requestHandler = new HttpServerFilterChainRequestHandler(filterChain);
} else {
requestHandler = new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler(
handlerRegistry,
this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE));
}
final HttpService httpService = new HttpService(
this.httpProcessor != null ? this.httpProcessor : HttpProcessors.server(),
requestHandler,
this.connStrategy != null ? this.connStrategy : DefaultConnectionReuseStrategy.INSTANCE,
this.streamListener);
ServerSocketFactory serverSocketFactoryCopy = this.serverSocketFactory;
if (serverSocketFactoryCopy == null) {
if (this.sslContext != null) {
serverSocketFactoryCopy = this.sslContext.getServerSocketFactory();
} else {
serverSocketFactoryCopy = ServerSocketFactory.getDefault();
}
}
HttpConnectionFactory<? extends DefaultBHttpServerConnection> connectionFactoryCopy = this.connectionFactory;
if (connectionFactoryCopy == null) {
final String scheme = serverSocketFactoryCopy instanceof SSLServerSocketFactory ? URIScheme.HTTPS.id : URIScheme.HTTP.id;
connectionFactoryCopy = new DefaultBHttpServerConnectionFactory(scheme, this.http1Config, this.charCodingConfig);
}
return new HttpServer(
this.listenerPort > 0 ? this.listenerPort : 0,
httpService,
this.localAddress,
this.socketConfig != null ? this.socketConfig : SocketConfig.DEFAULT,
serverSocketFactoryCopy,
connectionFactoryCopy,
sslSetupHandler != null ? sslSetupHandler : new DefaultTlsSetupHandler(),
this.exceptionListener != null ? this.exceptionListener : ExceptionListener.NO_OP);
}
}