blob: 5e2ac37ffc932f5c4a33e5cd74727344a4d6fd92 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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.servlet;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.server.ResourceCache;
import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.server.handler.ContextHandler;
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.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
/**
* The default servlet.
* <p>
* This servlet, normally mapped to /, provides the handling for static
* content, OPTION and TRACE methods for the context.
* The following initParameters are supported, these can be set either
* on the servlet itself or as ServletContext initParameters with a prefix
* of org.eclipse.jetty.servlet.Default. :
* <pre>
* acceptRanges If true, range requests and responses are
* supported
*
* dirAllowed If true, directory listings are returned if no
* welcome file is found. Else 403 Forbidden.
*
* welcomeServlets If true, attempt to dispatch to welcome files
* that are servlets, but only after no matching static
* resources could be found. If false, then a welcome
* file must exist on disk. If "exact", then exact
* servlet matches are supported without an existing file.
* Default is true.
*
* This must be false if you want directory listings,
* but have index.jsp in your welcome file list.
*
* redirectWelcome If true, welcome files are redirected rather than
* forwarded to.
*
* gzip If set to true, then static content will be served as
* gzip content encoded if a matching resource is
* found ending with ".gz"
*
* resourceBase Set to replace the context resource base
*
* resourceCache If set, this is a context attribute name, which the servlet
* will use to look for a shared ResourceCache instance.
*
* relativeResourceBase
* Set with a pathname relative to the base of the
* servlet context root. Useful for only serving static content out
* of only specific subdirectories.
*
* pathInfoOnly If true, only the path info will be applied to the resourceBase
*
* stylesheet Set with the location of an optional stylesheet that will be used
* to decorate the directory listing html.
*
* etags If True, weak etags will be generated and handled.
*
* maxCacheSize The maximum total size of the cache or 0 for no cache.
* maxCachedFileSize The maximum size of a file to cache
* maxCachedFiles The maximum number of files to cache
*
* useFileMappedBuffer
* If set to true, it will use mapped file buffer to serve static content
* when using NIO connector. Setting this value to false means that
* a direct buffer will be used instead of a mapped file buffer.
* This is set to false by default by this class, but may be overridden
* by eg webdefault.xml
*
* cacheControl If set, all static content will have this value set as the cache-control
* header.
*
* otherGzipFileExtensions
* Other file extensions that signify that a file is gzip compressed. Eg ".svgz"
*
*
* </pre>
*
*/
public class DefaultServlet extends HttpServlet implements ResourceFactory
{
private static final Logger LOG = Log.getLogger(DefaultServlet.class);
private static final long serialVersionUID = 4930458713846881193L;
private final ResourceService _resourceService;
private ServletContext _servletContext;
private ContextHandler _contextHandler;
private boolean _welcomeServlets=false;
private boolean _welcomeExactServlets=false;
private Resource _resourceBase;
private ResourceCache _cache;
private MimeTypes _mimeTypes;
private String[] _welcomes;
private Resource _stylesheet;
private boolean _useFileMappedBuffer=false;
private String _relativeResourceBase;
private ServletHandler _servletHandler;
private ServletHolder _defaultHolder;
public DefaultServlet()
{
_resourceService = new ResourceService()
{
@Override
protected String getWelcomeFile(String pathInContext)
{
if (_welcomes==null)
return null;
String welcome_servlet=null;
for (int i=0;i<_welcomes.length;i++)
{
String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
Resource welcome=getResource(welcome_in_context);
if (welcome!=null && welcome.exists())
return _welcomes[i];
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
{
MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context);
if (entry!=null && entry.getResource()!=_defaultHolder &&
(_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context))))
welcome_servlet=welcome_in_context;
}
}
return welcome_servlet;
}
@Override
protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
};
}
/* ------------------------------------------------------------ */
@Override
public void init()
throws UnavailableException
{
_servletContext=getServletContext();
_contextHandler = initContextHandler(_servletContext);
_mimeTypes = _contextHandler.getMimeTypes();
_welcomes = _contextHandler.getWelcomeFiles();
if (_welcomes==null)
_welcomes=new String[] {"index.html","index.jsp"};
_resourceService.setAcceptRanges(getInitBoolean("acceptRanges",_resourceService.isAcceptRanges()));
_resourceService.setDirAllowed(getInitBoolean("dirAllowed",_resourceService.isDirAllowed()));
_resourceService.setRedirectWelcome(getInitBoolean("redirectWelcome",_resourceService.isRedirectWelcome()));
_resourceService.setGzip(getInitBoolean("gzip",_resourceService.isGzip()));
_resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly()));
_resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags()));
if ("exact".equals(getInitParameter("welcomeServlets")))
{
_welcomeExactServlets=true;
_welcomeServlets=false;
}
else
_welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
_useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
_relativeResourceBase = getInitParameter("relativeResourceBase");
String rb=getInitParameter("resourceBase");
if (rb!=null)
{
if (_relativeResourceBase!=null)
throw new UnavailableException("resourceBase & relativeResourceBase");
try{_resourceBase=_contextHandler.newResource(rb);}
catch (Exception e)
{
LOG.warn(Log.EXCEPTION,e);
throw new UnavailableException(e.toString());
}
}
String css=getInitParameter("stylesheet");
try
{
if(css!=null)
{
_stylesheet = Resource.newResource(css);
if(!_stylesheet.exists())
{
LOG.warn("!" + css);
_stylesheet = null;
}
}
if(_stylesheet == null)
{
_stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
}
}
catch(Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
String cc=getInitParameter("cacheControl");
if (cc!=null)
_resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc));
String resourceCache = getInitParameter("resourceCache");
int max_cache_size=getInitInt("maxCacheSize", -2);
int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
int max_cached_files=getInitInt("maxCachedFiles", -2);
if (resourceCache!=null)
{
if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
if (_relativeResourceBase!=null || _resourceBase!=null)
throw new UnavailableException("resourceCache specified with resource bases");
_cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
}
try
{
if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
{
_cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.isGzip());
if (max_cache_size>=0)
_cache.setMaxCacheSize(max_cache_size);
if (max_cached_file_size>=-1)
_cache.setMaxCachedFileSize(max_cached_file_size);
if (max_cached_files>=-1)
_cache.setMaxCachedFiles(max_cached_files);
_servletContext.setAttribute(resourceCache==null?"resourceCache":resourceCache,_cache);
}
}
catch (Exception e)
{
LOG.warn(Log.EXCEPTION,e);
throw new UnavailableException(e.toString());
}
HttpContent.Factory contentFactory=_cache;
if (contentFactory==null)
{
contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip());
if (resourceCache!=null)
_servletContext.setAttribute(resourceCache,contentFactory);
}
_resourceService.setContentFactory(contentFactory);
List<String> gzip_equivalent_file_extensions = new ArrayList<String>();
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
if (otherGzipExtensions != null)
{
//comma separated list
StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
while (tok.hasMoreTokens())
{
String s = tok.nextToken().trim();
gzip_equivalent_file_extensions.add((s.charAt(0)=='.'?s:"."+s));
}
}
else
{
//.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
gzip_equivalent_file_extensions.add(".svgz");
}
_resourceService.setGzipEquivalentFileExtensions(gzip_equivalent_file_extensions);
_servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
for (ServletHolder h :_servletHandler.getServlets())
if (h.getServletInstance()==this)
_defaultHolder=h;
if (LOG.isDebugEnabled())
LOG.debug("resource base = "+_resourceBase);
}
/**
* Compute the field _contextHandler.<br>
* In the case where the DefaultServlet is deployed on the HttpService it is likely that
* this method needs to be overwritten to unwrap the ServletContext facade until we reach
* the original jetty's ContextHandler.
* @param servletContext The servletContext of this servlet.
* @return the jetty's ContextHandler for this servletContext.
*/
protected ContextHandler initContextHandler(ServletContext servletContext)
{
ContextHandler.Context scontext=ContextHandler.getCurrentContext();
if (scontext==null)
{
if (servletContext instanceof ContextHandler.Context)
return ((ContextHandler.Context)servletContext).getContextHandler();
else
throw new IllegalArgumentException("The servletContext " + servletContext + " " +
servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
}
else
return ContextHandler.getCurrentContext().getContextHandler();
}
/* ------------------------------------------------------------ */
@Override
public String getInitParameter(String name)
{
String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
if (value==null)
value=super.getInitParameter(name);
return value;
}
/* ------------------------------------------------------------ */
private boolean getInitBoolean(String name, boolean dft)
{
String value=getInitParameter(name);
if (value==null || value.length()==0)
return dft;
return (value.startsWith("t")||
value.startsWith("T")||
value.startsWith("y")||
value.startsWith("Y")||
value.startsWith("1"));
}
/* ------------------------------------------------------------ */
private int getInitInt(String name, int dft)
{
String value=getInitParameter(name);
if (value==null)
value=getInitParameter(name);
if (value!=null && value.length()>0)
return Integer.parseInt(value);
return dft;
}
/* ------------------------------------------------------------ */
/** get Resource to serve.
* Map a path to a resource. The default implementation calls
* HttpContext.getResource but derived servlets may provide
* their own mapping.
* @param pathInContext The path to find a resource for.
* @return The resource to serve.
*/
@Override
public Resource getResource(String pathInContext)
{
Resource r=null;
if (_relativeResourceBase!=null)
pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
try
{
if (_resourceBase!=null)
{
r = _resourceBase.addPath(pathInContext);
if (!_contextHandler.checkAlias(pathInContext,r))
r=null;
}
else if (_servletContext instanceof ContextHandler.Context)
{
r = _contextHandler.getResource(pathInContext);
}
else
{
URL u = _servletContext.getResource(pathInContext);
r = _contextHandler.newResource(u);
}
if (LOG.isDebugEnabled())
LOG.debug("Resource "+pathInContext+"="+r);
}
catch (IOException e)
{
LOG.ignore(e);
}
if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
r=_stylesheet;
return r;
}
/* ------------------------------------------------------------ */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
_resourceService.doGet(request,response);
}
/* ------------------------------------------------------------ */
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doGet(request,response);
}
/* ------------------------------------------------------------ */
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
/* ------------------------------------------------------------ */
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.Servlet#destroy()
*/
@Override
public void destroy()
{
if (_cache!=null)
_cache.flushCache();
super.destroy();
}
}