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

io.muserver.handlers.ResourceHandler Maven / Gradle / Ivy

The newest version!
package io.muserver.handlers;

import io.muserver.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * A handler to serve static content. To create a handler, using {@link ResourceHandlerBuilder#fileOrClasspath(String, String)},
 * {@link ResourceHandlerBuilder#classpathHandler(String)}, {@link ResourceHandlerBuilder#fileHandler(File)} or one of its variants.
 */
public class ResourceHandler implements MuHandler {
    private static final Logger log = LoggerFactory.getLogger(ResourceHandler.class);

    private final Map extensionToResourceType;
    private final String defaultFile;
    private final boolean directoryListingEnabled;
    private final ResourceProviderFactory resourceProviderFactory;
    private final String directoryListingCss;
    private final DateTimeFormatter dateFormatter;
    private final ResourceCustomizer resourceCustomizer;
    private final BareDirectoryRequestAction bareDirectoryRequestAction;

    ResourceHandler(ResourceProviderFactory resourceProviderFactory, String defaultFile, Map extensionToResourceType, boolean directoryListingEnabled, String directoryListingCss, DateTimeFormatter dateFormatter, ResourceCustomizer resourceCustomizer, BareDirectoryRequestAction bareDirectoryRequestAction) {
        this.resourceProviderFactory = resourceProviderFactory;
        this.extensionToResourceType = extensionToResourceType;
        this.defaultFile = defaultFile;
        this.directoryListingEnabled = directoryListingEnabled;
        this.directoryListingCss = directoryListingCss;
        this.dateFormatter = dateFormatter;
        this.resourceCustomizer = resourceCustomizer;
        this.bareDirectoryRequestAction = bareDirectoryRequestAction;
    }

    @Override
    public boolean handle(MuRequest request, MuResponse response) throws IOException {
        String requestPath = request.relativePath();
        if (requestPath.endsWith("/") && defaultFile != null) {
            requestPath += defaultFile;
        }
        String decodedRelativePath = Mutils.urlDecode(requestPath);

        ResourceProvider provider = resourceProviderFactory.get(decodedRelativePath);
        if (!provider.exists()) {
            if (directoryListingEnabled) {
                provider = resourceProviderFactory.get(Mutils.urlDecode(request.relativePath()));
                if (!provider.isDirectory()) {
                    return false;
                }
            } else {
                return false;
            }
        }

        if (provider.isDirectory()) {
            if (!request.relativePath().endsWith("/")) {
                switch (bareDirectoryRequestAction) {
                    case REDIRECT_WITH_TRAILING_SLASH:
                        response.redirect(request.uri().getRawPath() + "/");
                        return true;
                    case TREAT_AS_NOT_FOUND:
                        return false;
                    default: throw new IllegalStateException("Unreachable");
                }
            }
            if (directoryListingEnabled) {
                listDirectory(request, response, provider);
            } else {
                return false;
            }
        } else {
            String filename = requestPath.substring(requestPath.lastIndexOf('/'));
            Date lastModified = provider.lastModified();
            Long totalSize = provider.fileSize();
            addHeaders(response, filename, totalSize, lastModified, request);
            boolean sendBody = request.method() != Method.HEAD;

            String ims = request.headers().get(HeaderNames.IF_MODIFIED_SINCE);
            if (ims != null) {
                try {
                    long lastModTime = lastModified.getTime() / 1000;
                    long lastAccessed = Mutils.fromHttpDate(ims).getTime() / 1000;
                    if (lastModTime <= lastAccessed) {
                        response.status(304);
                        sendBody = false;
                    }
                } catch (DateTimeParseException e) {
                    log.info("Ignoring cache check due to invalid If-Modified-Since header value: " + ims);
                }
            }

            String rh = request.headers().get("range");
            long maxAmountToSend = totalSize != null ? totalSize : Long.MAX_VALUE;
            if (rh != null && totalSize != null && response.status() != 304) {
                try {
                    List requestedRanges = BytesRange.parse(totalSize, rh);
                    if (requestedRanges.size() == 1) {
                        BytesRange range = requestedRanges.get(0);
                        boolean couldSkip = provider.skipIfPossible(range.from);
                        if (couldSkip) {
                            response.status(206);
                            maxAmountToSend = range.length();
                            response.headers().set(HeaderNames.CONTENT_LENGTH, maxAmountToSend);
                            response.headers().set(HeaderNames.CONTENT_RANGE, range.toString());
                        }
                    }
                } catch (IllegalArgumentException e) {
                    log.info("Ignoring range request due to invalid Range header value: " + rh);
                }
            }
            try {
                provider.sendTo(request, response, sendBody, maxAmountToSend);
            } catch (IOException | IllegalStateException ignored) {
                log.debug(request + " cancelled before full response sent to the client");
            }
        }

        return true;
    }

    private void listDirectory(MuRequest request, MuResponse response, ResourceProvider provider) throws IOException {
        response.contentType(ContentTypes.TEXT_HTML_UTF8);
        response.headers().set("X-UA-Compatible", "IE=edge");

        try (OutputStreamWriter osw = new OutputStreamWriter(response.outputStream(), StandardCharsets.UTF_8);
             BufferedWriter writer = new BufferedWriter(osw, 8192)) {
            writer.write("\n");
            new DirectoryLister(writer, provider, request.contextPath(), request.relativePath(), directoryListingCss, dateFormatter).render();
        }
    }


    private void addHeaders(MuResponse response, String fileName, Long fileSize, Date lastModified, MuRequest request) {
        int ind = fileName.lastIndexOf('.');
        ResourceType type;
        if (ind == -1) {
            type = ResourceType.DEFAULT;
        } else {
            String extension = fileName.substring(ind + 1).toLowerCase();
            type = extensionToResourceType.getOrDefault(extension, ResourceType.DEFAULT);
        }
        response.contentType(type.mimeType());
        Headers headers = response.headers();
        headers.set(HeaderNames.ACCEPT_RANGES, HeaderValues.BYTES);
        if (fileSize != null) {
            headers.set(HeaderNames.CONTENT_LENGTH, fileSize);
        }
        if (lastModified != null) {
            headers.set(HeaderNames.LAST_MODIFIED, Mutils.toHttpDate(lastModified));
        }
        headers.add(type.headers());
        if (this.resourceCustomizer != null) {
            this.resourceCustomizer.beforeHeadersSent(request, headers);
        }
    }

    @Override
    public String toString() {
        return "ResourceHandler{" +
            "defaultFile='" + defaultFile + '\'' +
            ", directoryListingEnabled=" + directoryListingEnabled +
            ", resourceProviderFactory=" + resourceProviderFactory +
            '}';
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy