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

io.quarkus.vertx.http.runtime.StaticResourcesRecorder Maven / Gradle / Ivy

package io.quarkus.vertx.http.runtime;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.impl.MimeMapping;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.FileSystemAccess;
import io.vertx.ext.web.handler.StaticHandler;

@Recorder
public class StaticResourcesRecorder {

    public static final String META_INF_RESOURCES = "META-INF/resources";

    private static volatile List hotDeploymentResourcePaths;

    final RuntimeValue httpConfiguration;
    final HttpBuildTimeConfig httpBuildTimeConfig;
    private Set compressMediaTypes = Set.of();

    public StaticResourcesRecorder(RuntimeValue httpConfiguration,
            HttpBuildTimeConfig httpBuildTimeConfig) {
        this.httpConfiguration = httpConfiguration;
        this.httpBuildTimeConfig = httpBuildTimeConfig;
    }

    public static void setHotDeploymentResources(List resources) {
        hotDeploymentResourcePaths = resources;
    }

    public Consumer start(Set knownPaths) {
        if (httpBuildTimeConfig.enableCompression && httpBuildTimeConfig.compressMediaTypes.isPresent()) {
            this.compressMediaTypes = Set.copyOf(httpBuildTimeConfig.compressMediaTypes.get());
        }
        List> handlers = new ArrayList<>();
        StaticResourcesConfig config = httpConfiguration.getValue().staticResources;

        if (hotDeploymentResourcePaths != null && !hotDeploymentResourcePaths.isEmpty()) {
            for (Path resourcePath : hotDeploymentResourcePaths) {
                String root = resourcePath.toAbsolutePath().toString();
                StaticHandler staticHandler = StaticHandler.create(FileSystemAccess.ROOT, root)
                        .setDefaultContentEncoding(config.contentEncoding.name())
                        .setCachingEnabled(false)
                        .setIndexPage(config.indexPage)
                        .setIncludeHidden(config.includeHidden)
                        .setEnableRangeSupport(config.enableRangeSupport);
                handlers.add(new Handler<>() {
                    @Override
                    public void handle(RoutingContext ctx) {
                        try {
                            compressIfNeeded(ctx, ctx.normalizedPath());
                            staticHandler.handle(ctx);
                        } catch (Exception e) {
                            // on Windows, the drive in file path screws up cache lookup
                            // so just punt to next handler
                            ctx.next();
                        }
                    }
                });
            }
        }
        if (!knownPaths.isEmpty()) {
            ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
            StaticHandler staticHandler = StaticHandler.create(META_INF_RESOURCES)
                    .setDefaultContentEncoding("UTF-8")
                    .setCachingEnabled(config.cachingEnabled)
                    .setIndexPage(config.indexPage)
                    .setIncludeHidden(config.includeHidden)
                    .setEnableRangeSupport(config.enableRangeSupport)
                    .setMaxCacheSize(config.maxCacheSize)
                    .setCacheEntryTimeout(config.cacheEntryTimeout.toMillis())
                    .setMaxAgeSeconds(config.maxAge.toSeconds());
            // normalize index page like StaticHandler because its not expose
            // TODO: create a converter to normalize filename in config.indexPage?
            final String indexPage = (config.indexPage.charAt(0) == '/')
                    ? config.indexPage.substring(1)
                    : config.indexPage;
            handlers.add(new Handler<>() {
                @Override
                public void handle(RoutingContext ctx) {
                    String rel = ctx.mountPoint() == null ? ctx.normalizedPath()
                            : ctx.normalizedPath().substring(
                                    // let's be extra careful here in case Vert.x normalizes the mount points at some point
                                    ctx.mountPoint().endsWith("/") ? ctx.mountPoint().length() - 1 : ctx.mountPoint().length());
                    // check effective path, otherwise the index page when path ends with '/'
                    if (knownPaths.contains(rel) || (rel.endsWith("/") && knownPaths.contains(rel.concat(indexPage)))) {
                        compressIfNeeded(ctx, rel);
                        staticHandler.handle(ctx);
                    } else {
                        // make sure we don't lose the correct TCCL to Vert.x...
                        Thread.currentThread().setContextClassLoader(currentCl);
                        ctx.next();
                    }
                }
            });
        }

        return new Consumer<>() {

            @Override
            public void accept(Route route) {
                // Restrict the route for static resources to HEAD and GET
                // No other HTTP methods should be used
                route.method(HttpMethod.GET);
                route.method(HttpMethod.HEAD);
                route.method(HttpMethod.OPTIONS);
                for (Handler i : handlers) {
                    route.handler(i);
                }
            }
        };
    }

    private void compressIfNeeded(RoutingContext ctx, String path) {
        if (httpBuildTimeConfig.enableCompression && isCompressed(path)) {
            // VertxHttpRecorder is adding "Content-Encoding: identity" to all requests if compression is enabled.
            // Handlers can remove the "Content-Encoding: identity" header to enable compression.
            ctx.response().headers().remove(HttpHeaders.CONTENT_ENCODING);
        }
    }

    private boolean isCompressed(String path) {
        if (compressMediaTypes.isEmpty()) {
            return false;
        }
        final String resourcePath = path.endsWith("/") ? path + StaticHandler.DEFAULT_INDEX_PAGE : path;
        final String contentType = MimeMapping.getMimeTypeForFilename(resourcePath);
        return contentType != null && compressMediaTypes.contains(contentType);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy