com.nesscomputing.httpserver.jetty.ClasspathResourceHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.nesscomputing.httpserver.jetty.ClasspathResourceHandler.java

Source

/**
 * Copyright (C) 2012 Ness Computing, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.nesscomputing.httpserver.jetty;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.common.io.ByteStreams;
import com.google.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import com.nesscomputing.httpserver.HttpServerConfig;

/**
 * Serves files from a given folder on the classpath through jetty.
 * Intended to serve a couple of static files e.g. for javascript or HTML.
 *
 * Needs to be configured programatically, can not be used in a jetty.xml file.
 */
public class ClasspathResourceHandler extends AbstractHandler {
    private final String basePath;
    private final String resourceLocation;

    /** Record the startup time. The classpath resources will not change after this time, so the handler can return 304 if requested. */
    private final long startupTime = System.currentTimeMillis() / 1000L;

    private static final MimeTypes MIME_TYPES;
    private boolean is304Disabled;

    static {
        final MimeTypes mimeTypes = new MimeTypes();
        // Now here is an oversight... =:-O
        mimeTypes.addMimeMapping("json", "application/json");
        MIME_TYPES = mimeTypes;
    }

    @Inject
    public ClasspathResourceHandler(final String basePath, final String resourceLocation) {
        this.basePath = basePath;
        this.resourceLocation = resourceLocation;
    }

    @Inject
    void setHttpServerConfig(HttpServerConfig config) {
        this.is304Disabled = config.isIfModifiedSinceDisabled();
    }

    @Override
    public void handle(final String target, final Request baseRequest, final HttpServletRequest request,
            final HttpServletResponse response) throws IOException, ServletException {
        if (baseRequest.isHandled()) {
            return;
        }

        String pathInfo = request.getPathInfo();

        // Only serve the content if the request matches the base path.
        if (pathInfo == null || !pathInfo.startsWith(basePath)) {
            return;
        }

        pathInfo = pathInfo.substring(basePath.length());

        if (!pathInfo.startsWith("/") && !pathInfo.isEmpty()) {
            // basepath is /foo and request went to /foobar --> pathInfo starts with bar
            // basepath is /foo and request went to /foo --> pathInfo should be /index.html
            return;
        }

        // Allow index.html as welcome file
        if ("/".equals(pathInfo) || "".equals(pathInfo)) {
            pathInfo = "/index.html";
        }

        boolean skipContent = false;

        // When a request hits this handler, it will serve something. Either data or an error.
        baseRequest.setHandled(true);

        final String method = request.getMethod();
        if (!StringUtils.equals(HttpMethods.GET, method)) {
            if (StringUtils.equals(HttpMethods.HEAD, method)) {
                skipContent = true;
            } else {
                response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                return;
            }
        }

        // Does the request contain an IF_MODIFIED_SINCE header?
        final long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
        if (ifModifiedSince > 0 && startupTime <= ifModifiedSince / 1000 && !is304Disabled) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        InputStream resourceStream = null;

        try {
            if (pathInfo.startsWith("/")) {
                final String resourcePath = resourceLocation + pathInfo;
                resourceStream = getClass().getResourceAsStream(resourcePath);
            }

            if (resourceStream == null) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            final Buffer mime = MIME_TYPES.getMimeByExtension(request.getPathInfo());
            if (mime != null) {
                response.setContentType(mime.toString("ISO8859-1"));
            }

            response.setDateHeader(HttpHeaders.LAST_MODIFIED, startupTime * 1000L);

            if (skipContent) {
                return;
            }

            // Send the content out. Lifted straight out of ResourceHandler.java

            OutputStream out = null;
            try {
                out = response.getOutputStream();
            } catch (IllegalStateException e) {
                out = new WriterOutputStream(response.getWriter());
            }

            if (out instanceof AbstractHttpConnection.Output) {
                ((AbstractHttpConnection.Output) out).sendContent(resourceStream);
            } else {
                ByteStreams.copy(resourceStream, out);
            }
        } finally {
            IOUtils.closeQuietly(resourceStream);
        }
    }
}