Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.quarkus.vertx.http.runtime.VertxHttpRecorder Maven / Gradle / Ivy
package io.quarkus.vertx.http.runtime;
import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.RANDOM_PORT_MAIN_HTTP;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.RANDOM_PORT_MANAGEMENT;
import static io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils.getInsecureRequestStrategy;
import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.spi.CDI;
import org.crac.Resource;
import org.jboss.logging.Logger;
import org.wildfly.common.cpu.ProcessorInfo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.bootstrap.runner.Timing;
import io.quarkus.dev.spi.DevModeType;
import io.quarkus.dev.spi.HotReplacementContext;
import io.quarkus.netty.runtime.virtual.VirtualAddress;
import io.quarkus.netty.runtime.virtual.VirtualChannel;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.QuarkusBindException;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.ThreadPoolConfig;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigInstantiator;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.runtime.configuration.MemorySize;
import io.quarkus.runtime.logging.LogBuildTimeConfig;
import io.quarkus.runtime.shutdown.ShutdownConfig;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.quarkus.tls.runtime.config.TlsConfig;
import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
import io.quarkus.vertx.core.runtime.config.VertxConfiguration;
import io.quarkus.vertx.http.DomainSocketServerStart;
import io.quarkus.vertx.http.HttpServerOptionsCustomizer;
import io.quarkus.vertx.http.HttpServerStart;
import io.quarkus.vertx.http.HttpsServerStart;
import io.quarkus.vertx.http.ManagementInterface;
import io.quarkus.vertx.http.runtime.HttpConfiguration.InsecureRequests;
import io.quarkus.vertx.http.runtime.devmode.RemoteSyncHandler;
import io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup;
import io.quarkus.vertx.http.runtime.filters.Filter;
import io.quarkus.vertx.http.runtime.filters.Filters;
import io.quarkus.vertx.http.runtime.filters.GracefulShutdownFilter;
import io.quarkus.vertx.http.runtime.filters.QuarkusRequestWrapper;
import io.quarkus.vertx.http.runtime.filters.accesslog.AccessLogHandler;
import io.quarkus.vertx.http.runtime.filters.accesslog.AccessLogReceiver;
import io.quarkus.vertx.http.runtime.filters.accesslog.DefaultAccessLogReceiver;
import io.quarkus.vertx.http.runtime.filters.accesslog.JBossLoggingAccessLogReceiver;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceConfiguration;
import io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers;
import io.quarkus.vertx.http.runtime.options.HttpServerOptionsUtils;
import io.quarkus.vertx.http.runtime.options.TlsCertificateReloader;
import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
import io.vertx.core.Closeable;
import io.vertx.core.Context;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Verticle;
import io.vertx.core.Vertx;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.CookieSameSite;
import io.vertx.core.http.HttpConnection;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.impl.Http1xServerConnection;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.Utils;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.impl.ConnectionBase;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
@Recorder
public class VertxHttpRecorder {
/**
* The key that the request start time is stored under
*/
public static final String REQUEST_START_TIME = "io.quarkus.request-start-time";
public static final String MAX_REQUEST_SIZE_KEY = "io.quarkus.max-request-size";
private static final String DISABLE_WEBSOCKETS_PROP_NAME = "vertx.disableWebsockets";
private static final Logger LOGGER = Logger.getLogger(VertxHttpRecorder.class.getName());
private static volatile Handler hotReplacementHandler;
private static volatile HotReplacementContext hotReplacementContext;
private static volatile RemoteSyncHandler remoteSyncHandler;
private static volatile Runnable closeTask;
static volatile Handler rootHandler;
private static volatile Handler nonApplicationRedirectHandler;
private static volatile int actualHttpPort = -1;
private static volatile int actualHttpsPort = -1;
private static volatile int actualManagementPort = -1;
public static final String GET = "GET";
private static final Handler ACTUAL_ROOT = new Handler() {
/** JVM system property that disables URI validation, don't use this in production. */
private static final String DISABLE_URI_VALIDATION_PROP_NAME = "vertx.disableURIValidation";
/**
* Disables HTTP headers validation, so we can save some processing and save some allocations.
*/
private final boolean DISABLE_URI_VALIDATION = Boolean.getBoolean(DISABLE_URI_VALIDATION_PROP_NAME);
@Override
public void handle(HttpServerRequest httpServerRequest) {
if (!uriValid(httpServerRequest)) {
httpServerRequest.response().setStatusCode(400).end();
return;
}
//we need to pause the request to make sure that data does
//not arrive before handlers have a chance to install a read handler
//as it is possible filters such as the auth filter can do blocking tasks
//as the underlying handler has not had a chance to install a read handler yet
//and data that arrives while the blocking task is being processed will be lost
httpServerRequest.pause();
Handler rh = VertxHttpRecorder.rootHandler;
if (rh != null) {
rh.handle(httpServerRequest);
} else {
//very rare race condition, that can happen when dev mode is shutting down
httpServerRequest.resume();
httpServerRequest.response().setStatusCode(503).end();
}
}
private boolean uriValid(HttpServerRequest httpServerRequest) {
if (DISABLE_URI_VALIDATION) {
return true;
}
try {
// we simply need to know if the URI is valid
new URI(httpServerRequest.uri());
return true;
} catch (URISyntaxException e) {
return false;
}
}
};
private static HttpServerOptions httpMainSslServerOptions;
private static HttpServerOptions httpMainServerOptions;
private static HttpServerOptions httpMainDomainSocketOptions;
private static HttpServerOptions httpManagementServerOptions;
private static final List refresTaskIds = new CopyOnWriteArrayList<>();
final HttpBuildTimeConfig httpBuildTimeConfig;
final ManagementInterfaceBuildTimeConfig managementBuildTimeConfig;
final RuntimeValue httpConfiguration;
final RuntimeValue managementConfiguration;
private static volatile Handler managementRouter;
private static volatile Handler managementRouterDelegate;
public VertxHttpRecorder(HttpBuildTimeConfig httpBuildTimeConfig,
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig,
RuntimeValue httpConfiguration,
RuntimeValue managementConfiguration) {
this.httpBuildTimeConfig = httpBuildTimeConfig;
this.httpConfiguration = httpConfiguration;
this.managementBuildTimeConfig = managementBuildTimeConfig;
this.managementConfiguration = managementConfiguration;
}
public static void setHotReplacement(Handler handler, HotReplacementContext hrc) {
hotReplacementHandler = handler;
hotReplacementContext = hrc;
}
public static void shutDownDevMode() {
if (closeTask != null) {
closeTask.run();
closeTask = null;
}
rootHandler = null;
hotReplacementHandler = null;
hotReplacementContext = null;
}
public static void startServerAfterFailedStart() {
if (closeTask != null) {
//it is possible start failed after the server was started
//we shut it down in this case, as we have no idea what state it is in
final Handler prevHotReplacementHandler = hotReplacementHandler;
shutDownDevMode();
// reset back to the older live reload handler, so that it can be used
// to watch any artifacts that need hot deployment to fix the reason which caused
// the server start to fail
hotReplacementHandler = prevHotReplacementHandler;
}
Supplier supplier = VertxCoreRecorder.getVertx();
Vertx vertx;
if (supplier == null) {
VertxConfiguration vertxConfiguration = ConfigUtils.emptyConfigBuilder()
.addDiscoveredSources()
.withMapping(VertxConfiguration.class)
.build().getConfigMapping(VertxConfiguration.class);
ThreadPoolConfig threadPoolConfig = new ThreadPoolConfig();
ConfigInstantiator.handleObject(threadPoolConfig);
vertx = VertxCoreRecorder.recoverFailedStart(vertxConfiguration, threadPoolConfig).get();
} else {
vertx = supplier.get();
}
try {
HttpBuildTimeConfig buildConfig = new HttpBuildTimeConfig();
ConfigInstantiator.handleObject(buildConfig);
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig = new ManagementInterfaceBuildTimeConfig();
ConfigInstantiator.handleObject(managementBuildTimeConfig);
HttpConfiguration config = new HttpConfiguration();
ConfigInstantiator.handleObject(config);
ManagementInterfaceConfiguration managementConfig = new ManagementInterfaceConfiguration();
ConfigInstantiator.handleObject(managementConfig);
if (config.host == null) {
//HttpHostConfigSource does not come into play here
config.host = "localhost";
}
Router router = Router.router(vertx);
if (hotReplacementHandler != null) {
router.route().order(RouteConstants.ROUTE_ORDER_HOT_REPLACEMENT).blockingHandler(hotReplacementHandler);
}
Handler root = router;
LiveReloadConfig liveReloadConfig = new LiveReloadConfig();
ConfigInstantiator.handleObject(liveReloadConfig);
if (liveReloadConfig.password.isPresent()
&& hotReplacementContext.getDevModeType() == DevModeType.REMOTE_SERVER_SIDE) {
root = remoteSyncHandler = new RemoteSyncHandler(liveReloadConfig.password.get(), root, hotReplacementContext);
}
rootHandler = root;
var insecureRequestStrategy = getInsecureRequestStrategy(buildConfig, config.insecureRequests);
//we can't really do
doServerStart(vertx, buildConfig, managementBuildTimeConfig, null, config, managementConfig, LaunchMode.DEVELOPMENT,
new Supplier() {
@Override
public Integer get() {
return ProcessorInfo.availableProcessors(); //this is dev mode, so the number of IO threads not always being 100% correct does not really matter in this case
}
}, null, insecureRequestStrategy, false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RuntimeValue initializeRouter(final Supplier vertxRuntimeValue) {
Vertx vertx = vertxRuntimeValue.get();
Router router = Router.router(vertx);
return new RuntimeValue<>(router);
}
public RuntimeValue createMutinyRouter(final RuntimeValue router) {
return new RuntimeValue<>(new io.vertx.mutiny.ext.web.Router(router.getValue()));
}
public RuntimeValue> createAccessLogPublisher() {
return new RuntimeValue<>(new SubmissionPublisher<>());
}
public void startServer(Supplier vertx, ShutdownContext shutdown,
LaunchMode launchMode,
boolean startVirtual, boolean startSocket, Supplier ioThreads, List websocketSubProtocols,
boolean auxiliaryApplication, boolean disableWebSockets)
throws IOException {
// disable websockets if we have determined at build time that we should and the user has not overridden the relevant Vert.x property
if (disableWebSockets && !System.getProperties().containsKey(DISABLE_WEBSOCKETS_PROP_NAME)) {
System.setProperty(DISABLE_WEBSOCKETS_PROP_NAME, "true");
}
if (startVirtual) {
initializeVirtual(vertx.get());
shutdown.addShutdownTask(() -> {
try {
virtualBootstrapChannel.channel().close().sync();
} catch (InterruptedException e) {
LOGGER.warn("Unable to close virtualBootstrapChannel");
} finally {
virtualBootstrapChannel = null;
virtualBootstrap = null;
}
});
}
HttpConfiguration httpConfiguration = this.httpConfiguration.getValue();
ManagementInterfaceConfiguration managementConfig = this.managementConfiguration == null ? null
: this.managementConfiguration.getValue();
if (startSocket && (httpConfiguration.hostEnabled || httpConfiguration.domainSocketEnabled
|| (managementConfig != null && managementConfig.hostEnabled)
|| (managementConfig != null && managementConfig.domainSocketEnabled))) {
// Start the server
if (closeTask == null) {
var insecureRequestStrategy = getInsecureRequestStrategy(httpBuildTimeConfig,
httpConfiguration.insecureRequests);
doServerStart(vertx.get(), httpBuildTimeConfig, managementBuildTimeConfig, managementRouter,
httpConfiguration, managementConfig, launchMode, ioThreads, websocketSubProtocols,
insecureRequestStrategy,
auxiliaryApplication);
if (launchMode != LaunchMode.DEVELOPMENT) {
shutdown.addShutdownTask(closeTask);
} else {
shutdown.addShutdownTask(new Runnable() {
@Override
public void run() {
VertxHttpHotReplacementSetup.handleDevModeRestart();
}
});
}
}
}
}
public void mountFrameworkRouter(RuntimeValue mainRouter, RuntimeValue frameworkRouter,
String frameworkPath) {
mainRouter.getValue().mountSubRouter(frameworkPath, frameworkRouter.getValue());
}
public void finalizeRouter(BeanContainer container, Consumer defaultRouteHandler,
List filterList, List managementInterfaceFilterList, Supplier vertx,
LiveReloadConfig liveReloadConfig, Optional> mainRouterRuntimeValue,
RuntimeValue httpRouterRuntimeValue, RuntimeValue mutinyRouter,
RuntimeValue frameworkRouter, RuntimeValue managementRouter,
String rootPath, String nonRootPath,
LaunchMode launchMode, BooleanSupplier[] requireBodyHandlerConditions,
Handler bodyHandler,
GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig,
Executor executor,
LogBuildTimeConfig logBuildTimeConfig,
String srcMainJava,
List knowClasses,
List actions,
Optional>> publisher) {
HttpConfiguration httpConfiguration = this.httpConfiguration.getValue();
// install the default route at the end
Router httpRouteRouter = httpRouterRuntimeValue.getValue();
//allow the router to be modified programmatically
Event event = Arc.container().beanManager().getEvent();
// First, fire an event with the filter collector
Filters filters = new Filters();
event.select(Filters.class).fire(filters);
filterList.addAll(filters.getFilters());
// Then, fire the resuming router
event.select(Router.class, Default.Literal.INSTANCE).fire(httpRouteRouter);
// Also fires the Mutiny one
event.select(io.vertx.mutiny.ext.web.Router.class).fire(mutinyRouter.getValue());
for (Filter filter : filterList) {
if (filter.getHandler() != null) {
if (filter.isFailureHandler()) {
// Filters handling failures with high priority gets called first.
httpRouteRouter.route().order(-1 * filter.getPriority()).failureHandler(filter.getHandler());
} else {
// Filters handling HTTP requests with high priority gets called first.
httpRouteRouter.route().order(-1 * filter.getPriority()).handler(filter.getHandler());
}
}
}
if (defaultRouteHandler != null) {
defaultRouteHandler.accept(httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_DEFAULT));
}
applyCompression(httpBuildTimeConfig.enableCompression, httpRouteRouter);
httpRouteRouter.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, logBuildTimeConfig),
httpConfiguration.unhandledErrorContentTypeDefault, srcMainJava, knowClasses, actions));
for (BooleanSupplier requireBodyHandlerCondition : requireBodyHandlerConditions) {
if (requireBodyHandlerCondition.getAsBoolean()) {
//if this is set then everything needs the body handler installed
//TODO: config etc
httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER).handler(new Handler() {
@Override
public void handle(RoutingContext routingContext) {
routingContext.request().resume();
bodyHandler.handle(routingContext);
}
});
break;
}
}
HttpServerCommonHandlers.enforceMaxBodySize(httpConfiguration.limits, httpRouteRouter);
// Filter Configuration per path
var filtersInConfig = httpConfiguration.filter;
HttpServerCommonHandlers.applyFilters(filtersInConfig, httpRouteRouter);
// Headers sent on any request, regardless of the response
HttpServerCommonHandlers.applyHeaders(httpConfiguration.header, httpRouteRouter);
Handler root;
if (rootPath.equals("/")) {
addHotReplacementHandlerIfNeeded(httpRouteRouter);
root = httpRouteRouter;
} else {
Router mainRouter = mainRouterRuntimeValue.isPresent() ? mainRouterRuntimeValue.get().getValue()
: Router.router(vertx.get());
mainRouter.mountSubRouter(rootPath, httpRouteRouter);
addHotReplacementHandlerIfNeeded(mainRouter);
root = mainRouter;
}
warnIfProxyAddressForwardingAllowedWithMultipleHeaders(httpConfiguration.proxy);
root = HttpServerCommonHandlers.applyProxy(httpConfiguration.proxy, root, vertx);
boolean quarkusWrapperNeeded = false;
if (shutdownConfig.isShutdownTimeoutSet()) {
gracefulShutdownFilter.next(root);
root = gracefulShutdownFilter;
quarkusWrapperNeeded = true;
}
AccessLogConfig accessLog = httpConfiguration.accessLog;
if (accessLog.enabled) {
AccessLogReceiver receiver;
if (accessLog.logToFile) {
File outputDir = accessLog.logDirectory.isPresent() ? new File(accessLog.logDirectory.get()) : new File("");
receiver = new DefaultAccessLogReceiver(executor, outputDir, accessLog.baseFileName, accessLog.logSuffix,
accessLog.rotate);
} else {
receiver = new JBossLoggingAccessLogReceiver(accessLog.category);
}
setupAccessLogHandler(mainRouterRuntimeValue, httpRouterRuntimeValue, frameworkRouter, receiver, rootPath,
nonRootPath, accessLog.pattern, accessLog.consolidateReroutedRequests, accessLog.excludePattern);
quarkusWrapperNeeded = true;
}
// Add an access log for Dev UI
if (publisher.isPresent()) {
SubmissionPublisher logPublisher = publisher.get().getValue();
AccessLogReceiver receiver = new AccessLogReceiver() {
@Override
public void logMessage(String message) {
logPublisher.submit(message);
}
};
setupAccessLogHandler(mainRouterRuntimeValue, httpRouterRuntimeValue, frameworkRouter, receiver, rootPath,
nonRootPath, accessLog.pattern, accessLog.consolidateReroutedRequests,
accessLog.excludePattern.or(() -> Optional.of("^" + nonRootPath + ".*")));
quarkusWrapperNeeded = true;
}
BiConsumer cookieFunction = null;
if (!httpConfiguration.sameSiteCookie.isEmpty()) {
cookieFunction = processSameSiteConfig(httpConfiguration.sameSiteCookie);
quarkusWrapperNeeded = true;
}
BiConsumer cookieConsumer = cookieFunction;
if (quarkusWrapperNeeded) {
Handler old = root;
root = new Handler() {
@Override
public void handle(HttpServerRequest event) {
old.handle(new QuarkusRequestWrapper(event, cookieConsumer));
}
};
}
Handler delegate = root;
root = HttpServerCommonHandlers.enforceDuplicatedContext(delegate);
if (httpConfiguration.recordRequestStartTime) {
httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_RECORD_START_TIME).handler(new Handler() {
@Override
public void handle(RoutingContext event) {
event.put(REQUEST_START_TIME, System.nanoTime());
event.next();
}
});
}
if (launchMode == LaunchMode.DEVELOPMENT && liveReloadConfig.password.isPresent()
&& hotReplacementContext.getDevModeType() == DevModeType.REMOTE_SERVER_SIDE) {
root = remoteSyncHandler = new RemoteSyncHandler(liveReloadConfig.password.get(), root, hotReplacementContext);
}
rootHandler = root;
if (managementRouter != null && managementRouter.getValue() != null) {
// Add body handler and cors handler
var mr = managementRouter.getValue();
boolean hasManagementRoutes = !mr.getRoutes().isEmpty();
addHotReplacementHandlerIfNeeded(mr);
mr.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, logBuildTimeConfig),
httpConfiguration.unhandledErrorContentTypeDefault, srcMainJava, knowClasses, actions));
mr.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER_MANAGEMENT)
.handler(createBodyHandlerForManagementInterface());
// We can use "*" here as the management interface is not expected to be used publicly.
mr.route().order(RouteConstants.ROUTE_ORDER_CORS_MANAGEMENT).handler(CorsHandler.create().addOrigin("*"));
HttpServerCommonHandlers.applyFilters(managementConfiguration.getValue().filter, mr);
for (Filter filter : managementInterfaceFilterList) {
mr.route().order(filter.getPriority()).handler(filter.getHandler());
}
HttpServerCommonHandlers.applyHeaders(managementConfiguration.getValue().header, mr);
applyCompression(managementBuildTimeConfig.enableCompression, mr);
Handler handler = HttpServerCommonHandlers.enforceDuplicatedContext(mr);
handler = HttpServerCommonHandlers.applyProxy(managementConfiguration.getValue().proxy, handler, vertx);
int routesBeforeMiEvent = mr.getRoutes().size();
event.select(ManagementInterface.class).fire(new ManagementInterfaceImpl(mr));
// It may be that no build steps produced any management routes.
// But we still want to give a chance to the "ManagementInterface event" to collect any
// routes that users may have provided through observing this event.
//
// Hence, we only initialize the `managementRouter` router when we either had some routes from extensions (`hasManagementRoutes`)
// or if the event collected some routes (`routesBeforeMiEvent < routesAfterMiEvent`)
if (hasManagementRoutes || routesBeforeMiEvent < mr.getRoutes().size()) {
VertxHttpRecorder.managementRouterDelegate = handler;
if (VertxHttpRecorder.managementRouter == null) {
VertxHttpRecorder.managementRouter = new Handler() {
@Override
public void handle(HttpServerRequest event) {
VertxHttpRecorder.managementRouterDelegate.handle(event);
}
};
}
}
}
}
private void setupAccessLogHandler(Optional> mainRouterRuntimeValue,
RuntimeValue httpRouterRuntimeValue,
RuntimeValue frameworkRouter,
AccessLogReceiver receiver,
String rootPath,
String nonRootPath,
String pattern,
boolean consolidateReroutedRequests,
Optional excludePattern) {
Router httpRouteRouter = httpRouterRuntimeValue.getValue();
AccessLogHandler handler = new AccessLogHandler(receiver, pattern, consolidateReroutedRequests,
getClass().getClassLoader(),
excludePattern);
if (rootPath.equals("/") || nonRootPath.equals("/")) {
mainRouterRuntimeValue.orElse(httpRouterRuntimeValue).getValue().route()
.order(RouteConstants.ROUTE_ORDER_ACCESS_LOG_HANDLER)
.handler(handler);
} else if (nonRootPath.startsWith(rootPath)) {
httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_ACCESS_LOG_HANDLER).handler(handler);
} else if (rootPath.startsWith(nonRootPath)) {
frameworkRouter.getValue().route().order(RouteConstants.ROUTE_ORDER_ACCESS_LOG_HANDLER).handler(handler);
} else {
httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_ACCESS_LOG_HANDLER).handler(handler);
frameworkRouter.getValue().route().order(RouteConstants.ROUTE_ORDER_ACCESS_LOG_HANDLER).handler(handler);
}
}
private boolean decorateStacktrace(LaunchMode launchMode, LogBuildTimeConfig logBuildTimeConfig) {
return logBuildTimeConfig.decorateStacktraces() && launchMode.equals(LaunchMode.DEVELOPMENT);
}
private void addHotReplacementHandlerIfNeeded(Router router) {
if (hotReplacementHandler != null) {
//recorders are always executed in the current CL
ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
router.route().order(RouteConstants.ROUTE_ORDER_HOT_REPLACEMENT)
.handler(new HotReplacementRoutingContextHandler(currentCl));
}
}
private void applyCompression(boolean enableCompression, Router httpRouteRouter) {
if (enableCompression) {
httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_COMPRESSION).handler(new Handler() {
@Override
public void handle(RoutingContext ctx) {
// Add "Content-Encoding: identity" header that disables the compression
// This header can be removed to enable the compression
ctx.response().putHeader(HttpHeaders.CONTENT_ENCODING, HttpHeaders.IDENTITY);
ctx.next();
}
});
}
}
private void warnIfProxyAddressForwardingAllowedWithMultipleHeaders(ProxyConfig proxyConfig) {
boolean proxyAddressForwardingActivated = proxyConfig.proxyAddressForwarding;
boolean forwardedActivated = proxyConfig.allowForwarded;
boolean xForwardedActivated = proxyConfig.allowXForwarded.orElse(!forwardedActivated);
if (proxyAddressForwardingActivated && forwardedActivated && xForwardedActivated) {
LOGGER.warn(
"The X-Forwarded-* and Forwarded headers will be considered when determining the proxy address. " +
"This configuration can cause a security issue as clients can forge requests and send a " +
"forwarded header that is not overwritten by the proxy. " +
"Please consider use one of these headers just to forward the proxy address in requests.");
}
}
private static CompletableFuture initializeManagementInterfaceWithDomainSocket(Vertx vertx,
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, Handler managementRouter,
ManagementInterfaceConfiguration managementConfig,
List websocketSubProtocols) {
CompletableFuture managementInterfaceDomainSocketFuture = new CompletableFuture<>();
if (!managementBuildTimeConfig.enabled || managementRouter == null || managementConfig == null) {
managementInterfaceDomainSocketFuture.complete(null);
return managementInterfaceDomainSocketFuture;
}
HttpServerOptions domainSocketOptionsForManagement = createDomainSocketOptionsForManagementInterface(
managementBuildTimeConfig, managementConfig,
websocketSubProtocols);
if (domainSocketOptionsForManagement != null) {
vertx.createHttpServer(domainSocketOptionsForManagement)
.requestHandler(managementRouter)
.listen(ar -> {
if (ar.failed()) {
managementInterfaceDomainSocketFuture.completeExceptionally(
new IllegalStateException(
"Unable to start the management interface on the "
+ domainSocketOptionsForManagement.getHost() + " domain socket",
ar.cause()));
} else {
managementInterfaceDomainSocketFuture.complete(ar.result());
}
});
} else {
managementInterfaceDomainSocketFuture.complete(null);
}
return managementInterfaceDomainSocketFuture;
}
private static CompletableFuture initializeManagementInterface(Vertx vertx,
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, Handler managementRouter,
ManagementInterfaceConfiguration managementConfig,
LaunchMode launchMode,
List websocketSubProtocols, TlsConfigurationRegistry registry) throws IOException {
httpManagementServerOptions = null;
CompletableFuture managementInterfaceFuture = new CompletableFuture<>();
if (!managementBuildTimeConfig.enabled || managementRouter == null || managementConfig == null) {
managementInterfaceFuture.complete(null);
return managementInterfaceFuture;
}
HttpServerOptions httpServerOptionsForManagement = createHttpServerOptionsForManagementInterface(
managementBuildTimeConfig, managementConfig, launchMode,
websocketSubProtocols);
httpManagementServerOptions = HttpServerOptionsUtils.createSslOptionsForManagementInterface(
managementBuildTimeConfig, managementConfig, launchMode,
websocketSubProtocols, registry);
if (httpManagementServerOptions != null && httpManagementServerOptions.getKeyCertOptions() == null) {
httpManagementServerOptions = httpServerOptionsForManagement;
}
if (httpManagementServerOptions != null) {
vertx.createHttpServer(httpManagementServerOptions)
.requestHandler(managementRouter)
.listen(ar -> {
if (ar.failed()) {
managementInterfaceFuture.completeExceptionally(
new IllegalStateException("Unable to start the management interface on "
+ httpManagementServerOptions.getHost() + ":"
+ httpManagementServerOptions.getPort(), ar.cause()));
} else {
if (httpManagementServerOptions.isSsl()
&& (managementConfig.ssl.certificate.reloadPeriod.isPresent())) {
try {
long l = TlsCertificateReloader.initCertReloadingAction(
vertx, ar.result(), httpManagementServerOptions, managementConfig.ssl, registry,
managementConfig.tlsConfigurationName);
if (l != -1) {
refresTaskIds.add(l);
}
} catch (IllegalArgumentException e) {
managementInterfaceFuture.completeExceptionally(e);
return;
}
}
if (httpManagementServerOptions.isSsl()) {
CDI.current().select(HttpCertificateUpdateEventListener.class).get()
.register(ar.result(),
managementConfig.tlsConfigurationName.orElse(TlsConfig.DEFAULT_NAME),
"management interface");
}
actualManagementPort = ar.result().actualPort();
if (actualManagementPort != httpManagementServerOptions.getPort()) {
var managementPortSystemProperties = new PortSystemProperties();
managementPortSystemProperties.set("management", actualManagementPort, launchMode);
((VertxInternal) vertx).addCloseHook(new Closeable() {
@Override
public void close(Promise completion) {
managementPortSystemProperties.restore();
completion.complete();
}
});
}
managementInterfaceFuture.complete(ar.result());
}
});
} else {
managementInterfaceFuture.complete(null);
}
return managementInterfaceFuture;
}
private static CompletableFuture initializeMainHttpServer(Vertx vertx, HttpBuildTimeConfig httpBuildTimeConfig,
HttpConfiguration httpConfiguration,
LaunchMode launchMode,
Supplier eventLoops, List websocketSubProtocols, InsecureRequests insecureRequestStrategy,
TlsConfigurationRegistry registry)
throws IOException {
if (!httpConfiguration.hostEnabled && !httpConfiguration.domainSocketEnabled) {
return CompletableFuture.completedFuture(null);
}
// Http server configuration
httpMainServerOptions = createHttpServerOptions(httpBuildTimeConfig, httpConfiguration, launchMode,
websocketSubProtocols);
httpMainDomainSocketOptions = createDomainSocketOptions(httpBuildTimeConfig, httpConfiguration,
websocketSubProtocols);
HttpServerOptions tmpSslConfig = HttpServerOptionsUtils.createSslOptions(httpBuildTimeConfig, httpConfiguration,
launchMode, websocketSubProtocols, registry);
// Customize
ArcContainer container = Arc.container();
if (container != null) {
List> instances = container
.listAll(HttpServerOptionsCustomizer.class);
for (InstanceHandle instance : instances) {
HttpServerOptionsCustomizer customizer = instance.get();
if (httpMainServerOptions != null) {
customizer.customizeHttpServer(httpMainServerOptions);
}
if (tmpSslConfig != null) {
customizer.customizeHttpsServer(tmpSslConfig);
}
if (httpMainDomainSocketOptions != null) {
customizer.customizeDomainSocketServer(httpMainDomainSocketOptions);
}
}
}
// Disable TLS if certificate options are still missing after customize hooks.
if (tmpSslConfig != null && tmpSslConfig.getKeyCertOptions() == null) {
tmpSslConfig = null;
}
httpMainSslServerOptions = tmpSslConfig;
if (insecureRequestStrategy != HttpConfiguration.InsecureRequests.ENABLED
&& httpMainSslServerOptions == null) {
throw new IllegalStateException("Cannot set quarkus.http.insecure-requests without enabling SSL.");
}
int eventLoopCount = eventLoops.get();
final int ioThreads;
if (httpConfiguration.ioThreads.isPresent()) {
ioThreads = Math.min(httpConfiguration.ioThreads.getAsInt(), eventLoopCount);
} else if (launchMode.isDevOrTest()) {
ioThreads = Math.min(2, eventLoopCount); //Don't start ~100 threads to run a couple unit tests
} else {
ioThreads = eventLoopCount;
}
CompletableFuture futureResult = new CompletableFuture<>();
AtomicInteger connectionCount = new AtomicInteger();
// Note that a new HttpServer is created for each IO thread but we only want to fire the events (HttpServerStart etc.) once,
// for the first server that started listening
// See https://vertx.io/docs/vertx-core/java/#_server_sharing for more information
AtomicBoolean startEventsFired = new AtomicBoolean();
vertx.deployVerticle(new Supplier() {
@Override
public Verticle get() {
return new WebDeploymentVerticle(httpMainServerOptions, httpMainSslServerOptions, httpMainDomainSocketOptions,
launchMode, insecureRequestStrategy, httpConfiguration, connectionCount, registry, startEventsFired);
}
}, new DeploymentOptions().setInstances(ioThreads), new Handler>() {
@Override
public void handle(AsyncResult event) {
if (event.failed()) {
Throwable effectiveCause = event.cause();
if (effectiveCause instanceof BindException) {
List portsUsed = Collections.emptyList();
if ((httpMainSslServerOptions == null) && (httpMainServerOptions != null)) {
portsUsed = List.of(httpMainServerOptions.getPort());
} else if ((insecureRequestStrategy == InsecureRequests.DISABLED)
&& (httpMainSslServerOptions != null)) {
portsUsed = List.of(httpMainSslServerOptions.getPort());
} else if ((httpMainSslServerOptions != null)
&& (insecureRequestStrategy == InsecureRequests.ENABLED)
&& (httpMainServerOptions != null)) {
portsUsed = List.of(httpMainServerOptions.getPort(), httpMainSslServerOptions.getPort());
}
effectiveCause = new QuarkusBindException((BindException) effectiveCause, portsUsed);
}
futureResult.completeExceptionally(effectiveCause);
} else {
futureResult.complete(event.result());
}
}
});
return futureResult;
}
private static void doServerStart(Vertx vertx, HttpBuildTimeConfig httpBuildTimeConfig,
ManagementInterfaceBuildTimeConfig managementBuildTimeConfig, Handler managementRouter,
HttpConfiguration httpConfiguration, ManagementInterfaceConfiguration managementConfig,
LaunchMode launchMode,
Supplier eventLoops, List websocketSubProtocols,
InsecureRequests insecureRequestStrategy,
boolean auxiliaryApplication) throws IOException {
TlsConfigurationRegistry registry = null;
if (Arc.container() != null) {
registry = Arc.container().select(TlsConfigurationRegistry.class).orNull();
}
var mainServerFuture = initializeMainHttpServer(vertx, httpBuildTimeConfig, httpConfiguration, launchMode, eventLoops,
websocketSubProtocols, insecureRequestStrategy, registry);
var managementInterfaceFuture = initializeManagementInterface(vertx, managementBuildTimeConfig, managementRouter,
managementConfig, launchMode, websocketSubProtocols, registry);
var managementInterfaceDomainSocketFuture = initializeManagementInterfaceWithDomainSocket(vertx,
managementBuildTimeConfig, managementRouter, managementConfig, websocketSubProtocols);
try {
String deploymentIdIfAny = mainServerFuture.get();
HttpServer tmpManagementServer = null;
HttpServer tmpManagementServerUsingDomainSocket = null;
if (managementRouter != null) {
tmpManagementServer = managementInterfaceFuture.get();
tmpManagementServerUsingDomainSocket = managementInterfaceDomainSocketFuture.get();
}
HttpServer managementServer = tmpManagementServer;
HttpServer managementServerDomainSocket = tmpManagementServerUsingDomainSocket;
if (deploymentIdIfAny != null) {
VertxCoreRecorder.setWebDeploymentId(deploymentIdIfAny);
}
closeTask = new Runnable() {
@Override
public synchronized void run() {
//guard against this being run twice
if (closeTask == this) {
boolean isVertxClose = ((VertxInternal) vertx).closeFuture().future().isComplete();
int count = 0;
if (deploymentIdIfAny != null && vertx.deploymentIDs().contains(deploymentIdIfAny)) {
count++;
}
if (managementServer != null && !isVertxClose) {
count++;
}
if (managementServerDomainSocket != null && !isVertxClose) {
count++;
}
CountDownLatch latch = new CountDownLatch(count);
var handler = new Handler>() {
@Override
public void handle(AsyncResult event) {
latch.countDown();
}
};
// shutdown main HTTP server
if (deploymentIdIfAny != null) {
try {
vertx.undeploy(deploymentIdIfAny, handler);
} catch (Exception e) {
if (e instanceof RejectedExecutionException) {
// Shutting down
LOGGER.debug("Failed to undeploy deployment because a task was rejected (due to shutdown)",
e);
} else {
LOGGER.warn("Failed to undeploy deployment", e);
}
}
}
// shutdown the management interface
try {
for (Long id : refresTaskIds) {
TlsCertificateReloader.unschedule(vertx, id);
}
if (managementServer != null && !isVertxClose) {
managementServer.close(handler);
}
if (managementServerDomainSocket != null && !isVertxClose) {
managementServerDomainSocket.close(handler);
}
} catch (Exception e) {
LOGGER.warn("Unable to shutdown the management interface quietly", e);
}
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
closeTask = null;
if (remoteSyncHandler != null) {
remoteSyncHandler.close();
remoteSyncHandler = null;
}
}
};
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Unable to start HTTP server", e);
}
setHttpServerTiming(insecureRequestStrategy == InsecureRequests.DISABLED, httpMainServerOptions,
httpMainSslServerOptions,
httpMainDomainSocketOptions,
auxiliaryApplication, httpManagementServerOptions);
}
private static void setHttpServerTiming(boolean httpDisabled, HttpServerOptions httpServerOptions,
HttpServerOptions sslConfig,
HttpServerOptions domainSocketOptions, boolean auxiliaryApplication, HttpServerOptions managementConfig) {
StringBuilder serverListeningMessage = new StringBuilder("Listening on: ");
int socketCount = 0;
if (!httpDisabled && httpServerOptions != null) {
serverListeningMessage.append(String.format(
"http://%s:%s", getDeveloperFriendlyHostName(httpServerOptions), actualHttpPort));
socketCount++;
}
if (sslConfig != null) {
if (socketCount > 0) {
serverListeningMessage.append(" and ");
}
serverListeningMessage
.append(String.format("https://%s:%s", getDeveloperFriendlyHostName(sslConfig), actualHttpsPort));
socketCount++;
}
if (domainSocketOptions != null) {
if (socketCount > 0) {
serverListeningMessage.append(" and ");
}
serverListeningMessage.append(String.format("unix:%s", getDeveloperFriendlyHostName(domainSocketOptions)));
}
if (managementConfig != null) {
serverListeningMessage.append(
String.format(". Management interface listening on http%s://%s:%s.", managementConfig.isSsl() ? "s" : "",
getDeveloperFriendlyHostName(managementConfig), actualManagementPort));
}
Timing.setHttpServer(serverListeningMessage.toString(), auxiliaryApplication);
}
/**
* To improve developer experience in WSL dev/test mode, the server listening message should print "localhost" when
* the host is set to "0.0.0.0". Otherwise, display the actual host.
* Do not use this during the actual configuration, use options.getHost() there directly instead.
*/
private static String getDeveloperFriendlyHostName(HttpServerOptions options) {
return (LaunchMode.current().isDevOrTest() && "0.0.0.0".equals(options.getHost()) && isWSL()) ? "localhost"
: options.getHost();
}
/**
* @return {@code true} if the application is running in a WSL (Windows Subsystem for Linux) environment
*/
private static boolean isWSL() {
var sysEnv = System.getenv();
return sysEnv.containsKey("IS_WSL") || sysEnv.containsKey("WSL_DISTRO_NAME");
}
private static HttpServerOptions createHttpServerOptions(
HttpBuildTimeConfig buildTimeConfig, HttpConfiguration httpConfiguration,
LaunchMode launchMode, List websocketSubProtocols) {
if (!httpConfiguration.hostEnabled) {
return null;
}
// TODO other config properties
HttpServerOptions options = new HttpServerOptions();
int port = httpConfiguration.determinePort(launchMode);
options.setPort(port == 0 ? RANDOM_PORT_MAIN_HTTP : port);
HttpServerOptionsUtils.applyCommonOptions(options, buildTimeConfig, httpConfiguration, websocketSubProtocols);
httpConfiguration.websocketServer.maxFrameSize.ifPresent(s -> options.setMaxWebSocketFrameSize(s));
httpConfiguration.websocketServer.maxMessageSize.ifPresent(s -> options.setMaxWebSocketMessageSize(s));
return options;
}
private static HttpServerOptions createHttpServerOptionsForManagementInterface(
ManagementInterfaceBuildTimeConfig buildTimeConfig, ManagementInterfaceConfiguration httpConfiguration,
LaunchMode launchMode, List websocketSubProtocols) {
if (!httpConfiguration.hostEnabled) {
return null;
}
HttpServerOptions options = new HttpServerOptions();
int port = httpConfiguration.determinePort(launchMode);
options.setPort(port == 0 ? RANDOM_PORT_MANAGEMENT : port);
HttpServerOptionsUtils.applyCommonOptionsForManagementInterface(options, buildTimeConfig, httpConfiguration,
websocketSubProtocols);
return options;
}
private static HttpServerOptions createDomainSocketOptions(
HttpBuildTimeConfig buildTimeConfig, HttpConfiguration httpConfiguration,
List websocketSubProtocols) {
if (!httpConfiguration.domainSocketEnabled) {
return null;
}
HttpServerOptions options = new HttpServerOptions();
HttpServerOptionsUtils.applyCommonOptions(options, buildTimeConfig, httpConfiguration, websocketSubProtocols);
// Override the host (0.0.0.0 by default) with the configured domain socket.
options.setHost(httpConfiguration.domainSocket);
// Check if we can write into the domain socket directory
// We can do this check using a blocking API as the execution is done from the main thread (not an I/O thread)
File file = new File(httpConfiguration.domainSocket);
if (!file.getParentFile().canWrite()) {
LOGGER.warnf(
"Unable to write in the domain socket directory (`%s`). Binding to the socket is likely going to fail.",
httpConfiguration.domainSocket);
}
return options;
}
private static HttpServerOptions createDomainSocketOptionsForManagementInterface(
ManagementInterfaceBuildTimeConfig buildTimeConfig, ManagementInterfaceConfiguration httpConfiguration,
List websocketSubProtocols) {
if (!httpConfiguration.domainSocketEnabled) {
return null;
}
HttpServerOptions options = new HttpServerOptions();
HttpServerOptionsUtils.applyCommonOptionsForManagementInterface(options, buildTimeConfig, httpConfiguration,
websocketSubProtocols);
// Override the host (0.0.0.0 by default) with the configured domain socket.
options.setHost(httpConfiguration.domainSocket);
// Check if we can write into the domain socket directory
// We can do this check using a blocking API as the execution is done from the main thread (not an I/O thread)
File file = new File(httpConfiguration.domainSocket);
if (!file.getParentFile().canWrite()) {
LOGGER.warnf(
"Unable to write in the domain socket directory (`%s`). Binding to the socket is likely going to fail.",
httpConfiguration.domainSocket);
}
return options;
}
public void addRoute(RuntimeValue router, Function route, Handler handler,
HandlerType type) {
Route vr = route.apply(router.getValue());
if (type == HandlerType.BLOCKING) {
vr.blockingHandler(handler, false);
} else if (type == HandlerType.FAILURE) {
vr.failureHandler(handler);
} else {
vr.handler(handler);
}
}
public void setNonApplicationRedirectHandler(String nonApplicationPath, String rootPath) {
nonApplicationRedirectHandler = new Handler() {
@Override
public void handle(RoutingContext context) {
String absoluteURI = context.request().path();
String target = absoluteURI.substring(rootPath.length());
String redirectTo = nonApplicationPath + target;
String query = context.request().query();
if (query != null && !query.isEmpty()) {
redirectTo += '?' + query;
}
context.response()
.setStatusCode(HttpResponseStatus.MOVED_PERMANENTLY.code())
.putHeader(HttpHeaderNames.LOCATION, redirectTo)
.end();
}
};
}
public Handler getNonApplicationRedirectHandler() {
return nonApplicationRedirectHandler;
}
public GracefulShutdownFilter createGracefulShutdownHandler() {
return new GracefulShutdownFilter();
}
private static class WebDeploymentVerticle extends AbstractVerticle implements Resource {
private final TlsConfigurationRegistry registry;
private HttpServer httpServer;
private HttpServer httpsServer;
private HttpServer domainSocketServer;
private final HttpServerOptions httpOptions;
private final HttpServerOptions httpsOptions;
private final HttpServerOptions domainSocketOptions;
private final LaunchMode launchMode;
private volatile boolean clearHttpProperty = false;
private volatile boolean clearHttpsProperty = false;
private volatile PortSystemProperties portSystemProperties;
private final HttpConfiguration.InsecureRequests insecureRequests;
private final HttpConfiguration quarkusConfig;
private final AtomicInteger connectionCount;
private final List reloadingTasks = new CopyOnWriteArrayList<>();
private final AtomicBoolean startEventsFired;
public WebDeploymentVerticle(HttpServerOptions httpOptions, HttpServerOptions httpsOptions,
HttpServerOptions domainSocketOptions, LaunchMode launchMode,
InsecureRequests insecureRequests, HttpConfiguration quarkusConfig, AtomicInteger connectionCount,
TlsConfigurationRegistry registry, AtomicBoolean startEventsFired) {
this.httpOptions = httpOptions;
this.httpsOptions = httpsOptions;
this.launchMode = launchMode;
this.domainSocketOptions = domainSocketOptions;
this.insecureRequests = insecureRequests;
this.quarkusConfig = quarkusConfig;
this.connectionCount = connectionCount;
this.registry = registry;
this.startEventsFired = startEventsFired;
org.crac.Core.getGlobalContext().register(this);
}
@Override
public void start(Promise startFuture) {
assert Context.isOnEventLoopThread();
final AtomicInteger remainingCount = new AtomicInteger(0);
boolean httpServerEnabled = httpOptions != null && insecureRequests != HttpConfiguration.InsecureRequests.DISABLED;
if (httpServerEnabled) {
remainingCount.incrementAndGet();
}
if (httpsOptions != null) {
remainingCount.incrementAndGet();
}
if (domainSocketOptions != null) {
remainingCount.incrementAndGet();
}
if (remainingCount.get() == 0) {
startFuture
.fail(new IllegalArgumentException("Must configure at least one of http, https or unix domain socket"));
}
ArcContainer container = Arc.container();
boolean notifyStartObservers = container != null ? startEventsFired.compareAndSet(false, true) : false;
if (httpServerEnabled) {
httpServer = vertx.createHttpServer(httpOptions);
if (insecureRequests == HttpConfiguration.InsecureRequests.ENABLED) {
httpServer.requestHandler(ACTUAL_ROOT);
} else {
httpServer.requestHandler(new Handler() {
@Override
public void handle(HttpServerRequest req) {
try {
String host = req.getHeader(HttpHeaderNames.HOST);
if (host == null) {
//TODO: solution for HTTP/1.0, but really there is not much we can do
req.response().setStatusCode(HttpResponseStatus.NOT_FOUND.code()).end();
} else {
int includedPort = host.indexOf(":");
if (includedPort != -1) {
host = host.substring(0, includedPort);
}
req.response()
.setStatusCode(301)
.putHeader("Location",
"https://" + host + ":" + httpsOptions.getPort() + req.uri())
.end();
}
} catch (Exception e) {
req.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end();
}
}
});
}
setupTcpHttpServer(httpServer, httpOptions, false, startFuture, remainingCount, connectionCount,
container, notifyStartObservers);
}
if (domainSocketOptions != null) {
domainSocketServer = vertx.createHttpServer(domainSocketOptions);
domainSocketServer.requestHandler(ACTUAL_ROOT);
setupUnixDomainSocketHttpServer(domainSocketServer, domainSocketOptions, startFuture, remainingCount,
container, notifyStartObservers);
}
if (httpsOptions != null) {
httpsServer = vertx.createHttpServer(httpsOptions);
httpsServer.requestHandler(ACTUAL_ROOT);
setupTcpHttpServer(httpsServer, httpsOptions, true, startFuture, remainingCount, connectionCount,
container, notifyStartObservers);
}
}
private void setupUnixDomainSocketHttpServer(HttpServer httpServer, HttpServerOptions options,
Promise startFuture,
AtomicInteger remainingCount, ArcContainer container, boolean notifyStartObservers) {
httpServer.listen(SocketAddress.domainSocketAddress(options.getHost()), event -> {
if (event.succeeded()) {
if (notifyStartObservers) {
container.beanManager().getEvent().select(DomainSocketServerStart.class)
.fireAsync(new DomainSocketServerStart(options));
}
if (remainingCount.decrementAndGet() == 0) {
startFuture.complete(null);
}
} else {
if (event.cause() != null && event.cause().getMessage() != null
&& event.cause().getMessage().contains("Permission denied")) {
startFuture.fail(new IllegalStateException(
String.format(
"Unable to bind to Unix domain socket (%s) as the application does not have the permission to write in the directory.",
domainSocketOptions.getHost())));
} else if (event.cause() instanceof IllegalArgumentException) {
startFuture.fail(new IllegalArgumentException(
String.format(
"Unable to bind to Unix domain socket. Consider adding the 'io.netty:%s' dependency. See the Quarkus Vert.x reference guide for more details.",
Utils.isLinux() ? "netty-transport-native-epoll" : "netty-transport-native-kqueue")));
} else {
startFuture.fail(event.cause());
}
}
});
}
private void setupTcpHttpServer(HttpServer httpServer, HttpServerOptions options, boolean https,
Promise startFuture, AtomicInteger remainingCount, AtomicInteger currentConnectionCount,
ArcContainer container, boolean notifyStartObservers) {
if (quarkusConfig.limits.maxConnections.isPresent() && quarkusConfig.limits.maxConnections.getAsInt() > 0) {
var tracker = vertx.isMetricsEnabled()
? ((ExtendedQuarkusVertxHttpMetrics) ((VertxInternal) vertx).metricsSPI()).getHttpConnectionTracker()
: ExtendedQuarkusVertxHttpMetrics.NOOP_CONNECTION_TRACKER;
final int maxConnections = quarkusConfig.limits.maxConnections.getAsInt();
tracker.initialize(maxConnections, currentConnectionCount);
httpServer.connectionHandler(new Handler() {
@Override
public void handle(HttpConnection event) {
int current;
do {
current = currentConnectionCount.get();
if (current == maxConnections) {
//just close the connection
LOGGER.debug("Rejecting connection as there are too many active connections");
tracker.onConnectionRejected();
event.close();
return;
}
} while (!currentConnectionCount.compareAndSet(current, current + 1));
event.closeHandler(new Handler() {
@Override
public void handle(Void event) {
LOGGER.debug("Connection closed");
currentConnectionCount.decrementAndGet();
}
});
}
});
}
httpServer.listen(options.getPort(), options.getHost(), new Handler<>() {
@Override
public void handle(AsyncResult event) {
if (event.cause() != null) {
startFuture.fail(event.cause());
} else {
// Port may be random, so set the actual port
int actualPort = event.result().actualPort();
if (https) {
actualHttpsPort = actualPort;
validateHttpPorts(actualHttpPort, actualHttpsPort);
} else {
actualHttpPort = actualPort;
validateHttpPorts(actualHttpPort, actualHttpsPort);
}
if (actualPort != options.getPort()) {
// Override quarkus.http(s)?.(test-)?port
String schema;
if (https) {
clearHttpsProperty = true;
schema = "https";
} else {
clearHttpProperty = true;
actualHttpPort = actualPort;
schema = "http";
}
portSystemProperties = new PortSystemProperties();
portSystemProperties.set(schema, actualPort, launchMode);
}
if (https && (quarkusConfig.ssl.certificate.reloadPeriod.isPresent())) {
try {
long l = TlsCertificateReloader.initCertReloadingAction(
vertx, httpsServer, httpsOptions, quarkusConfig.ssl, registry,
quarkusConfig.tlsConfigurationName);
if (l != -1) {
reloadingTasks.add(l);
}
} catch (IllegalArgumentException e) {
startFuture.fail(e);
return;
}
}
if (https) {
container.instance(HttpCertificateUpdateEventListener.class).get()
.register(event.result(), quarkusConfig.tlsConfigurationName.orElse(TlsConfig.DEFAULT_NAME),
"http server");
}
if (notifyStartObservers) {
Event startEvent = container.beanManager().getEvent();
if (https) {
startEvent.select(HttpsServerStart.class).fireAsync(new HttpsServerStart(options));
} else {
startEvent.select(HttpServerStart.class).fireAsync(new HttpServerStart(options));
}
}
if (remainingCount.decrementAndGet() == 0) {
//make sure we only complete once
startFuture.complete(null);
}
}
}
private void validateHttpPorts(int httpPort, int httpsPort) {
if (httpsPort == httpPort) {
startFuture
.fail(new IllegalArgumentException("Both http and https servers started on port " + httpPort));
}
}
});
}
@Override
public void stop(Promise stopFuture) {
for (Long id : reloadingTasks) {
TlsCertificateReloader.unschedule(vertx, id);
}
final AtomicInteger remainingCount = new AtomicInteger(0);
if (httpServer != null) {
remainingCount.incrementAndGet();
}
if (httpsServer != null) {
remainingCount.incrementAndGet();
}
if (domainSocketServer != null) {
remainingCount.incrementAndGet();
}
Handler> handleClose = event -> {
if (remainingCount.decrementAndGet() == 0) {
if (clearHttpProperty) {
String portPropertyName = launchMode == LaunchMode.TEST ? "quarkus.http.test-port"
: "quarkus.http.port";
System.clearProperty(portPropertyName);
if (launchMode.isDevOrTest()) {
System.clearProperty(propertyWithProfilePrefix(portPropertyName));
}
}
if (clearHttpsProperty) {
String portPropertyName = launchMode == LaunchMode.TEST ? "quarkus.http.test-ssl-port"
: "quarkus.http.ssl-port";
System.clearProperty(portPropertyName);
if (launchMode.isDevOrTest()) {
System.clearProperty(propertyWithProfilePrefix(portPropertyName));
}
}
if (portSystemProperties != null) {
portSystemProperties.restore();
}
stopFuture.complete();
}
};
if (httpServer != null) {
httpServer.close(handleClose);
}
if (httpsServer != null) {
httpsServer.close(handleClose);
}
if (domainSocketServer != null) {
domainSocketServer.close(handleClose);
}
}
private String propertyWithProfilePrefix(String portPropertyName) {
return "%" + launchMode.getDefaultProfile() + "." + portPropertyName;
}
@Override
public void beforeCheckpoint(org.crac.Context extends Resource> context) throws Exception {
Promise p = Promise.promise();
stop(p);
p.future().toCompletionStage().toCompletableFuture().get();
}
@Override
public void afterRestore(org.crac.Context extends Resource> context) throws Exception {
Promise p = Promise.promise();
// The verticle must be started by the event-loop thread; the thread calling
// afterRestore will likely do so for all suspended verticles, and had we called
// this directly the verticles would all share the same context (run on the same thread).
this.context.runOnContext(nil -> start(p));
p.future().toCompletionStage().toCompletableFuture().get();
}
}
protected static ServerBootstrap virtualBootstrap;
protected static ChannelFuture virtualBootstrapChannel;
public static VirtualAddress VIRTUAL_HTTP = new VirtualAddress("netty-virtual-http");
private static void initializeVirtual(Vertx vertxRuntime) {
if (virtualBootstrap != null) {
return;
}
VertxInternal vertx = (VertxInternal) vertxRuntime;
virtualBootstrap = new ServerBootstrap();
virtualBootstrap.group(vertx.getEventLoopGroup())
.channel(VirtualServerChannel.class)
.handler(new ChannelInitializer() {
@Override
public void initChannel(VirtualServerChannel ch) throws Exception {
//ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
}
})
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(VirtualChannel ch) throws Exception {
ContextInternal rootContext = vertx.createEventLoopContext();
VertxHandler handler = VertxHandler.create(chctx -> {
Http1xServerConnection conn = new Http1xServerConnection(
() -> {
ContextInternal duplicated = (ContextInternal) VertxContext
.getOrCreateDuplicatedContext(rootContext);
setContextSafe(duplicated, true);
return duplicated;
},
null,
new HttpServerOptions(),
chctx,
rootContext,
"localhost",
null);
conn.handler(ACTUAL_ROOT);
return conn;
});
ch.pipeline().addLast("handler", handler);
}
});
// Start the server.
try {
virtualBootstrapChannel = virtualBootstrap.bind(VIRTUAL_HTTP).sync();
} catch (InterruptedException e) {
throw new RuntimeException("failed to bind virtual http");
}
}
public static Handler getRootHandler() {
return ACTUAL_ROOT;
}
/**
* used in the live reload handler to make sure the application has not been changed by another source (e.g. reactive
* messaging)
*/
public static Object getCurrentApplicationState() {
return rootHandler;
}
private static Handler configureAndGetBody(Optional maxBodySize, BodyConfig bodyConfig) {
BodyHandler bodyHandler = BodyHandler.create();
if (maxBodySize.isPresent()) {
bodyHandler.setBodyLimit(maxBodySize.get().asLongValue());
}
bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads);
bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory);
bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd);
bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes);
bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer);
return new Handler() {
@Override
public void handle(RoutingContext event) {
if (!Context.isOnEventLoopThread()) {
((ConnectionBase) event.request().connection()).channel().eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
//this can happen if blocking authentication is involved for get requests
if (!event.request().isEnded()) {
event.request().resume();
if (CAN_HAVE_BODY.contains(event.request().method())) {
bodyHandler.handle(event);
} else {
event.next();
}
} else {
event.next();
}
} catch (Throwable t) {
event.fail(t);
}
}
});
} else {
if (!event.request().isEnded()) {
event.request().resume();
}
if (CAN_HAVE_BODY.contains(event.request().method())) {
bodyHandler.handle(event);
} else {
event.next();
}
}
}
};
}
public Handler createBodyHandler() {
Optional maxBodySize = httpConfiguration.getValue().limits.maxBodySize;
return configureAndGetBody(maxBodySize, httpConfiguration.getValue().body);
}
public Handler createBodyHandlerForManagementInterface() {
Optional maxBodySize = managementConfiguration.getValue().limits.maxBodySize;
return configureAndGetBody(maxBodySize, managementConfiguration.getValue().body);
}
private static final List CAN_HAVE_BODY = Arrays.asList(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH,
HttpMethod.DELETE);
private BiConsumer processSameSiteConfig(Map httpConfiguration) {
List> functions = new ArrayList<>();
BiFunction last = null;
for (Map.Entry entry : new TreeMap<>(httpConfiguration).entrySet()) {
Pattern p = Pattern.compile(entry.getKey(), entry.getValue().caseSensitive ? 0 : Pattern.CASE_INSENSITIVE);
BiFunction biFunction = new BiFunction() {
@Override
public Boolean apply(Cookie cookie, HttpServerRequest request) {
if (p.matcher(cookie.getName()).matches()) {
if (entry.getValue().value == CookieSameSite.NONE) {
if (entry.getValue().enableClientChecker) {
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
if (userAgent != null
&& SameSiteNoneIncompatibleClientChecker.isSameSiteNoneIncompatible(userAgent)) {
return false;
}
}
if (entry.getValue().addSecureForNone) {
cookie.setSecure(true);
}
}
cookie.setSameSite(entry.getValue().value);
return true;
}
return false;
}
};
if (entry.getKey().equals(".*")) {
//a bit of a hack to make sure the pattern .* is evaluated last
last = biFunction;
} else {
functions.add(biFunction);
}
}
if (last != null) {
functions.add(last);
}
return new BiConsumer() {
@Override
public void accept(Cookie cookie, HttpServerRequest request) {
for (BiFunction i : functions) {
if (i.apply(cookie, request)) {
return;
}
}
}
};
}
public static class AlwaysCreateBodyHandlerSupplier implements BooleanSupplier {
@Override
public boolean getAsBoolean() {
return true;
}
}
private static class HotReplacementRoutingContextHandler implements Handler {
private final ClassLoader currentCl;
public HotReplacementRoutingContextHandler(ClassLoader currentCl) {
this.currentCl = currentCl;
}
@Override
public void handle(RoutingContext event) {
Thread.currentThread().setContextClassLoader(currentCl);
hotReplacementHandler.handle(event);
}
}
}