]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / server / handler / ResourceHandler.java
diff --git a/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java b/lib/jetty/org/eclipse/jetty/server/handler/ResourceHandler.java
new file mode 100644 (file)
index 0000000..10d1e05
--- /dev/null
@@ -0,0 +1,613 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests for resources that do not exist are let pass (Eg no 404's).
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ResourceHandler.class);
+
+    ContextHandler _context;
+    Resource _baseResource;
+    Resource _defaultStylesheet;
+    Resource _stylesheet;
+    String[] _welcomeFiles={"index.html"};
+    MimeTypes _mimeTypes = new MimeTypes();
+    String _cacheControl;
+    boolean _directory;
+    boolean _etags;
+    int _minMemoryMappedContentLength=-1;
+    int _minAsyncContentLength=0;
+
+    /* ------------------------------------------------------------ */
+    public ResourceHandler()
+    {
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public MimeTypes getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the directory option.
+     * @return true if directories are listed.
+     */
+    public boolean isDirectoriesListed()
+    {
+        return _directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the directory.
+     * @param directory true if directories are listed.
+     */
+    public void setDirectoriesListed(boolean directory)
+    {
+        _directory = directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get minimum memory mapped file content length.
+     * @return the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 (default) for no memory mapped
+     * buffers.
+     */
+    public int getMinMemoryMappedContentLength()
+    {
+        return _minMemoryMappedContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set minimum memory mapped file content length.
+     * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
+     * be served using a memory mapped buffer, or -1 for no memory mapped
+     * buffers.
+     */
+    public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
+    {
+        _minMemoryMappedContentLength = minMemoryMappedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the minimum content length for async handling.
+     * @return The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 (default) for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public int getMinAsyncContentLength()
+    {
+        return _minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the minimum content length for async handling.
+     * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous 
+     * handling is used, or -1 for no async handling or 0 for using
+     * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+     */
+    public void setMinAsyncContentLength(int minAsyncContentLength)
+    {
+        _minAsyncContentLength = minAsyncContentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if ETag processing is done
+     */
+    public boolean isEtags()
+    {
+        return _etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param etags True if ETag processing is done
+     */
+    public void setEtags(boolean etags)
+    {
+        _etags = etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+    throws Exception
+    {
+        Context scontext = ContextHandler.getCurrentContext();
+        _context = (scontext==null?null:scontext.getContextHandler());
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    public String getResourceBase()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param base The resourceBase to set.
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource=base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resourceBase The base resource as a string.
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(Resource.newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the stylesheet as a Resource.
+     */
+    public Resource getStylesheet()
+    {
+       if(_stylesheet != null)
+       {
+           return _stylesheet;
+       }
+       else
+       {
+           if(_defaultStylesheet == null)
+           {
+               _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+           }
+           return _defaultStylesheet;
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param stylesheet The location of the stylesheet to be used as a String.
+     */
+    public void setStylesheet(String stylesheet)
+    {
+        try
+        {
+            _stylesheet = Resource.newResource(stylesheet);
+            if(!_stylesheet.exists())
+            {
+                LOG.warn("unable to find custom stylesheet: " + stylesheet);
+                _stylesheet = null;
+            }
+        }
+       catch(Exception e)
+       {
+           LOG.warn(e.toString());
+           LOG.debug(e);
+           throw new IllegalArgumentException(stylesheet);
+       }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cacheControl header to set on all static content.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cacheControl the cacheControl header to set on all static content.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl=cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path==null || !path.startsWith("/"))
+            throw new MalformedURLException(path);
+
+        Resource base = _baseResource;
+        if (base==null)
+        {
+            if (_context==null)
+                return null;
+            base=_context.getBaseResource();
+            if (base==null)
+                return null;
+        }
+
+        try
+        {
+            path=URIUtil.canonicalPath(path);
+            return base.addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+    {
+        String servletPath;
+        String pathInfo;
+        Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
+        if (included != null && included.booleanValue())
+        {
+            servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+
+            if (servletPath == null && pathInfo == null)
+            {
+                servletPath = request.getServletPath();
+                pathInfo = request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        return getResource(pathInContext);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setWelcomeFiles(String[] welcomeFiles)
+    {
+        _welcomeFiles=welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+    {
+        for (int i=0;i<_welcomeFiles.length;i++)
+        {
+            Resource welcome=directory.addPath(_welcomeFiles[i]);
+            if (welcome.exists() && !welcome.isDirectory())
+                return welcome;
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (baseRequest.isHandled())
+            return;
+
+        boolean skipContentBody = false;
+
+        if(!HttpMethod.GET.is(request.getMethod()))
+        {
+            if(!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                //try another handler
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+            skipContentBody = true;
+        }
+
+        Resource resource = getResource(request);
+        // If resource is not found
+        if (resource==null || !resource.exists())
+        {
+            // inject the jetty-dir.css file if it matches
+            if (target.endsWith("/jetty-dir.css"))
+            {
+                resource = getStylesheet();
+                if (resource==null)
+                    return;
+                response.setContentType("text/css");
+            }
+            else
+            {
+                //no resource - try other handlers
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+        }
+
+        // We are going to serve something
+        baseRequest.setHandled(true);
+
+        // handle directories
+        if (resource.isDirectory())
+        {
+            if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+            {
+                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
+                return;
+            }
+
+            Resource welcome=getWelcome(resource);
+            if (welcome!=null && welcome.exists())
+                resource=welcome;
+            else
+            {
+                doDirectory(request,response,resource);
+                baseRequest.setHandled(true);
+                return;
+            }
+        }
+
+        // Handle ETAGS
+        long last_modified=resource.lastModified();
+        String etag=null;
+        if (_etags)
+        {
+            // simple handling of only a single etag
+            String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+            etag=resource.getWeakETag();
+            if (ifnm!=null && resource!=null && ifnm.equals(etag))
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+                return;
+            }
+        }
+        
+        // Handle if modified since 
+        if (last_modified>0)
+        {
+            long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+            if (if_modified>0 && last_modified/1000<=if_modified/1000)
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                return;
+            }
+        }
+
+        // set the headers
+        String mime=_mimeTypes.getMimeByExtension(resource.toString());
+        if (mime==null)
+            mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+        doResponseHeaders(response,resource,mime);
+        if (_etags)
+            baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+        
+        if(skipContentBody)
+            return;
+        
+        
+        // Send the content
+        OutputStream out =null;
+        try {out = response.getOutputStream();}
+        catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+        // Has the output been wrapped
+        if (!(out instanceof HttpOutput))
+            // Write content via wrapped output
+            resource.writeTo(out,0,resource.length());
+        else
+        {
+            // select async by size
+            int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
+            
+            if (request.isAsyncSupported() && 
+                min_async_size>0 &&
+                resource.length()>=min_async_size)
+            {
+                final AsyncContext async = request.startAsync();
+                Callback callback = new Callback()
+                {
+                    @Override
+                    public void succeeded()
+                    {
+                        async.complete();
+                    }
+
+                    @Override
+                    public void failed(Throwable x)
+                    {
+                        LOG.warn(x.toString());
+                        LOG.debug(x);
+                        async.complete();
+                    }   
+                };
+
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer,callback);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    // Close of the channel/inputstream is done by the async sendContent
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel,callback);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
+                }
+            }
+            else
+            {
+                // Can we use a memory mapped file?
+                if (_minMemoryMappedContentLength>0 && 
+                    resource.length()>_minMemoryMappedContentLength &&
+                    resource instanceof FileResource)
+                {
+                    ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+                    ((HttpOutput)out).sendContent(buffer);
+                }
+                else  // Do a blocking write of a channel (if available) or input stream
+                {
+                    ReadableByteChannel channel= resource.getReadableByteChannel();
+                    if (channel!=null)
+                        ((HttpOutput)out).sendContent(channel);
+                    else
+                        ((HttpOutput)out).sendContent(resource.getInputStream());
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
+        throws IOException
+    {
+        if (_directory)
+        {
+            String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
+            response.setContentType("text/html; charset=UTF-8");
+            response.getWriter().println(listing);
+        }
+        else
+            response.sendError(HttpStatus.FORBIDDEN_403);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the response headers.
+     * This method is called to set the response headers such as content type and content length.
+     * May be extended to add additional headers.
+     * @param response
+     * @param resource
+     * @param mimeType
+     */
+    protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+    {
+        if (mimeType!=null)
+            response.setContentType(mimeType);
+
+        long length=resource.length();
+
+        if (response instanceof Response)
+        {
+            HttpFields fields = ((Response)response).getHttpFields();
+
+            if (length>0)
+                ((Response)response).setLongContentLength(length);
+
+            if (_cacheControl!=null)
+                fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
+        }
+        else
+        {
+            if (length>Integer.MAX_VALUE)
+                response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
+            else if (length>0)
+                response.setContentLength((int)length);
+
+            if (_cacheControl!=null)
+                response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
+        }
+    }
+}