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

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

/*
 * Copyright (c) 2011-Present VMware, Inc. or its affiliates, 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.net.SocketAddress;
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 java.util.function.Supplier;

import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
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.channel.ChannelMetricsRecorder;
import reactor.netty.http.Http2SettingsSpec;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.server.logging.AccessLog;
import reactor.netty.http.server.logging.AccessLogArgProvider;
import reactor.netty.tcp.SslProvider;
import reactor.netty.tcp.TcpServer;
import reactor.netty.transport.ServerTransport;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.Metrics;

import static reactor.netty.ReactorNetty.format;

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

*

Examples: *

 * {@code
 * HttpServer.create()
 *           .host("0.0.0.0")
 *           .handle((req, res) -> res.sendString(Flux.just("hello")))
 *           .bind()
 *           .block();
 * }
 * 
* * @author Stephane Maldini * @author Violeta Georgieva */ public abstract class HttpServer extends ServerTransport { /** * 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} * @deprecated Use {@link HttpServer} methods for TCP level configurations. This method * will be removed in version 1.1.0. */ @Deprecated public static HttpServer from(TcpServer tcpServer) { Objects.requireNonNull(tcpServer, "tcpServer"); return HttpServerBind.applyTcpServerConfig(tcpServer.configuration()); } /** * Provide customized access log. *

* Example: *

	 * {@code
	 * HttpServer.create()
	 *           .port(8080)
	 *           .route(r -> r.get("/hello",
	 *                   (req, res) -> res.header(CONTENT_TYPE, TEXT_PLAIN)
	 *                                    .sendString(Mono.just("Hello World!"))))
	 *           .accessLog(argProvider ->
	 *                   AccessLog.create("user-agent={}", argProvider.requestHeader("user-agent")))
	 *           .bindNow()
	 *           .onDispose()
	 *           .block();
	 * }
	 * 
* * @param accessLog apply an {@link AccessLog} by an {@link AccessLogArgProvider} * @return a new {@link HttpServer} */ public final HttpServer accessLog(Function accessLog) { Objects.requireNonNull(accessLog, "accessLog"); HttpServer dup = duplicate(); dup.configuration().accessLog = accessLog; return dup; } @Override public final HttpServer bindAddress(Supplier bindAddressSupplier) { return super.bindAddress(bindAddressSupplier); } @Override public final HttpServer channelGroup(ChannelGroup channelGroup) { return super.channelGroup(channelGroup); } /** * 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"); HttpServer dup = duplicate(); dup.configuration().compressPredicate = predicate; return dup; } /** * Specifies whether GZip response compression is enabled if the client request * presents accept encoding. * * @param compressionEnabled if true GZip response compression * is enabled if the client request presents accept encoding, otherwise disabled. * @return a new {@link HttpServer} */ public final HttpServer compress(boolean compressionEnabled) { HttpServer dup = duplicate(); if (compressionEnabled) { dup.configuration().minCompressionSize = 0; } else { dup.configuration().minCompressionSize = -1; dup.configuration().compressPredicate = null; } return dup; } /** * 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"); } HttpServer dup = duplicate(); dup.configuration().minCompressionSize = minResponseSize; return dup; } /** * 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) { Objects.requireNonNull(encoder, "encoder"); ServerCookieDecoder decoder = encoder == ServerCookieEncoder.LAX ? ServerCookieDecoder.LAX : ServerCookieDecoder.STRICT; HttpServer dup = duplicate(); dup.configuration().cookieEncoder = encoder; dup.configuration().cookieDecoder = decoder; return dup; } /** * 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) { Objects.requireNonNull(encoder, "encoder"); Objects.requireNonNull(decoder, "decoder"); HttpServer dup = duplicate(); dup.configuration().cookieEncoder = encoder; dup.configuration().cookieDecoder = decoder; return dup; } /** * Specifies a custom request handler for deriving information about the connection. * * @param handler the forwarded header handler * @return a new {@link HttpServer} * @since 0.9.12 */ public final HttpServer forwarded(BiFunction handler) { Objects.requireNonNull(handler, "handler"); HttpServer dup = duplicate(); dup.configuration().forwardedHeaderHandler = handler; return dup; } /** * 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} * @since 0.9.7 */ public final HttpServer forwarded(boolean forwardedEnabled) { if (forwardedEnabled) { if (configuration().forwardedHeaderHandler == DefaultHttpForwardedHeaderHandler.INSTANCE) { return this; } HttpServer dup = duplicate(); dup.configuration().forwardedHeaderHandler = DefaultHttpForwardedHeaderHandler.INSTANCE; return dup; } else if (configuration().forwardedHeaderHandler != null) { HttpServer dup = duplicate(); dup.configuration().forwardedHeaderHandler = null; return dup; } return this; } /** * 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) { Objects.requireNonNull(handler, "handler"); return childObserve(new HttpServerHandle(handler)); } @Override public final HttpServer host(String host) { return super.host(host); } /** * Apply HTTP/2 configuration * * @param http2Settings configures {@link Http2SettingsSpec} before requesting * @return a new {@link HttpServer} */ public final HttpServer http2Settings(Consumer http2Settings) { Objects.requireNonNull(http2Settings, "http2Settings"); Http2SettingsSpec.Builder builder = Http2SettingsSpec.builder(); http2Settings.accept(builder); Http2SettingsSpec settings = builder.build(); if (settings.equals(configuration().http2Settings)) { return this; } HttpServer dup = duplicate(); dup.configuration().http2Settings = settings; return dup; } /** * 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) { Objects.requireNonNull(requestDecoderOptions, "requestDecoderOptions"); HttpRequestDecoderSpec decoder = requestDecoderOptions.apply(new HttpRequestDecoderSpec()).build(); if (decoder.equals(configuration().decoder)) { return this; } HttpServer dup = duplicate(); dup.configuration().decoder = decoder; return dup; } /** * Decorate the configured I/O handler. * See {@link #handle(BiFunction)}. * * @param mapHandle A {@link BiFunction} to decorate the configured I/O handler * @return a new {@link HttpServer} */ public final HttpServer mapHandle(BiFunction, ? super Connection, ? extends Mono> mapHandle) { Objects.requireNonNull(mapHandle, "mapHandle"); HttpServer dup = duplicate(); dup.configuration().mapHandle = mapHandle; return dup; } /** * Whether to enable metrics to be collected and registered in Micrometer's * {@link io.micrometer.core.instrument.Metrics#globalRegistry globalRegistry} * under the name {@link reactor.netty.Metrics#HTTP_SERVER_PREFIX}. *

{@code uriTagValue} function receives the actual uri and returns the uri tag value * that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag. * For example instead of using the actual uri {@code "/users/1"} as uri tag value, templated uri * {@code "/users/{id}"} can be used. *

Note: * It is strongly recommended applications to configure an upper limit for the number of the URI tags. * For example: *

	 * Metrics.globalRegistry
	 *        .config()
	 *        .meterFilter(MeterFilter.maximumAllowableTags(HTTP_SERVER_PREFIX, URI, 100, MeterFilter.deny()));
	 * 
*

By default metrics are not enabled. * * @param enable true enables metrics collection; false disables it * @param uriTagValue a function that receives the actual uri and returns the uri tag value * that will be used for the metrics with {@link reactor.netty.Metrics#URI} tag * @return a new {@link HttpServer} * @since 0.9.7 */ public final HttpServer metrics(boolean enable, Function uriTagValue) { if (enable) { if (!Metrics.isInstrumentationAvailable()) { throw new UnsupportedOperationException( "To enable metrics, you must add the dependency `io.micrometer:micrometer-core`" + " to the class path first"); } HttpServer dup = duplicate(); dup.configuration().metricsRecorder(() -> configuration().defaultMetricsRecorder()); dup.configuration().uriTagValue = uriTagValue; return dup; } else if (configuration().metricsRecorder() != null) { HttpServer dup = duplicate(); dup.configuration().metricsRecorder(null); dup.configuration().uriTagValue = null; return dup; } else { return this; } } @Override public final HttpServer metrics(boolean enable, Supplier recorder) { return super.metrics(enable, recorder); } /** * Specifies whether the metrics are enabled on the {@link HttpServer}. * All generated metrics are provided to the specified recorder which is only * instantiated if metrics are being enabled. *

{@code uriValue} function receives the actual uri and returns the uri value * that will be used when the metrics are propagated to the recorder. * For example instead of using the actual uri {@code "/users/1"} as uri value, templated uri * {@code "/users/{id}"} can be used. * * @param enable true enables metrics collection; false disables it * @param recorder a supplier for the metrics recorder that receives the collected metrics * @param uriValue a function that receives the actual uri and returns the uri value * that will be used when the metrics are propagated to the recorder. * @return a new {@link HttpServer} */ public final HttpServer metrics(boolean enable, Supplier recorder, Function uriValue) { if (enable) { HttpServer dup = duplicate(); dup.configuration().metricsRecorder(recorder); dup.configuration().uriTagValue = uriValue; return dup; } else if (configuration().metricsRecorder() != null) { HttpServer dup = duplicate(); dup.configuration().metricsRecorder(null); dup.configuration().uriTagValue = null; return dup; } else { return this; } } /** * Removes any previously applied SSL configuration customization * * @return a new {@link HttpServer} */ public final HttpServer noSSL() { if (configuration().isSecure()) { HttpServer dup = duplicate(); dup.configuration().sslProvider = null; return dup; } return this; } @Override public final HttpServer port(int port) { return super.port(port); } /** * 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) { Objects.requireNonNull(supportedProtocols, "supportedProtocols"); HttpServer dup = duplicate(); dup.configuration().protocols(supportedProtocols); return dup; } /** * Specifies whether support for the {@code "HAProxy proxy protocol"} * for deriving information about the address of the remote peer is enabled. * * @param proxyProtocolSupportType *

    *
  • * choose {@link ProxyProtocolSupportType#ON} * to enable support for the {@code "HAProxy proxy protocol"} * for deriving information about the address of the remote peer. *
  • *
  • choose {@link ProxyProtocolSupportType#OFF} to disable the proxy protocol support.
  • *
  • * choose {@link ProxyProtocolSupportType#AUTO} * then each connection of the same {@link HttpServer} will auto detect whether there is proxy protocol, * so {@link HttpServer} can accept requests with or without proxy protocol at the same time. *
  • *
* * @return a new {@link HttpServer} */ public final HttpServer proxyProtocol(ProxyProtocolSupportType proxyProtocolSupportType) { Objects.requireNonNull(proxyProtocolSupportType, "The parameter: proxyProtocolSupportType must not be null."); if (proxyProtocolSupportType == configuration().proxyProtocolSupportType) { return this; } if (proxyProtocolSupportType == ProxyProtocolSupportType.ON || proxyProtocolSupportType == ProxyProtocolSupportType.AUTO) { if (!HAProxyMessageReader.hasProxyProtocol()) { throw new UnsupportedOperationException( "To enable proxyProtocol, you must add the dependency `io.netty:netty-codec-haproxy`" + " to the class path first"); } } HttpServer dup = duplicate(); dup.configuration().proxyProtocolSupportType = proxyProtocolSupportType; return dup; } /** * 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 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 SslContext. * @return a new {@link HttpServer} */ public final HttpServer secure(Consumer sslProviderBuilder) { Objects.requireNonNull(sslProviderBuilder, "sslProviderBuilder"); HttpServer dup = duplicate(); SslProvider.SslContextSpec builder = SslProvider.builder(); sslProviderBuilder.accept(builder); dup.configuration().sslProvider = ((SslProvider.Builder) builder).build(); return dup; } /** * Applies an SSL configuration via the passed {@link SslProvider}. * * 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 sslProvider The provider to set when configuring SSL * * @return a new {@link HttpServer} */ public final HttpServer secure(SslProvider sslProvider) { Objects.requireNonNull(sslProvider, "sslProvider"); HttpServer dup = duplicate(); dup.configuration().sslProvider = sslProvider; return dup; } /** * Apply a {@link TcpServer} mapping function to update TCP configuration and * return an enriched {@link HttpServer} to use. * * @param tcpMapper A {@link TcpServer} mapping function to update TCP configuration and * return an enriched {@link HttpServer} to use. * @return a new {@link HttpServer} * @deprecated Use {@link HttpServer} methods for TCP level configurations. This method * will be removed in version 1.1.0. */ @Deprecated @SuppressWarnings("ReturnValueIgnored") public final HttpServer tcpConfiguration(Function tcpMapper) { Objects.requireNonNull(tcpMapper, "tcpMapper"); HttpServerTcpConfig tcpServer = new HttpServerTcpConfig(this); // ReturnValueIgnored is deliberate tcpMapper.apply(tcpServer); return tcpServer.httpServer; } @Override public final HttpServer wiretap(boolean enable) { return super.wiretap(enable); } static final Logger log = Loggers.getLogger(HttpServer.class); static final class HttpServerHandle implements ConnectionObserver { final BiFunction> handler; HttpServerHandle(BiFunction> handler) { this.handler = handler; } @Override @SuppressWarnings("FutureReturnValueIgnored") public void onStateChange(Connection connection, State newState) { if (newState == HttpServerState.REQUEST_RECEIVED) { try { if (log.isDebugEnabled()) { log.debug(format(connection.channel(), "Handler is being applied: {}"), handler); } HttpServerOperations ops = (HttpServerOperations) connection; Mono mono = Mono.fromDirect(handler.apply(ops, ops)); if (ops.mapHandle != null) { mono = ops.mapHandle.apply(mono, connection); } mono.subscribe(ops.disposeSubscriber()); } catch (Throwable t) { log.error(format(connection.channel(), ""), t); //"FutureReturnValueIgnored" this is deliberate connection.channel() .close(); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy