view src/org/eclipse/jetty/server/handler/ResourceHandler.java @ 1000:32d4b569567c

simplify handle()
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 19 Oct 2016 04:22:51 -0600
parents 7d28be82ab75
children 6939226e0ac4
line wrap: on
line source

//
//  ========================================================================
//  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 javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.AbstractHttpConnection;
import javax.servlet.RequestDispatcher;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 final class ResourceHandler extends HandlerWrapper
{
	private static final Logger LOG = LoggerFactory.getLogger(ResourceHandler.class);

	Resource _baseResource;
	Resource _defaultStylesheet;
	Resource _stylesheet;
	String[] _welcomeFiles={"index.html"};
	MimeTypes _mimeTypes = new MimeTypes();
	ByteArrayBuffer _cacheControl;
	boolean _aliases;
	boolean _directory;

	/* ------------------------------------------------------------ */
	public ResourceHandler()
	{
		
	}

	/* ------------------------------------------------------------ */
	public MimeTypes getMimeTypes()
	{
		return _mimeTypes;
	}

	/* ------------------------------------------------------------ */
	public void setMimeTypes(MimeTypes mimeTypes)
	{
		_mimeTypes = mimeTypes;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return True if resource aliases are allowed.
	 */
	public boolean isAliases()
	{
		return _aliases;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
	 * Allowing aliases can significantly increase security vulnerabilities.
	 * If this handler is deployed inside a ContextHandler, then the
	 * {@link ContextHandler#isAliases()} takes precedent.
	 * @param aliases True if aliases are supported.
	 */
	public void setAliases(boolean aliases)
	{
		_aliases = aliases;
	}

	/* ------------------------------------------------------------ */
	/** 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;
	}

	/* ------------------------------------------------------------ */
	@Override
	public void doStart()
	throws Exception
	{
		if (!_aliases && !FileResource.getCheckAliases())
			throw new IllegalStateException("Alias checking disabled");

		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);
		}
	}
	
	private Resource getStylesheet()
	{
		if(_stylesheet != null)
		{
			return _stylesheet;
		}
		else
		{
			if(_defaultStylesheet == null)
			{
				try
				{
					_defaultStylesheet =  Resource.newResource(this.getClass().getResource("/org/eclipse/jetty/jetty-dir.css"));
				}
				catch(IOException e)
				{
					LOG.warn(e.toString());
					LOG.debug("",e);
				}	 
			}
			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.toString());
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return the cacheControl header to set on all static content.
	 */
	public String getCacheControl()
	{
		return _cacheControl.toString();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param cacheControl the cacheControl header to set on all static content.
	 */
	public void setCacheControl(String cacheControl)
	{
		_cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
	}

	private Resource getResource(Request request) throws MalformedURLException
	{
		String path = request.getPathInfo();
		if (path==null || !path.startsWith("/"))
			throw new MalformedURLException(path);

		Resource base = _baseResource;
		if (base==null)
		{
			ContextHandler ch = request._contextHandler;
			if (ch==null)
				return null;
			base = ch.getBaseResource();
			if (base==null)
				return null;
		}

		try
		{
			path = URIUtil.canonicalPath(path);
			return base.addPath(path);
		}
		catch(Exception e)
		{
			LOG.trace("",e);
		}

		return null;
	}


	/* ------------------------------------------------------------ */
	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;
	}

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

		boolean skipContentBody = false;

		if(!HttpMethods.GET.equals(request.getMethod()))
		{
			if(!HttpMethods.HEAD.equals(request.getMethod()))
			{
				//try another handler
				super.handle(target, request, response);
				return;
			}
			skipContentBody = true;
		}
		
		Resource resource = getResource(request);
		
		if (resource==null || !resource.exists())
		{
			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, request, response);
				return;
			}
		}
			
		if (!_aliases && resource.getAlias()!=null)
		{
			LOG.info(resource+" aliased to "+resource.getAlias());
			return;
		}

		// We are going to serve something
		request.setHandled(true);

		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);
				request.setHandled(true);
				return;
			}
		}

		// set some headers
		long last_modified=resource.lastModified();
		if (last_modified>0)
		{
			long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
			if (if_modified>0 && last_modified/1000<=if_modified/1000)
			{
				response.setStatus(HttpStatus.NOT_MODIFIED_304);
				return;
			}
		}

		Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
		if (mime==null)
			mime=_mimeTypes.getMimeByExtension(request.getPathInfo());

		// set the headers
		doResponseHeaders(response,resource,mime!=null?mime.toString():null);
		response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
		
		if(skipContentBody)
			return;
		// Send the content
		OutputStream out =null;
		try {out = response.getOutputStream();}
		catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}

		// See if a short direct method can be used?
		if (out instanceof AbstractHttpConnection.Output)
		{
			// TODO file mapped buffers
			((AbstractHttpConnection.Output)out).sendContent(resource.getInputStream());
		}
		else
		{
			// Write content normally
			resource.writeTo(out,0,resource.length());
		}
	}

	/* ------------------------------------------------------------ */
	protected void doDirectory(Request 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)
				fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);

			if (_cacheControl!=null)
				fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
		}
		else
		{
			if (length>0)
				response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(length));

			if (_cacheControl!=null)
				response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
		}

	}
}