// // ======================================================================== // Copyright (c) 1995-2017 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.CompressedContentFormat; 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.CachedContentFactory; import org.eclipse.jetty.server.ResourceContentFactory; import org.eclipse.jetty.server.ResourceService; import org.eclipse.jetty.server.ResourceService.WelcomeFactory; 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" (default false) * (deprecated: use precompressed) * * precompressed If set to a comma separated list of encoding types (that may be * listed in a requests Accept-Encoding header) to file * extension mappings to look for and serve. For example: * "br=.br,gzip=.gz,bzip2=.bz". * If set to a boolean True, then a default set of compressed formats * will be used, otherwise no precompressed formats. * * 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 already compressed. Eg ".svgz" * * * </pre> * */ public class DefaultServlet extends HttpServlet implements ResourceFactory, WelcomeFactory { public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; 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 CachedContentFactory _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 resourceService) { _resourceService=resourceService; } /* ------------------------------------------------------------ */ public DefaultServlet() { this(new ResourceService()); } /* ------------------------------------------------------------ */ @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.setPrecompressedFormats(parsePrecompressedFormats(getInitParameter("precompressed"), getInitBoolean("gzip", false))); _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); } int encodingHeaderCacheSize = getInitInt("encodingHeaderCacheSize", -1); if (encodingHeaderCacheSize >= 0) _resourceService.setEncodingCacheSize(encodingHeaderCacheSize); 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=(CachedContentFactory)_servletContext.getAttribute(resourceCache); } try { if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2)) { _cache = new CachedContentFactory(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.getPrecompressedFormats()); 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.ContentFactory contentFactory=_cache; if (contentFactory==null) { contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats()); if (resourceCache!=null) _servletContext.setAttribute(resourceCache,contentFactory); } _resourceService.setContentFactory(contentFactory); _resourceService.setWelcomeFactory(this); 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); } private CompressedContentFormat[] parsePrecompressedFormats(String precompressed, boolean gzip) { List<CompressedContentFormat> ret = new ArrayList<>(); if (precompressed != null && precompressed.indexOf('=') > 0) { for (String pair : precompressed.split(",")) { String[] setting = pair.split("="); String encoding = setting[0].trim(); String extension = setting[1].trim(); ret.add(new CompressedContentFormat(encoding,extension)); if (gzip && !ret.contains(CompressedContentFormat.GZIP)) ret.add(CompressedContentFormat.GZIP); } } else if (precompressed != null) { if (Boolean.parseBoolean(precompressed)) { ret.add(CompressedContentFormat.BR); ret.add(CompressedContentFormat.GZIP); } } else if (gzip) { // gzip handling is for backwards compatibility with older Jetty ret.add(CompressedContentFormat.GZIP); } return ret.toArray(new CompressedContentFormat[ret.size()]); } /** * 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(CONTEXT_INIT+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(); } /* ------------------------------------------------------------ */ @Override public 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 welcome_in_context; if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) { MappedResource<ServletHolder> entry=_servletHandler.getMappedServlet(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; } }