org.eclipse.jetty.ee8.servlet.DefaultServlet Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee8.servlet;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
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.ee8.nested.ContextHandler;
import org.eclipse.jetty.ee8.nested.ResourceService;
import org.eclipse.jetty.ee8.nested.ResourceService.WelcomeFactory;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.content.CachingHttpContentFactory;
import org.eclipse.jetty.http.content.FileMappingHttpContentFactory;
import org.eclipse.jetty.http.content.HttpContent;
import org.eclipse.jetty.http.content.PreCompressedHttpContentFactory;
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
import org.eclipse.jetty.http.content.ValidatingCachingHttpContentFactory;
import org.eclipse.jetty.http.content.VirtualHttpContentFactory;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The default servlet.
*
* 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. :
*
* 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 false.
*
* 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.
*
* baseResource 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.
*
* relativeBaseResource
* 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 baseResource
*
* 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-ee8.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"
*
* encodingHeaderCacheSize
* Max entries in a cache of ACCEPT-ENCODING headers.
*
*/
public class DefaultServlet extends HttpServlet implements WelcomeFactory {
public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default.";
private static final Logger LOG = LoggerFactory.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 _baseResource;
private CachingHttpContentFactory _cachingContentFactory;
private MimeTypes _mimeTypes;
private String[] _welcomes;
private ResourceFactory.Closeable _resourceFactory;
private Resource _styleSheet;
private boolean _useFileMappedBuffer = false;
private String _relativeBaseResource;
private ServletHandler _servletHandler;
public DefaultServlet(ResourceService resourceService) {
_resourceService = resourceService;
}
public DefaultServlet() {
this(new ResourceService());
}
@Override
public void init() throws UnavailableException {
_servletContext = getServletContext();
_contextHandler = initContextHandler(_servletContext);
_resourceFactory = ResourceFactory.closeable();
_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"), _resourceService.getPrecompressedFormats()));
_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);
_relativeBaseResource = getInitParameter("relativeBaseResource", "relativeResourceBase");
String br = getInitParameter("baseResource", "resourceBase");
if (br == null) {
_baseResource = _contextHandler.getBaseResource();
} else {
if (_relativeBaseResource != null)
throw new UnavailableException("baseResource & relativeBaseResource");
try {
_baseResource = _contextHandler.newResource(br);
} catch (Exception e) {
LOG.warn("Unable to create baseResource from {}", br, e);
throw new UnavailableException(e.toString());
}
}
String stylesheet = getInitParameter("stylesheet");
try {
if (stylesheet != null) {
_styleSheet = _resourceFactory.newResource(stylesheet);
if (Resources.missing(_styleSheet)) {
LOG.warn("Stylesheet {} does not exist", stylesheet);
_styleSheet = null;
}
}
if (_styleSheet == null) {
_styleSheet = _contextHandler.getServer().getDefaultStyleSheet();
}
} catch (Exception e) {
if (LOG.isDebugEnabled())
LOG.warn("Unable to use stylesheet: {}", stylesheet, e);
else
LOG.warn("Unable to use stylesheet: {} - {}", stylesheet, e.toString());
}
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));
// Create HttpContentFactory if none already set
if (_resourceService.getHttpContentFactory() == null) {
// Try to get factory from ServletContext attribute.
HttpContent.Factory contentFactory = (HttpContent.Factory) getServletContext().getAttribute(HttpContent.Factory.class.getName());
if (contentFactory == null) {
contentFactory = new ResourceHttpContentFactory(_baseResource, _mimeTypes) {
@Override
protected Resource resolve(String pathInContext) {
return DefaultServlet.this.resolve(pathInContext);
}
};
if (_useFileMappedBuffer)
contentFactory = new FileMappingHttpContentFactory(contentFactory);
contentFactory = new VirtualHttpContentFactory(contentFactory, _styleSheet, "text/css");
contentFactory = new PreCompressedHttpContentFactory(contentFactory, _resourceService.getPrecompressedFormats());
int maxCacheSize = getInitInt("maxCacheSize", -2);
int maxCachedFileSize = getInitInt("maxCachedFileSize", -2);
int maxCachedFiles = getInitInt("maxCachedFiles", -2);
long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2;
if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2) {
ByteBufferPool bufferPool = getByteBufferPool(_contextHandler);
_cachingContentFactory = new ValidatingCachingHttpContentFactory(contentFactory, (cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool);
contentFactory = _cachingContentFactory;
if (maxCacheSize >= 0)
_cachingContentFactory.setMaxCacheSize(maxCacheSize);
if (maxCachedFileSize >= -1)
_cachingContentFactory.setMaxCachedFileSize(maxCachedFileSize);
if (maxCachedFiles >= -1)
_cachingContentFactory.setMaxCachedFiles(maxCachedFiles);
}
}
_resourceService.setHttpContentFactory(contentFactory);
}
_resourceService.setWelcomeFactory(this);
List gzipEquivalentFileExtensions = new ArrayList<>();
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
if (otherGzipExtensions != null) {
//comma separated list
StringTokenizer tok = new StringTokenizer(otherGzipExtensions, ",", false);
while (tok.hasMoreTokens()) {
String s = tok.nextToken().trim();
gzipEquivalentFileExtensions.add((s.charAt(0) == '.' ? s : "." + s));
}
} else {
//.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
gzipEquivalentFileExtensions.add(".svgz");
}
_resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions);
_servletHandler = _contextHandler.getChildHandlerByClass(ServletHandler.class);
if (LOG.isDebugEnabled())
LOG.debug("resource base = {}", _baseResource);
}
private static ByteBufferPool getByteBufferPool(ContextHandler contextHandler) {
if (contextHandler == null)
return ByteBufferPool.NON_POOLING;
Server server = contextHandler.getServer();
if (server == null)
return ByteBufferPool.NON_POOLING;
return server.getByteBufferPool();
}
private String getInitParameter(String name, String... deprecated) {
String value = getInitParameter(name);
if (value != null)
return value;
for (String d : deprecated) {
value = getInitParameter(d);
if (value != null) {
LOG.warn("Deprecated {} used instead of {}", d, name);
return value;
}
}
return null;
}
private CompressedContentFormat[] parsePrecompressedFormats(String precompressed, Boolean gzip, CompressedContentFormat[] dft) {
if (precompressed == null && gzip == null) {
return dft;
}
List 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 == Boolean.TRUE && !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 == Boolean.TRUE) {
// gzip handling is for backwards compatibility with older Jetty
ret.add(CompressedContentFormat.GZIP);
}
return ret.toArray(new CompressedContentFormat[ret.size()]);
}
/**
* Compute the field _contextHandler.
* 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.APIContext scontext = ContextHandler.getCurrentContext();
if (scontext == null) {
if (servletContext instanceof ContextHandler.APIContext)
return ((ContextHandler.APIContext) servletContext).getContextHandler();
else
throw new IllegalArgumentException("The servletContext " + servletContext + " " + servletContext.getClass().getName() + " is not " + ContextHandler.APIContext.class.getName());
} else
return ContextHandler.getCurrentContext().getContextHandler();
}
/**
*
* Returns a {@code String} containing the value of the named initialization parameter, or null if the parameter does not exist.
*
*
*
* Parameter lookup first checks the {@link ServletContext#getInitParameter(String)} for the
* parameter prefixed with {@code org.eclipse.jetty.servlet.Default.}, then checks
* {@link javax.servlet.ServletConfig#getInitParameter(String)} for the actual value
*
*
* @param name a {@code String} specifying the name of the initialization parameter
* @return a {@code String} containing the value of the initialization parameter
*/
@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) {
String value = getInitParameter(name);
if (value == null || value.length() == 0)
return null;
return (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1"));
}
private boolean getInitBoolean(String name, boolean dft) {
return Optional.ofNullable(getInitBoolean(name)).orElse(dft);
}
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 subUriPath The path to find a resource for.
* @return The resource to serve.
*/
protected Resource resolve(String subUriPath) {
subUriPath = URIUtil.encodePath(subUriPath);
Resource r = null;
if (_relativeBaseResource != null)
subUriPath = URIUtil.addPaths(_relativeBaseResource, subUriPath);
try {
if (_baseResource != null) {
r = _baseResource.resolve(subUriPath);
} else if (_servletContext instanceof ContextHandler.APIContext) {
r = _contextHandler.getResource(subUriPath);
} else {
return null;
}
if (LOG.isDebugEnabled())
LOG.debug("Resource {}={}", subUriPath, r);
} catch (IOException e) {
LOG.trace("IGNORED", e);
}
if (Resources.missing(r) && subUriPath.endsWith("/jetty-dir.css"))
r = _styleSheet;
return r;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (!_resourceService.doGet(request, response))
response.sendError(404);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
}
@Override
public void destroy() {
if (_cachingContentFactory != null)
_cachingContentFactory.flushCache();
super.destroy();
IO.close(_resourceFactory);
}
@Override
public String getWelcomeFile(String pathInContext) {
if (_welcomes == null)
return null;
String welcomeServlet = null;
for (String s : _welcomes) {
String welcomeInContext = URIUtil.addPaths(pathInContext, s);
Resource welcome = resolve(welcomeInContext);
if (welcome != null && welcome.exists())
return welcomeInContext;
if ((_welcomeServlets || _welcomeExactServlets) && welcomeServlet == null) {
ServletHandler.MappedServlet entry = _servletHandler.getMappedServlet(welcomeInContext);
if (entry != null && entry.getServletHolder().getServletInstance() != this && (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcomeInContext))))
welcomeServlet = welcomeInContext;
}
}
return welcomeServlet;
}
}