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

io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler Maven / Gradle / Ivy

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

import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import io.quarkus.fs.util.ZipUtils;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.impl.HttpUtils;
import io.vertx.core.http.impl.MimeMapping;
import io.vertx.core.net.impl.URIDecoder;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.impl.Utils;

/**
 * A Handler to serve static files from jar files or from a local directory.
 */
public class FileSystemStaticHandler implements Handler, Closeable {

    private static final String DEFAULT_CONTENT_ENCODING = StandardCharsets.UTF_8.name();

    private static final ReentrantLock ROOT_CREATION_LOCK = new ReentrantLock();

    /**
     * Resolved web roots based on the configured web roots.
     */
    private List resolvedWebRoots;

    /**
     * Web roots that are used to serve files from. File resolving is tried in the order of configurations.
     */
    private List webRootConfigurations;

    public FileSystemStaticHandler() {

    }

    public FileSystemStaticHandler(List webRootConfigurations) {
        this.webRootConfigurations = webRootConfigurations;
    }

    public List getWebRootConfigurations() {
        return webRootConfigurations;
    }

    public void setWebRootConfigurations(List webRootConfigurations) {
        this.webRootConfigurations = webRootConfigurations;
    }

    @Override
    public void handle(RoutingContext context) {
        HttpServerRequest request = context.request();
        if (request.method() != HttpMethod.GET && request.method() != HttpMethod.HEAD) {
            context.next();
            return;
        }

        // decode URL path
        String uriDecodedPath = URIDecoder.decodeURIComponent(context.normalizedPath(), false);
        // if the normalized path is null it cannot be resolved
        if (uriDecodedPath == null) {
            context.next();
            return;
        }
        // will normalize and handle all paths as UNIX paths
        String path = HttpUtils.removeDots(uriDecodedPath.replace('\\', '/'));
        path = Utils.pathOffset(path, context);
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        try {
            sendStatic(context, path);
        } catch (IOException e) {
            context.fail(e);
            return;
        }
    }

    /**
     * Resolves the web roots based on the webRootConfigurations
     *
     * @throws IOException if an I/O error occurs
     */
    private void resolveWebRoots() throws IOException {
        if (resolvedWebRoots == null) {
            try {
                ROOT_CREATION_LOCK.lock();
                if (resolvedWebRoots == null) {
                    List rootPaths = new ArrayList<>();
                    for (StaticWebRootConfiguration fsConfiguration : webRootConfigurations) {
                        Path sysFSPath = Paths.get(fsConfiguration.fileSystem);
                        Path rootPath = sysFSPath;
                        if (Files.isRegularFile(sysFSPath)) {
                            FileSystem fileSystem = ZipUtils.newFileSystem(sysFSPath);
                            rootPath = fileSystem.getPath("");
                        }
                        rootPaths.add(rootPath.resolve(fsConfiguration.webRoot));
                    }
                    resolvedWebRoots = new ArrayList<>(rootPaths);
                }
            } finally {
                ROOT_CREATION_LOCK.unlock();
            }
        }
    }

    /**
     *
     * @param context
     * @param path
     * @throws IOException if an I/O error occurs
     */
    private void sendStatic(RoutingContext context, String path) throws IOException {
        resolveWebRoots();

        Path found = null;
        for (Path root : resolvedWebRoots) {
            Path resolved = root.resolve(path);
            if (resolved.getFileSystem().isOpen() && Files.exists(resolved)) {
                found = resolved;
                break;
            }
        }

        if (found == null) {
            context.next();
            return;
        }

        final HttpServerResponse response = context.response();
        String contentType = MimeMapping.getMimeTypeForFilename(path);
        if (contentType != null) {
            if (contentType.startsWith("text")) {
                response.putHeader(HttpHeaders.CONTENT_TYPE, contentType + ";charset=" + DEFAULT_CONTENT_ENCODING);
            } else {
                response.putHeader(HttpHeaders.CONTENT_TYPE, contentType);
            }
        }

        BasicFileAttributes fileAttributes = Files.readAttributes(found, BasicFileAttributes.class);
        if (fileAttributes.isDirectory()) {
            // directory listing is not supported
            context.next();
            return;
        } else if (fileAttributes.isRegularFile()) {
            context.end(Buffer.buffer(Files.readAllBytes(found)));
        }
    }

    @Override
    public void close() throws IOException {
        if (resolvedWebRoots == null) {
            // web roots are not initialized, most likely no call to dev ui was made before
            return;
        }

        // Close all filesystems that might have been created to access jar files
        for (Path resolvedWebRoot : resolvedWebRoots) {
            FileSystem fs = resolvedWebRoot.getFileSystem();

            // never attempt to close the default filesystem, e.g. WindowsFileSystem. Only one instance of it exists.
            if (fs != FileSystems.getDefault()) {
                fs.close();
            }
        }
    }

    public static class StaticWebRootConfiguration {
        /**
         * File system of the webroot. This can point to a directory on disk, or a jar file.
         */
        private String fileSystem;

        /**
         * Root directory inside the file system to service static files from. This is relative to the fileSystem. For a
         * directory fileSystem, this has to be a subdirectory. For jar files, this has to be a directory inside the jar.
         */
        private String webRoot;

        public StaticWebRootConfiguration() {

        }

        public StaticWebRootConfiguration(String fileSystemPath, String webRoot) {
            this.fileSystem = fileSystemPath;
            this.webRoot = webRoot;
        }

        public String getFileSystem() {
            return fileSystem;
        }

        public void setFileSystem(String fileSystem) {
            this.fileSystem = fileSystem;
        }

        public String getWebRoot() {
            return webRoot;
        }

        public void setWebRoot(String webRoot) {
            this.webRoot = webRoot;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy