All Downloads are FREE. Search and download functionalities are using the official Maven repository.

reactor.netty.http.server.HttpServer Maven / Gradle / Ivy

There is a newer version: 1.1.22
Show newest version
/*
 * Copyright (c) 2011-2019 Pivotal Software Inc, All Rights Reserved.
 *
 * Licensed 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
 *
 *       https://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.
 */

package reactor.netty.http.server;

import java.time.Duration;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.netty.Connection;
import reactor.netty.ConnectionObserver;
import reactor.netty.DisposableServer;
import reactor.netty.NettyPipeline;
import reactor.netty.channel.BootstrapHandlers;
import reactor.netty.http.HttpProtocol;
import reactor.netty.tcp.SslProvider;
import reactor.netty.tcp.TcpServer;
import reactor.util.Logger;
import reactor.util.Loggers;

/**
 * An HttpServer allows to build in a safe immutable way an HTTP server that is
 * materialized and connecting when {@link #bind(TcpServer)} is ultimately called.
 * 

Internally, materialization happens in two phases:

*
    *
  • first {@link #tcpConfiguration()} is called to retrieve a ready to use {@link TcpServer}
  • *
  • then {@link #bind(TcpServer)} is called
  • *
*

Examples:

*
 * {@code
 * HttpServer.create()
 *           .host("0.0.0.0")
 *           .handle((req, res) -> res.sendString(Flux.just("hello")))
 *           .bind()
 *           .block();
 * }
 * 
* * @author Stephane Maldini */ public abstract class HttpServer { /** * Prepare an {@link HttpServer} * * @return a new {@link HttpServer} */ public static HttpServer create() { return HttpServerBind.INSTANCE; } /** * Prepare an {@link HttpServer} * * @return a new {@link HttpServer} */ public static HttpServer from(TcpServer tcpServer) { return new HttpServerBind(tcpServer); } /** * Bind the {@link HttpServer} and return a {@link Mono} of {@link DisposableServer}. If * {@link Mono} is cancelled, the underlying binding will be aborted. Once the {@link * DisposableServer} has been emitted and is not necessary anymore, disposing main server * loop must be done by the user via {@link DisposableServer#dispose()}. * * If update configuration phase fails, a {@link Mono#error(Throwable)} will be returned * * @return a {@link Mono} of {@link DisposableServer} */ public final Mono bind() { return bind(tcpConfiguration()); } /** * Start the server in a blocking fashion, and wait for it to finish initializing * or the startup timeout expires (the startup timeout is {@code 45} seconds). The * returned {@link DisposableServer} offers simple server API, including to {@link * DisposableServer#disposeNow()} shut it down in a blocking fashion. * * @return a {@link DisposableServer} */ public final DisposableServer bindNow() { return bindNow(Duration.ofSeconds(45)); } /** * Start the server in a blocking fashion, and wait for it to finish initializing * or the provided startup timeout expires. The returned {@link DisposableServer} * offers simple server API, including to {@link DisposableServer#disposeNow()} * shut it down in a blocking fashion. * * @param timeout max startup timeout * * @return a {@link DisposableServer} */ public final DisposableServer bindNow(Duration timeout) { Objects.requireNonNull(timeout, "timeout"); try { return Objects.requireNonNull(bind().block(timeout), "aborted"); } catch (IllegalStateException e) { if (e.getMessage().contains("blocking read")) { throw new IllegalStateException("HttpServer couldn't be started within " + timeout.toMillis() + "ms"); } throw e; } } /** * Start the server in a fully blocking fashion, not only waiting for it to initialize * but also blocking during the full lifecycle of the server. Since most * servers will be long-lived, this is more adapted to running a server out of a main * method, only allowing shutdown of the servers through {@code sigkill}. *

* Note: {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} is added by * this method in order to properly disconnect the server upon receiving a * {@code sigkill} signal. *

* * @param timeout a timeout for server shutdown * @param onStart an optional callback on server start */ public final void bindUntilJavaShutdown(Duration timeout, @Nullable Consumer onStart) { Objects.requireNonNull(timeout, "timeout"); DisposableServer facade = bindNow(); Objects.requireNonNull(facade, "facade"); if (onStart != null) { onStart.accept(facade); } Runtime.getRuntime() .addShutdownHook(new Thread(() -> facade.disposeNow(timeout))); facade.onDispose() .block(); } /** * Specifies whether GZip response compression/websocket compression * extension is enabled if the client request * presents accept encoding/websocket extensions headers. * * @param compressionEnabled if true GZip response compression/websocket compression * extension is enabled if the client request presents * accept encoding/websocket extensions headers, otherwise disabled. * @return a new {@link HttpServer} */ public final HttpServer compress(boolean compressionEnabled) { if (compressionEnabled) { return tcpConfiguration(COMPRESS_ATTR_CONFIG); } else { return tcpConfiguration(COMPRESS_ATTR_DISABLE); } } /** * Enable GZip response compression if the client request presents accept encoding * headers AND the response reaches a minimum threshold * * @param minResponseSize compression is performed once response size exceeds the given * value in bytes * * @return a new {@link HttpServer} */ public final HttpServer compress(int minResponseSize) { if (minResponseSize < 0) { throw new IllegalArgumentException("minResponseSize must be positive"); } return tcpConfiguration(tcp -> tcp.bootstrap(b -> HttpServerConfiguration.compressSize(b, minResponseSize))); } /** * Enable GZip response compression if the client request presents accept encoding * headers and the provided {@link java.util.function.Predicate} matches. *

* Note: the passed {@link HttpServerRequest} and {@link HttpServerResponse} * should be considered read-only and the implement SHOULD NOT consume or * write the request/response in this predicate. *

* * @param predicate that returns true to compress the response. * * @return a new {@link HttpServer} */ public final HttpServer compress(BiPredicate predicate) { Objects.requireNonNull(predicate, "compressionPredicate"); return tcpConfiguration(tcp -> tcp.bootstrap(b -> HttpServerConfiguration.compressPredicate(b, predicate))); } /** * Specifies whether support for the {@code "Forwarded"} and {@code "X-Forwarded-*"} * HTTP request headers for deriving information about the connection is enabled. * * @param forwardedEnabled if true support for the {@code "Forwarded"} and {@code "X-Forwarded-*"} * HTTP request headers for deriving information about the connection is enabled, * otherwise disabled. * @return a new {@link HttpServer} */ public final HttpServer forwarded(boolean forwardedEnabled) { if (forwardedEnabled) { return tcpConfiguration(FORWARD_ATTR_CONFIG); } else { return tcpConfiguration(FORWARD_ATTR_DISABLE); } } /** * Specifies whether support for the {@code "HAProxy proxy protocol"} * for deriving information about the address of the remote peer is enabled. * * @param proxyProtocolEnabled if true support for the {@code "HAProxy proxy protocol"} * for deriving information about the address of the remote peer is enabled, * otherwise disabled. * @return a new {@link HttpServer} */ public final HttpServer proxyProtocol(boolean proxyProtocolEnabled) { if (proxyProtocolEnabled) { if (!HAProxyMessageReader.hasProxyProtocol()) { throw new UnsupportedOperationException( "To enable proxyProtocol, you must add the dependency `io.netty:netty-codec-haproxy`" + " to the class path first"); } return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> BootstrapHandlers.updateConfiguration(b, NettyPipeline.ProxyProtocolDecoder, (connectionObserver, channel) -> { channel.pipeline() .addFirst(NettyPipeline.ProxyProtocolDecoder, new HAProxyMessageDecoder()); channel.pipeline() .addAfter(NettyPipeline.ProxyProtocolDecoder, NettyPipeline.ProxyProtocolReader, new HAProxyMessageReader()); }))); } else { return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> BootstrapHandlers.removeConfiguration(b, NettyPipeline.ProxyProtocolDecoder))); } } /** * The host to which this server should bind. * By default the server will listen on any local address. * * @param host The host to bind to. * * @return a new {@link HttpServer} */ public final HttpServer host(String host) { return tcpConfiguration(tcpServer -> tcpServer.host(host)); } /** * Attach an I/O handler to react on a connected client * * @param handler an I/O handler that can dispose underlying connection when {@link * Publisher} terminates. Only the first registered handler will subscribe to the * returned {@link Publisher} while other will immediately cancel given a same * {@link Connection} * * @return a new {@link HttpServer} */ public final HttpServer handle(BiFunction> handler) { return new HttpServerHandle(this, handler); } /** * Configure the {@link io.netty.handler.codec.http.HttpServerCodec}'s request decoding options. * * @param requestDecoderOptions a function to mutate the provided Http request decoder options * @return a new {@link HttpServer} */ public final HttpServer httpRequestDecoder(Function requestDecoderOptions) { return tcpConfiguration( requestDecoderOptions.apply(new HttpRequestDecoderSpec()) .build()); } /** * Configure the * {@link ServerCookieEncoder}; {@link ServerCookieDecoder} will be * chosen based on the encoder * * @param encoder the preferred ServerCookieEncoder * * @return a new {@link HttpServer} */ public final HttpServer cookieCodec(ServerCookieEncoder encoder) { ServerCookieDecoder decoder = encoder == ServerCookieEncoder.LAX ? ServerCookieDecoder.LAX : ServerCookieDecoder.STRICT; return tcpConfiguration(tcp -> tcp.bootstrap( b -> HttpServerConfiguration.cookieCodec(b, encoder, decoder))); } /** * Configure the * {@link ServerCookieEncoder} and {@link ServerCookieDecoder} * * @param encoder the preferred ServerCookieEncoder * @param decoder the preferred ServerCookieDecoder * * @return a new {@link HttpServer} */ public final HttpServer cookieCodec(ServerCookieEncoder encoder, ServerCookieDecoder decoder) { return tcpConfiguration(tcp -> tcp.bootstrap( b -> HttpServerConfiguration.cookieCodec(b, encoder, decoder))); } /** * Setup all lifecycle callbacks called on or after * each child {@link io.netty.channel.Channel} * has been connected and after it has been disconnected. * * @param observer a consumer observing state changes * * @return a new {@link HttpServer} */ public final HttpServer observe(ConnectionObserver observer) { return new HttpServerObserve(this, observer); } /** * The HTTP protocol to support. Default is {@link HttpProtocol#HTTP11}. * * @param supportedProtocols The various {@link HttpProtocol} this server will support * * @return a new {@link HttpServer} */ public final HttpServer protocol(HttpProtocol... supportedProtocols) { return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> HttpServerConfiguration.protocols(b, supportedProtocols))); } /** * The port to which this server should bind. * By default the system will pick up an ephemeral port in the {@link #bind()} operation: * * @param port The port to bind to. * * @return a new {@link HttpServer} */ public final HttpServer port(int port) { return tcpConfiguration(tcpServer -> tcpServer.port(port)); } /** * Apply an SSL configuration customization via the passed builder. The builder * will produce the {@link SslContext} to be passed to with a default value of * {@code 10} seconds handshake timeout unless the environment property {@code * reactor.netty.tcp.sslHandshakeTimeout} is set. * * If {@link SelfSignedCertificate} needs to be used, the sample below can be * used. Note that {@link SelfSignedCertificate} should not be used in production. *
	 * {@code
	 *     SelfSignedCertificate cert = new SelfSignedCertificate();
	 *     SslContextBuilder sslContextBuilder =
	 *             SslContextBuilder.forServer(cert.certificate(), cert.privateKey());
	 *     secure(sslContextSpec -> sslContextSpec.sslContext(sslContextBuilder));
	 * }
	 * 
* * @param sslProviderBuilder builder callback for further customization of {@link SslContext}. * * @return a new {@link HttpServer} */ public final HttpServer secure(Consumer sslProviderBuilder) { return new HttpServerSecure(this, sslProviderBuilder); } /** * Define routes for the server through the provided {@link HttpServerRoutes} builder. * * @param routesBuilder provides a route builder to be mutated in order to define routes. * @return a new {@link HttpServer} starting the router on subscribe */ public final HttpServer route(Consumer routesBuilder) { Objects.requireNonNull(routesBuilder, "routeBuilder"); HttpServerRoutes routes = HttpServerRoutes.newRoutes(); routesBuilder.accept(routes); return handle(routes); } /** * Apply {@link ServerBootstrap} configuration given mapper taking currently * configured one and returning a new one to be ultimately used for socket binding. *

Configuration will apply during {@link #tcpConfiguration()} phase.

* * @param tcpMapper A {@link TcpServer} mapping function to update tcp configuration and * return an enriched TCP server to use. * * @return a new {@link HttpServer} */ public final HttpServer tcpConfiguration(Function tcpMapper) { return new HttpServerTcpConfig(this, tcpMapper); } /** * Apply a wire logger configuration using {@link HttpServer} category * and {@code DEBUG} logger level * * @return a new {@link HttpServer} * @deprecated Use {@link HttpServer#wiretap(boolean)} */ @Deprecated public final HttpServer wiretap() { return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> BootstrapHandlers.updateLogSupport(b, LOGGING_HANDLER))); } /** * Apply or remove a wire logger configuration using {@link HttpServer} category * and {@code DEBUG} logger level * * @param enable Specifies whether the wire logger configuration will be added to * the pipeline * @return a new {@link HttpServer} */ public final HttpServer wiretap(boolean enable) { if (enable) { return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> BootstrapHandlers.updateLogSupport(b, LOGGING_HANDLER))); } else { return tcpConfiguration(tcpServer -> tcpServer.bootstrap(b -> BootstrapHandlers.removeConfiguration(b, NettyPipeline.LoggingHandler))); } } /** * Bind the {@link HttpServer} and return a {@link Mono} of {@link DisposableServer} * * @param b the {@link TcpServer} to bind * * @return a {@link Mono} of {@link DisposableServer} */ protected abstract Mono bind(TcpServer b); /** * Materialize a {@link TcpServer} from the parent {@link HttpServer} chain to use with * {@link #bind(TcpServer)} or separately * * @return a configured {@link TcpServer} */ protected TcpServer tcpConfiguration() { return DEFAULT_TCP_SERVER; } static final TcpServer DEFAULT_TCP_SERVER = TcpServer.create(); static final LoggingHandler LOGGING_HANDLER = new LoggingHandler(HttpServer.class); static final Logger log = Loggers.getLogger(HttpServer.class); static final Function COMPRESS_ATTR_CONFIG = tcp -> tcp.bootstrap(HttpServerConfiguration.MAP_COMPRESS); static final Function COMPRESS_ATTR_DISABLE = tcp -> tcp.bootstrap(HttpServerConfiguration.MAP_NO_COMPRESS); static final Function FORWARD_ATTR_CONFIG = tcp -> tcp.bootstrap(HttpServerConfiguration.MAP_FORWARDED); static final Function FORWARD_ATTR_DISABLE = tcp -> tcp.bootstrap(HttpServerConfiguration.MAP_NO_FORWARDED); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy