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

io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers Maven / Gradle / Ivy

package io.quarkus.vertx.http.runtime.options;

import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setCurrentContextSafe;
import static io.quarkus.vertx.http.runtime.TrustedProxyCheck.allowAll;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.vertx.http.runtime.FilterConfig;
import io.quarkus.vertx.http.runtime.ForwardedProxyHandler;
import io.quarkus.vertx.http.runtime.ForwardedServerRequestWrapper;
import io.quarkus.vertx.http.runtime.ForwardingProxyOptions;
import io.quarkus.vertx.http.runtime.HeaderConfig;
import io.quarkus.vertx.http.runtime.ProxyConfig;
import io.quarkus.vertx.http.runtime.ResumingRequestWrapper;
import io.quarkus.vertx.http.runtime.RouteConstants;
import io.quarkus.vertx.http.runtime.ServerLimitsConfig;
import io.quarkus.vertx.http.runtime.TrustedProxyCheck;
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

public class HttpServerCommonHandlers {
    public static void enforceMaxBodySize(ServerLimitsConfig limits, Router httpRouteRouter) {
        if (limits.maxBodySize.isPresent()) {
            long limit = limits.maxBodySize.get().asLongValue();
            Long limitObj = limit;
            httpRouteRouter.route().order(RouteConstants.ROUTE_ORDER_UPLOAD_LIMIT).handler(new Handler() {
                @Override
                public void handle(RoutingContext event) {
                    String lengthString = event.request().headers().get(HttpHeaderNames.CONTENT_LENGTH);

                    if (lengthString != null) {
                        long length = Long.parseLong(lengthString);
                        if (length > limit) {
                            event.response().headers().add(HttpHeaderNames.CONNECTION, "close");
                            event.response().setStatusCode(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE.code());
                            event.response().endHandler(new Handler() {
                                @Override
                                public void handle(Void e) {
                                    event.request().connection().close();
                                }
                            });
                            event.response().end();
                            return;
                        }
                    } else {
                        event.put(VertxHttpRecorder.MAX_REQUEST_SIZE_KEY, limitObj);
                    }
                    event.next();
                }
            });
        }
    }

    public static Handler enforceDuplicatedContext(Handler delegate) {
        return new Handler() {
            @Override
            public void handle(HttpServerRequest event) {
                if (!VertxContext.isOnDuplicatedContext()) {
                    // Vert.x should call us on a duplicated context.
                    // But in the case of pipelined requests, it does not.
                    // See https://github.com/quarkusio/quarkus/issues/24626.
                    Context context = VertxContext.createNewDuplicatedContext();
                    context.runOnContext(new Handler() {
                        @Override
                        public void handle(Void x) {
                            setCurrentContextSafe(true);
                            delegate.handle(new ResumingRequestWrapper(event));
                        }
                    });
                } else {
                    setCurrentContextSafe(true);
                    delegate.handle(new ResumingRequestWrapper(event));
                }
            }
        };
    }

    public static Handler applyProxy(ProxyConfig proxyConfig, Handler root,
            Supplier vertx) {
        if (proxyConfig.proxyAddressForwarding) {
            final ForwardingProxyOptions forwardingProxyOptions = ForwardingProxyOptions.from(proxyConfig);
            final TrustedProxyCheck.TrustedProxyCheckBuilder proxyCheckBuilder = forwardingProxyOptions.trustedProxyCheckBuilder;
            if (proxyCheckBuilder == null) {
                // no proxy check => we do not restrict who can send `X-Forwarded` or `X-Forwarded-*` headers
                final TrustedProxyCheck allowAllProxyCheck = allowAll();
                return new Handler() {
                    @Override
                    public void handle(HttpServerRequest event) {
                        root.handle(new ForwardedServerRequestWrapper(event, forwardingProxyOptions, allowAllProxyCheck));
                    }
                };
            } else {
                // restrict who can send `Forwarded`, `X-Forwarded` or `X-Forwarded-*` headers
                return new ForwardedProxyHandler(proxyCheckBuilder, vertx, root, forwardingProxyOptions);
            }
        }
        return root;
    }

    public static void applyFilters(Map filtersInConfig, Router httpRouteRouter) {
        if (!filtersInConfig.isEmpty()) {
            for (var entry : filtersInConfig.entrySet()) {
                var filterConfig = entry.getValue();
                var matches = filterConfig.matches;
                var order = filterConfig.order.orElse(Integer.MIN_VALUE);
                var methods = filterConfig.methods;
                var headers = filterConfig.header;
                if (methods.isEmpty()) {
                    httpRouteRouter.routeWithRegex(matches)
                            .order(order)
                            .handler(new Handler() {
                                @Override
                                public void handle(RoutingContext event) {
                                    addFilterHeaders(event, headers);
                                    event.next();
                                }

                            });
                } else {
                    for (var method : methods.get()) {
                        httpRouteRouter.routeWithRegex(HttpMethod.valueOf(method.toUpperCase(Locale.ROOT)), matches)
                                .order(order)
                                .handler(new Handler() {
                                    @Override
                                    public void handle(RoutingContext event) {
                                        addFilterHeaders(event, headers);
                                        event.next();
                                    }
                                });
                    }
                }
            }
        }
    }

    private static void addFilterHeaders(RoutingContext event, Map headers) {
        for (var entry : headers.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            MultiMap responseHeaders = event.response().headers();
            List oldValues = responseHeaders.getAll(key);
            if (oldValues.isEmpty()) {
                responseHeaders.set(key, value);
            } else {
                // we need to make sure the new value is not duplicated
                var newValues = new LinkedHashSet(oldValues);
                boolean added = newValues.add(value);
                if (added) {
                    responseHeaders.set(key, newValues);
                } else {
                    // we don't need to do anything here as the value was already in the set
                }
            }
        }
    }

    public static void applyHeaders(Map headers, Router httpRouteRouter) {
        if (!headers.isEmpty()) {
            // Creates a handler for each header entry
            for (Map.Entry entry : headers.entrySet()) {
                var name = entry.getKey();
                var config = entry.getValue();
                if (config.methods.isEmpty()) {
                    httpRouteRouter.route(config.path)
                            .order(RouteConstants.ROUTE_ORDER_HEADERS)
                            .handler(new Handler() {
                                @Override
                                public void handle(RoutingContext event) {
                                    event.response().headers().set(name, config.value);
                                    event.next();
                                }
                            });
                } else {
                    for (String method : config.methods.get()) {
                        httpRouteRouter.route(HttpMethod.valueOf(method.toUpperCase(Locale.ROOT)), config.path)
                                .order(RouteConstants.ROUTE_ORDER_HEADERS)
                                .handler(new Handler() {
                                    @Override
                                    public void handle(RoutingContext event) {
                                        event.response().headers().add(name, config.value);
                                        event.next();
                                    }
                                });
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy