diff --git a/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/web/DefaultServlet.java b/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/web/DefaultServlet.java
index 1f40600ae0..a6f0f6c081 100644
--- a/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/web/DefaultServlet.java
+++ b/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/web/DefaultServlet.java
@@ -1,21 +1,6 @@
-/*
- * Copyright 2022 OPS4J.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
//
// ========================================================================
-// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
+// 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
@@ -26,226 +11,300 @@
// ========================================================================
//
-package org.ops4j.pax.web.service.jetty.internal.web;
+package org.eclipse.jetty.ee10.servlet;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.file.InvalidPathException;
+import java.time.Duration;
import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
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 java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.UnavailableException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.http.CompressedContentFormat;
-import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MimeTypes;
-import org.eclipse.jetty.http.PreEncodedHttpField;
-import org.eclipse.jetty.http.pathmap.MatchedResource;
-import org.eclipse.jetty.server.CachedContentFactory;
-import org.eclipse.jetty.server.ResourceContentFactory;
+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.ByteBufferInputStream;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.Context;
+import org.eclipse.jetty.server.HttpStream;
+import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ResourceService;
-import org.eclipse.jetty.server.ResourceService.WelcomeFactory;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.util.Blocker;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+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.
- *
- * 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"
- *
- * encodingHeaderCacheSize
- * Max entries in a cache of ACCEPT-ENCODING headers.
- *
- *
- * Pax Web 8: I had to copy this servlet from Jetty code to change some fields from private to protected.
+ * The default Servlet, normally mapped to {@code /}, that handles static resources.
+ * The following init parameters are supported:
+ *
+ * - acceptRanges
+ * -
+ * Use {@code true} to accept range requests, defaults to {@code true}.
+ *
+ * - baseResource
+ * -
+ * Defaults to the context's baseResource.
+ * The root directory to look for static resources.
+ *
+ * - cacheControl
+ * -
+ * The value of the {@code Cache-Control} header.
+ * If omitted, no {@code Cache-Control} header is generated in responses.
+ * By default is omitted.
+ *
+ * - cacheValidationTime
+ * -
+ * How long in milliseconds a resource is cached.
+ * If omitted, defaults to {@code 1000} ms.
+ * Use {@code -1} to cache forever or {@code 0} to not cache.
+ *
+ * - dirAllowed
+ * -
+ * Use {@code true} to serve directory listing if no welcome file is found.
+ * Otherwise responds with {@code 403 Forbidden}.
+ * Defaults to {@code true}.
+ *
+ * - encodingHeaderCacheSize
+ * -
+ * Max number of cached {@code Accept-Encoding} entries.
+ * Use {@code -1} for the default value (100), {@code 0} for no cache.
+ *
+ * - etags
+ * -
+ * Use {@code true} to generate ETags in responses.
+ * Defaults to {@code false}.
+ *
+ * - maxCachedFiles
+ * -
+ * The max number of cached static resources.
+ * Use {@code -1} for the default value (2048) or {@code 0} for no cache.
+ *
+ * - maxCachedFileSize
+ * -
+ * The max size in bytes of a single cached static resource.
+ * Use {@code -1} for the default value (128 MiB) or {@code 0} for no cache.
+ *
+ * - maxCacheSize
+ * -
+ * The max size in bytes of the cache for static resources.
+ * Use {@code -1} for the default value (256 MiB) or {@code 0} for no cache.
+ *
+ * - otherGzipFileExtensions
+ * -
+ * A comma-separated list of extensions of files whose content is implicitly
+ * gzipped.
+ * Defaults to {@code .svgz}.
+ *
+ * - pathInfoOnly
+ * -
+ * Use {@code true} to use only the request {@code pathInfo} to look for
+ * static resources.
+ * Defaults to {@code false}.
+ *
+ * - precompressed
+ * -
+ * Omitted by default, so that no pre-compressed content will be served.
+ * If set to {@code true}, the default set of pre-compressed formats will be used.
+ * Otherwise can be set to a comma-separated list of {@code encoding=extension} pairs,
+ * such as: {@code br=.br,gzip=.gz,bzip2=.bz}, where {@code encoding} is used as the
+ * value for the {@code Content-Encoding} header.
+ *
+ * - redirectWelcome
+ * -
+ * Use {@code true} to redirect welcome files, otherwise they are forwarded.
+ * Defaults to {@code false}.
+ *
+ * - stylesheet
+ * -
+ * Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}.
+ * The path of a custom stylesheet to style the directory listing HTML.
+ *
+ * - useFileMappedBuffer
+ * -
+ * Use {@code true} to use file mapping to serve static resources.
+ * Defaults to {@code false}.
+ *
+ * - welcomeServlets
+ * -
+ * Use {@code false} to only serve welcome resources from the file system.
+ * Use {@code true} to dispatch welcome resources to a matching Servlet
+ * (for example mapped to {@code *.welcome}), when the welcome resources
+ * does not exist on file system.
+ * Use {@code exact} to dispatch welcome resource to a Servlet whose mapping
+ * is exactly the same as the welcome resource (for example {@code /index.welcome}),
+ * when the welcome resources does not exist on file system.
+ * Defaults to {@code false}.
+ *
+ *
*/
-// CHECKSTYLE:OFF
-public class DefaultServlet extends HttpServlet implements ResourceFactory, WelcomeFactory
+public class DefaultServlet extends HttpServlet
{
- 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;
-
- protected final ResourceService _resourceService;
- private ServletContext _servletContext;
- private ContextHandler _contextHandler;
-
- private boolean _welcomeServlets = false;
- private boolean _welcomeExactServlets = false;
-
- private Resource _resourceBase;
- protected CachedContentFactory _cache;
-
- private MimeTypes _mimeTypes;
- protected String[] _welcomes;
- private Resource _stylesheet;
- private boolean _useFileMappedBuffer = false;
- private String _relativeResourceBase;
- private ServletHandler _servletHandler;
-
- public DefaultServlet(ResourceService resourceService)
- {
- _resourceService = resourceService;
- }
+ private ServletContextHandler _contextHandler;
+ private ServletResourceService _resourceService;
+ private WelcomeServletMode _welcomeServletMode;
+ private Resource _baseResource;
+ private boolean _isPathInfoOnly;
- public DefaultServlet()
+ public ResourceService getResourceService()
{
- this(new ResourceService());
+ return _resourceService;
}
@Override
- public void init()
- throws UnavailableException
+ public void init() throws ServletException
{
- _servletContext = getServletContext();
- _contextHandler = initContextHandler(_servletContext);
-
- _mimeTypes = _contextHandler.getMimeTypes();
-
- _welcomes = _contextHandler.getWelcomeFiles();
- if (_welcomes == null)
- _welcomes = new String[]{"index.html", "index.jsp"};
+ _contextHandler = initContextHandler(getServletContext());
+ _resourceService = new ServletResourceService(_contextHandler);
+ _resourceService.setWelcomeFactory(_resourceService);
+ _baseResource = _contextHandler.getBaseResource();
- _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);
-
- _relativeResourceBase = getInitParameter("relativeResourceBase");
-
- String rb = getInitParameter("resourceBase");
+ String rb = getInitParameter("baseResource", "resourceBase");
if (rb != null)
{
- if (_relativeResourceBase != null)
- throw new UnavailableException("resourceBase & relativeResourceBase");
try
{
- _resourceBase = _contextHandler.newResource(rb);
+ _baseResource = Objects.requireNonNull(_contextHandler.newResource(rb));
}
catch (Exception e)
{
- LOG.warn("Unable to create resourceBase from {}", rb, e);
+ LOG.warn("Unable to create baseResource from {}", rb, e);
throw new UnavailableException(e.toString());
}
}
- String css = getInitParameter("stylesheet");
- try
+ List precompressedFormats = parsePrecompressedFormats(getInitParameter("precompressed"),
+ getInitBoolean("gzip"), _resourceService.getPrecompressedFormats());
+
+ // Try to get factory from ServletContext attribute.
+ HttpContent.Factory contentFactory = (HttpContent.Factory)getServletContext().getAttribute(HttpContent.Factory.class.getName());
+ if (contentFactory == null)
{
- if (css != null)
+ MimeTypes mimeTypes = _contextHandler.getMimeTypes();
+ ResourceFactory resourceFactory = _baseResource != null ? ResourceFactory.of(_baseResource) : this::getResource;
+ contentFactory = new ResourceHttpContentFactory(resourceFactory, mimeTypes);
+
+ // Use the servers default stylesheet unless there is one explicitly set by an init param.
+ Resource styleSheet = _contextHandler.getServer().getDefaultStyleSheet();
+ String stylesheetParam = getInitParameter("stylesheet");
+ if (stylesheetParam != null)
{
- _stylesheet = Resource.newResource(css);
- if (!_stylesheet.exists())
+ try
+ {
+ HttpContent styleSheetContent = contentFactory.getContent(stylesheetParam);
+ Resource s = styleSheetContent == null ? null : styleSheetContent.getResource();
+ if (Resources.isReadableFile(s))
+ styleSheet = s;
+ else
+ LOG.warn("Stylesheet {} does not exist", stylesheetParam);
+ }
+ catch (Exception e)
{
- LOG.warn("!{}", css);
- _stylesheet = null;
+ if (LOG.isDebugEnabled())
+ LOG.warn("Unable to use stylesheet: {}", stylesheetParam, e);
+ else
+ LOG.warn("Unable to use stylesheet: {} - {}", stylesheetParam, e.toString());
}
}
- if (_stylesheet == null)
+
+ if (getInitBoolean("useFileMappedBuffer", false))
+ contentFactory = new FileMappingHttpContentFactory(contentFactory);
+
+ contentFactory = new VirtualHttpContentFactory(contentFactory, styleSheet, "text/css");
+ contentFactory = new PreCompressedHttpContentFactory(contentFactory, precompressedFormats);
+
+ 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)
{
- _stylesheet = ResourceHandler.getDefaultStylesheet();
+ ByteBufferPool bufferPool = getByteBufferPool(_contextHandler);
+ ValidatingCachingHttpContentFactory cached = new ValidatingCachingHttpContentFactory(contentFactory,
+ (cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool);
+ contentFactory = cached;
+ if (maxCacheSize >= 0)
+ cached.setMaxCacheSize(maxCacheSize);
+ if (maxCachedFileSize >= 0)
+ cached.setMaxCachedFileSize(maxCachedFileSize);
+ if (maxCachedFiles >= 0)
+ cached.setMaxCachedFiles(maxCachedFiles);
}
}
- catch (Exception e)
+ _resourceService.setHttpContentFactory(contentFactory);
+
+ if (_contextHandler.getWelcomeFiles() == null)
+ _contextHandler.setWelcomeFiles(new String[]{"index.html", "index.jsp"});
+
+ _resourceService.setAcceptRanges(getInitBoolean("acceptRanges", _resourceService.isAcceptRanges()));
+ _resourceService.setDirAllowed(getInitBoolean("dirAllowed", _resourceService.isDirAllowed()));
+ boolean redirectWelcome = getInitBoolean("redirectWelcome", false);
+ _resourceService.setWelcomeMode(redirectWelcome ? ResourceService.WelcomeMode.REDIRECT : ResourceService.WelcomeMode.SERVE);
+ _resourceService.setPrecompressedFormats(precompressedFormats);
+ _resourceService.setEtags(getInitBoolean("etags", _resourceService.isEtags()));
+
+ _isPathInfoOnly = getInitBoolean("pathInfoOnly", _isPathInfoOnly);
+
+ _welcomeServletMode = WelcomeServletMode.NONE;
+ String welcomeServlets = getInitParameter("welcomeServlets");
+ if (welcomeServlets != null)
{
- if (LOG.isDebugEnabled())
- LOG.warn("Unable to use stylesheet: {}", css, e);
- else
- LOG.warn("Unable to use stylesheet: {} - {}", css, e.toString());
+ welcomeServlets = welcomeServlets.toLowerCase(Locale.ENGLISH);
+ _welcomeServletMode = switch (welcomeServlets)
+ {
+ case "true" -> WelcomeServletMode.MATCH;
+ case "exact" -> WelcomeServletMode.EXACT;
+ default -> WelcomeServletMode.NONE;
+ };
}
int encodingHeaderCacheSize = getInitInt("encodingHeaderCacheSize", -1);
@@ -254,50 +313,7 @@ public void init()
String cc = getInitParameter("cacheControl");
if (cc != null)
- _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc));
-
- String resourceCache = getInitParameter("resourceCache");
- int maxCacheSize = getInitInt("maxCacheSize", -2);
- int maxCachedFileSize = getInitInt("maxCachedFileSize", -2);
- int maxCachedFiles = getInitInt("maxCachedFiles", -2);
- if (resourceCache != null)
- {
- if (maxCacheSize != -1 || maxCachedFileSize != -2 || maxCachedFiles != -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 && (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2))
- {
- _cache = new CachedContentFactory(null, this, _mimeTypes, _useFileMappedBuffer, _resourceService.isEtags(), _resourceService.getPrecompressedFormats());
- if (maxCacheSize >= 0)
- _cache.setMaxCacheSize(maxCacheSize);
- if (maxCachedFileSize >= -1)
- _cache.setMaxCachedFileSize(maxCachedFileSize);
- if (maxCachedFiles >= -1)
- _cache.setMaxCachedFiles(maxCachedFiles);
- _servletContext.setAttribute(resourceCache == null ? "resourceCache" : resourceCache, _cache);
- }
- }
- catch (Exception e)
- {
- LOG.warn("Unable to setup CachedContentFactory", 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);
+ _resourceService.setCacheControl(cc);
List gzipEquivalentFileExtensions = new ArrayList<>();
String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
@@ -313,18 +329,50 @@ public void init()
}
else
{
- //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
+ // .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 = {}", _resourceBase);
+ {
+ LOG.debug(" .baseResource = {}", _baseResource);
+ LOG.debug(" .resourceService = {}", _resourceService);
+ LOG.debug(" .isPathInfoOnly = {}", _isPathInfoOnly);
+ LOG.debug(" .welcomeServletMode = {}", _welcomeServletMode);
+ }
+ }
+
+ private static ByteBufferPool getByteBufferPool(ContextHandler contextHandler)
+ {
+ if (contextHandler == null)
+ return new ByteBufferPool.NonPooling();
+ Server server = contextHandler.getServer();
+ if (server == null)
+ return new ByteBufferPool.NonPooling();
+ return server.getByteBufferPool();
}
- private CompressedContentFormat[] parsePrecompressedFormats(String precompressed, Boolean gzip, CompressedContentFormat[] dft)
+ private String getInitParameter(String name, String... deprecated)
+ {
+ String value = super.getInitParameter(name);
+ if (value != null)
+ return value;
+
+ for (String d : deprecated)
+ {
+ value = super.getInitParameter(d);
+ if (value != null)
+ {
+ LOG.warn("Deprecated {} used instead of {}", d, name);
+ return value;
+ }
+ }
+
+ return null;
+ }
+
+ private List parsePrecompressedFormats(String precompressed, Boolean gzip, List dft)
{
if (precompressed == null && gzip == null)
{
@@ -356,40 +404,7 @@ 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.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;
+ return ret;
}
private Boolean getInitBoolean(String name)
@@ -412,127 +427,876 @@ private boolean getInitBoolean(String name, boolean 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 pathInContext The path to find a resource for.
- * @return The resource to serve.
- */
+ protected ServletContextHandler initContextHandler(ServletContext servletContext)
+ {
+ if (servletContext instanceof ServletContextHandler.ServletContextApi api)
+ return api.getContext().getServletContextHandler();
+
+ Context context = ContextHandler.getCurrentContext();
+ if (context instanceof ContextHandler.ScopedContext scopedContext)
+ return scopedContext.getContextHandler();
+
+ throw new IllegalArgumentException("The servletContext " + servletContext + " " +
+ servletContext.getClass().getName() + " is not " + ContextHandler.ScopedContext.class.getName());
+ }
+
+ protected boolean isPathInfoOnly()
+ {
+ return _isPathInfoOnly;
+ }
+
@Override
- public Resource getResource(String pathInContext)
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
- Resource r = null;
- if (_relativeResourceBase != null)
- pathInContext = URIUtil.addPaths(_relativeResourceBase, pathInContext);
+ String includedServletPath = (String)req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ boolean included = includedServletPath != null;
+ String encodedPathInContext;
+ if (included)
+ encodedPathInContext = URIUtil.encodePath(getIncludedPathInContext(req, includedServletPath, isPathInfoOnly()));
+ else if (isPathInfoOnly())
+ encodedPathInContext = URIUtil.encodePath(req.getPathInfo());
+ else if (req instanceof ServletApiRequest apiRequest)
+ encodedPathInContext = Context.getPathInContext(req.getContextPath(), apiRequest.getServletContextRequest().getHttpURI().getCanonicalPath());
+ else
+ encodedPathInContext = Context.getPathInContext(req.getContextPath(), URIUtil.canonicalPath(req.getRequestURI()));
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("doGet(req={}, resp={}) pathInContext={}, included={}", req, resp, encodedPathInContext, included);
try
{
- if (_resourceBase != null)
+ HttpContent content = _resourceService.getContent(encodedPathInContext, ServletContextRequest.getServletContextRequest(req));
+ if (LOG.isDebugEnabled())
+ LOG.debug("content = {}", content);
+
+ if (content == null || Resources.missing(content.getResource()))
{
- r = _resourceBase.addPath(pathInContext);
- if (!_contextHandler.checkAlias(pathInContext, r))
- r = null;
+ if (included)
+ {
+ /* https://github.com/jakartaee/servlet/blob/6.0.0-RELEASE/spec/src/main/asciidoc/servlet-spec-body.adoc#93-the-include-method
+ * 9.3 - If the default servlet is the target of a RequestDispatch.include() and the requested
+ * resource does not exist, then the default servlet MUST throw FileNotFoundException.
+ * If the exception isn’t caught and handled, and the response
+ * hasn’t been committed, the status code MUST be set to 500.
+ */
+ throw new FileNotFoundException(encodedPathInContext);
+ }
+
+ // no content
+ resp.sendError(404);
}
- else if (_servletContext instanceof ContextHandler.Context)
+ else
{
- r = _contextHandler.getResource(pathInContext);
+ ServletCoreRequest coreRequest = new ServletCoreRequest(req);
+ ServletCoreResponse coreResponse = new ServletCoreResponse(coreRequest, resp);
+
+ if (coreResponse.isCommitted())
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response already committed for {}", coreRequest.getHttpURI());
+ return;
+ }
+
+ // Servlet Filters could be interacting with the Response already.
+ if (coreResponse.isHttpServletResponseWrapped() ||
+ coreResponse.isWritingOrStreaming())
+ {
+ content = new UnknownLengthHttpContent(content);
+ }
+
+ ServletContextResponse contextResponse = coreResponse.getServletContextResponse();
+ if (contextResponse != null)
+ {
+ String characterEncoding = contextResponse.getRawCharacterEncoding();
+ if (characterEncoding != null)
+ content = new ForcedCharacterEncodingHttpContent(content, characterEncoding);
+ }
+
+ // serve content
+ try (Blocker.Callback callback = Blocker.callback())
+ {
+ _resourceService.doGet(coreRequest, coreResponse, callback, content);
+ callback.block();
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
}
- else
+ }
+ catch (InvalidPathException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("InvalidPathException for pathInContext: {}", encodedPathInContext, e);
+ if (included)
+ throw new FileNotFoundException(encodedPathInContext);
+ resp.setStatus(404);
+ }
+ }
+
+ @Override
+ protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("doHead(req={}, resp={}) (calling doGet())", req, resp);
+ doGet(req, resp);
+ }
+
+ private Resource getResource(URI uri)
+ {
+ String uriPath = uri.getRawPath();
+ Resource result = null;
+ try
+ {
+ result = _contextHandler.getResource(uriPath);
+ }
+ catch (IOException x)
+ {
+ LOG.trace("IGNORED", x);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resource {}={}", uriPath, result);
+ return result;
+ }
+
+ private static class ServletCoreRequest extends Request.Wrapper
+ {
+ // TODO fully implement this class and move it to the top level
+ // TODO Some methods are directed to core that probably should be intercepted
+
+ private final HttpServletRequest _servletRequest;
+ private final HttpFields _httpFields;
+ private final HttpURI _uri;
+
+ ServletCoreRequest(HttpServletRequest request)
+ {
+ super(ServletContextRequest.getServletContextRequest(request));
+ _servletRequest = request;
+
+ HttpFields.Mutable fields = HttpFields.build();
+
+ Enumeration headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements())
{
- return null;
+ String headerName = headerNames.nextElement();
+ Enumeration headerValues = request.getHeaders(headerName);
+ while (headerValues.hasMoreElements())
+ {
+ String headerValue = headerValues.nextElement();
+ fields.add(new HttpField(headerName, headerValue));
+ }
}
- if (LOG.isDebugEnabled())
- LOG.debug("Resource {}={}", pathInContext, r);
+ _httpFields = fields.asImmutable();
+ String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ boolean included = includedServletPath != null;
+ if (request.getDispatcherType() == DispatcherType.REQUEST)
+ _uri = getWrapped().getHttpURI();
+ else if (included)
+ _uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(getIncludedPathInContext(request, includedServletPath, false)));
+ else
+ _uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo())));
}
- catch (IOException e)
+
+ @Override
+ public HttpFields getHeaders()
{
- LOG.trace("IGNORED", e);
+ return _httpFields;
}
- if ((r == null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
- r = _stylesheet;
+ @Override
+ public HttpURI getHttpURI()
+ {
+ return _uri;
+ }
- return r;
+ @Override
+ public String getId()
+ {
+ return _servletRequest.getRequestId();
+ }
+
+ @Override
+ public String getMethod()
+ {
+ return _servletRequest.getMethod();
+ }
+
+ @Override
+ public boolean isSecure()
+ {
+ return _servletRequest.isSecure();
+ }
+
+ @Override
+ public boolean addErrorListener(Predicate onError)
+ {
+ return false;
+ }
+
+ @Override
+ public void addHttpStreamWrapper(Function wrapper)
+ {
+ }
+
+ @Override
+ public Object removeAttribute(String name)
+ {
+ Object value = _servletRequest.getAttribute(name);
+ _servletRequest.removeAttribute(name);
+ return value;
+ }
+
+ @Override
+ public Object setAttribute(String name, Object attribute)
+ {
+ Object value = _servletRequest.getAttribute(name);
+ _servletRequest.setAttribute(name, attribute);
+ return value;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ return _servletRequest.getAttribute(name);
+ }
+
+ @Override
+ public Set getAttributeNameSet()
+ {
+ Set set = new HashSet<>();
+ Enumeration e = _servletRequest.getAttributeNames();
+ while (e.hasMoreElements())
+ set.add(e.nextElement());
+ return set;
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ Enumeration e = _servletRequest.getAttributeNames();
+ while (e.hasMoreElements())
+ _servletRequest.removeAttribute(e.nextElement());
+ }
}
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ private static class HttpServletResponseHttpFields implements HttpFields.Mutable
{
- if (!_resourceService.doGet(request, response))
- response.sendError(404);
+ private final HttpServletResponse _response;
+
+ private HttpServletResponseHttpFields(HttpServletResponse response)
+ {
+ _response = response;
+ }
+
+ @Override
+ public ListIterator listIterator()
+ {
+ // The minimum requirement is to implement the listIterator, but it is inefficient.
+ // Other methods are implemented for efficiency.
+ final ListIterator list = _response.getHeaderNames().stream()
+ .map(n -> new HttpField(n, _response.getHeader(n)))
+ .collect(Collectors.toList())
+ .listIterator();
+
+ return new ListIterator<>()
+ {
+ HttpField _last;
+
+ @Override
+ public boolean hasNext()
+ {
+ return list.hasNext();
+ }
+
+ @Override
+ public HttpField next()
+ {
+ return _last = list.next();
+ }
+
+ @Override
+ public boolean hasPrevious()
+ {
+ return list.hasPrevious();
+ }
+
+ @Override
+ public HttpField previous()
+ {
+ return _last = list.previous();
+ }
+
+ @Override
+ public int nextIndex()
+ {
+ return list.nextIndex();
+ }
+
+ @Override
+ public int previousIndex()
+ {
+ return list.previousIndex();
+ }
+
+ @Override
+ public void remove()
+ {
+ if (_last != null)
+ {
+ // This is not exactly the right semantic for repeated field names
+ list.remove();
+ _response.setHeader(_last.getName(), null);
+ }
+ }
+
+ @Override
+ public void set(HttpField httpField)
+ {
+ list.set(httpField);
+ _response.setHeader(httpField.getName(), httpField.getValue());
+ }
+
+ @Override
+ public void add(HttpField httpField)
+ {
+ list.add(httpField);
+ _response.addHeader(httpField.getName(), httpField.getValue());
+ }
+ };
+ }
+
+ @Override
+ public Mutable add(String name, String value)
+ {
+ _response.addHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpHeader header, HttpHeaderValue value)
+ {
+ _response.addHeader(header.asString(), value.asString());
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpHeader header, String value)
+ {
+ _response.addHeader(header.asString(), value);
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpField field)
+ {
+ _response.addHeader(field.getName(), field.getValue());
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpField field)
+ {
+ _response.setHeader(field.getName(), field.getValue());
+ return this;
+ }
+
+ @Override
+ public Mutable put(String name, String value)
+ {
+ _response.setHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpHeader header, HttpHeaderValue value)
+ {
+ _response.setHeader(header.asString(), value.asString());
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpHeader header, String value)
+ {
+ _response.setHeader(header.asString(), value);
+ return this;
+ }
+
+ @Override
+ public Mutable put(String name, List list)
+ {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(list);
+ boolean first = true;
+ for (String s : list)
+ {
+ if (first)
+ _response.setHeader(name, s);
+ else
+ _response.addHeader(name, s);
+ first = false;
+ }
+ return this;
+ }
+
+ @Override
+ public Mutable remove(HttpHeader header)
+ {
+ _response.setHeader(header.asString(), null);
+ return this;
+ }
+
+ @Override
+ public Mutable remove(EnumSet fields)
+ {
+ for (HttpHeader header : fields)
+ remove(header);
+ return this;
+ }
+
+ @Override
+ public Mutable remove(String name)
+ {
+ _response.setHeader(name, null);
+ return this;
+ }
}
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ private static class ServletCoreResponse implements Response
{
- doGet(request, response);
+ // TODO fully implement this class and move it to the top level
+
+ private final HttpServletResponse _response;
+ private final ServletCoreRequest _coreRequest;
+ private final Response _coreResponse;
+ private final HttpFields.Mutable _httpFields;
+
+ public ServletCoreResponse(ServletCoreRequest coreRequest, HttpServletResponse response)
+ {
+ _coreRequest = coreRequest;
+ _response = response;
+ _coreResponse = ServletContextResponse.getServletContextResponse(response);
+ _httpFields = new HttpServletResponseHttpFields(response);
+ }
+
+ @Override
+ public HttpFields.Mutable getHeaders()
+ {
+ return _httpFields;
+ }
+
+ public ServletContextResponse getServletContextResponse()
+ {
+ if (_response instanceof ServletApiResponse)
+ {
+ ServletApiResponse apiResponse = (ServletApiResponse)_response;
+ return apiResponse.getResponse();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return _response.isCommitted();
+ }
+
+ /**
+ * Test if the HttpServletResponse is wrapped by the webapp.
+ *
+ * @return true if wrapped.
+ */
+ public boolean isHttpServletResponseWrapped()
+ {
+ return (_response instanceof HttpServletResponseWrapper);
+ }
+
+ /**
+ * Test if {@link HttpServletResponse#getOutputStream()} or
+ * {@link HttpServletResponse#getWriter()} has been called already
+ *
+ * @return true if {@link HttpServletResponse} has started to write or stream content
+ */
+ public boolean isWritingOrStreaming()
+ {
+ ServletContextResponse servletContextResponse = Response.as(_coreResponse, ServletContextResponse.class);
+ return servletContextResponse.isWritingOrStreaming();
+ }
+
+ public boolean isWriting()
+ {
+ ServletContextResponse servletContextResponse = Response.as(_coreResponse, ServletContextResponse.class);
+ return servletContextResponse.isWriting();
+ }
+
+ @Override
+ public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
+ {
+ try
+ {
+ if (BufferUtil.hasContent(byteBuffer))
+ {
+ if (isWriting())
+ {
+ String characterEncoding = _response.getCharacterEncoding();
+ try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
+ InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
+ {
+ IO.copy(reader, _response.getWriter());
+ }
+
+ if (last)
+ _response.getWriter().close();
+ }
+ else
+ {
+ BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
+ if (last)
+ _response.getOutputStream().close();
+ }
+ }
+
+ callback.succeeded();
+ }
+ catch (Throwable t)
+ {
+ callback.failed(t);
+ }
+ }
+
+ @Override
+ public Request getRequest()
+ {
+ return _coreRequest;
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return _response.getStatus();
+ }
+
+ @Override
+ public void setStatus(int code)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}.setStatus({})", this.getClass().getSimpleName(), code);
+ _response.setStatus(code);
+ }
+
+ @Override
+ public Supplier getTrailersSupplier()
+ {
+ return null;
+ }
+
+ @Override
+ public void setTrailersSupplier(Supplier trailers)
+ {
+ }
+
+ @Override
+ public boolean isCompletedSuccessfully()
+ {
+ return _coreResponse.isCompletedSuccessfully();
+ }
+
+ @Override
+ public void reset()
+ {
+ _response.reset();
+ }
+
+ @Override
+ public CompletableFuture writeInterim(int status, HttpFields headers)
+ {
+ return null;
+ }
}
- @Override
- protected void doHead(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ private class ServletResourceService extends ResourceService implements ResourceService.WelcomeFactory
{
- doGet(request, response);
+ private final ServletContextHandler _servletContextHandler;
+
+ private ServletResourceService(ServletContextHandler servletContextHandler)
+ {
+ _servletContextHandler = servletContextHandler;
+ }
+
+ @Override
+ public String getWelcomeTarget(Request coreRequest)
+ {
+ String[] welcomes = _servletContextHandler.getWelcomeFiles();
+ if (welcomes == null)
+ return null;
+
+ HttpServletRequest request = getServletRequest(coreRequest);
+ String pathInContext = Request.getPathInContext(coreRequest);
+ String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ String requestTarget;
+ if (includedServletPath != null)
+ requestTarget = getIncludedPathInContext(request, includedServletPath, isPathInfoOnly());
+ else
+ requestTarget = isPathInfoOnly() ? request.getPathInfo() : pathInContext;
+
+ String welcomeTarget = null;
+ Resource base = _baseResource.resolve(requestTarget);
+ if (Resources.isReadableDirectory(base))
+ {
+ for (String welcome : welcomes)
+ {
+ String welcomeInContext = URIUtil.addPaths(pathInContext, welcome);
+
+ // If the welcome resource is a file, it has
+ // precedence over resources served by Servlets.
+ Resource welcomePath = base.resolve(welcome);
+ if (Resources.isReadableFile(welcomePath))
+ return welcomeInContext;
+
+ // Check whether a Servlet may serve the welcome resource.
+ if (_welcomeServletMode != WelcomeServletMode.NONE && welcomeTarget == null)
+ {
+ ServletHandler.MappedServlet entry = _servletContextHandler.getServletHandler().getMappedServlet(welcomeInContext);
+ // Is there a different Servlet that may serve the welcome resource?
+ if (entry != null && entry.getServletHolder().getServletInstance() != DefaultServlet.this)
+ {
+ if (_welcomeServletMode == WelcomeServletMode.MATCH || entry.getPathSpec().getDeclaration().equals(welcomeInContext))
+ {
+ welcomeTarget = welcomeInContext;
+ // Do not break the loop, because we want to try other welcome resources
+ // that may be files and take precedence over Servlet welcome resources.
+ }
+ }
+ }
+ }
+ }
+ return welcomeTarget;
+ }
+
+ @Override
+ protected void redirectWelcome(Request request, Response response, Callback callback, String welcomeTarget) throws IOException
+ {
+ HttpServletRequest servletRequest = getServletRequest(request);
+ HttpServletResponse servletResponse = getServletResponse(response);
+
+ boolean included = isIncluded(servletRequest);
+
+ String servletPath = included ? (String)servletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH)
+ : servletRequest.getServletPath();
+
+ if (isPathInfoOnly())
+ welcomeTarget = URIUtil.addPaths(servletPath, welcomeTarget);
+
+ servletResponse.setContentLength(0);
+ Response.sendRedirect(request, response, callback, welcomeTarget);
+ }
+
+ @Override
+ protected void serveWelcome(Request request, Response response, Callback callback, String welcomeTarget) throws IOException
+ {
+ HttpServletRequest servletRequest = getServletRequest(request);
+ HttpServletResponse servletResponse = getServletResponse(response);
+
+ boolean included = isIncluded(servletRequest);
+
+ RequestDispatcher dispatcher = servletRequest.getServletContext().getRequestDispatcher(welcomeTarget);
+ if (dispatcher == null)
+ {
+ // We know that the welcome target exists and can be served.
+ Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500);
+ return;
+ }
+
+ try
+ {
+ if (included)
+ {
+ dispatcher.include(servletRequest, servletResponse);
+ }
+ else
+ {
+ servletRequest.setAttribute("org.eclipse.jetty.server.welcome", welcomeTarget);
+ dispatcher.forward(servletRequest, servletResponse);
+ }
+ callback.succeeded();
+ }
+ catch (ServletException e)
+ {
+ callback.failed(e);
+ }
+ }
+
+ @Override
+ protected void rehandleWelcome(Request request, Response response, Callback callback, String welcomeTarget) throws IOException
+ {
+ serveWelcome(request, response, callback, welcomeTarget);
+ }
+
+ @Override
+ protected void writeHttpError(Request coreRequest, Response coreResponse, Callback callback, int statusCode)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("writeHttpError(coreRequest={}, coreResponse={}, callback={}, statusCode={})", coreRequest, coreResponse, callback, statusCode);
+ writeHttpError(coreRequest, coreResponse, callback, statusCode, null, null);
+ }
+
+ @Override
+ protected void writeHttpError(Request coreRequest, Response coreResponse, Callback callback, Throwable cause)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("writeHttpError(coreRequest={}, coreResponse={}, callback={}, cause={})", coreRequest, coreResponse, callback, cause, cause);
+
+ int statusCode = HttpStatus.INTERNAL_SERVER_ERROR_500;
+ String reason = null;
+ if (cause instanceof HttpException httpException)
+ {
+ statusCode = httpException.getCode();
+ reason = httpException.getReason();
+ }
+ writeHttpError(coreRequest, coreResponse, callback, statusCode, reason, cause);
+ }
+
+ @Override
+ protected void writeHttpError(Request coreRequest, Response coreResponse, Callback callback, int statusCode, String reason, Throwable cause)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("writeHttpError(coreRequest={}, coreResponse={}, callback={}, statusCode={}, reason={}, cause={})", coreRequest, coreResponse, callback, statusCode, reason, cause, cause);
+ HttpServletRequest request = getServletRequest(coreRequest);
+ HttpServletResponse response = getServletResponse(coreResponse);
+ try
+ {
+ // TODO: not sure if this is allowed here.
+ if (cause != null)
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, cause);
+ response.sendError(statusCode, reason);
+ }
+ catch (IOException e)
+ {
+ // TODO: Need a better exception?
+ throw new RuntimeException(e);
+ }
+ finally
+ {
+ callback.succeeded();
+ }
+ }
+
+ @Override
+ protected boolean passConditionalHeaders(Request request, Response response, HttpContent content, Callback callback) throws IOException
+ {
+ boolean included = isIncluded(getServletRequest(request));
+ if (included)
+ return false;
+ return super.passConditionalHeaders(request, response, content, callback);
+ }
+
+ private HttpServletRequest getServletRequest(Request request)
+ {
+ // TODO, this unwrapping is fragile
+ return ((ServletCoreRequest)request)._servletRequest;
+ }
+
+ private HttpServletResponse getServletResponse(Response response)
+ {
+ // TODO, this unwrapping is fragile
+ return ((ServletCoreResponse)response)._response;
+ }
}
- @Override
- protected void doTrace(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ private static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly)
{
- response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ String servletPath = isPathInfoOnly ? "/" : includedServletPath;
+ String pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+ return URIUtil.addPaths(servletPath, pathInfo);
}
- @Override
- protected void doOptions(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ private static boolean isIncluded(HttpServletRequest request)
{
- response.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+ return request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
}
- @Override
- public void destroy()
+ /**
+ * Wrap an existing HttpContent with one that takes has an unknown/unspecified length.
+ */
+ private static class UnknownLengthHttpContent extends HttpContent.Wrapper
{
- if (_cache != null)
- _cache.flushCache();
- super.destroy();
+ public UnknownLengthHttpContent(HttpContent content)
+ {
+ super(content);
+ }
+
+ @Override
+ public HttpField getContentLength()
+ {
+ return null;
+ }
+
+ @Override
+ public long getContentLengthValue()
+ {
+ return -1;
+ }
}
- @Override
- public String getWelcomeFile(String pathInContext)
+ private static class ForcedCharacterEncodingHttpContent extends HttpContent.Wrapper
{
- if (_welcomes == null)
- return null;
+ private final String characterEncoding;
+ private final String contentType;
- String welcomeServlet = null;
- for (String s : _welcomes)
+ public ForcedCharacterEncodingHttpContent(HttpContent content, String characterEncoding)
{
- String welcomeInContext = URIUtil.addPaths(pathInContext, s);
- Resource welcome = getResource(welcomeInContext);
- if (welcome != null && welcome.exists())
- return welcomeInContext;
+ super(content);
+ this.characterEncoding = characterEncoding;
+ String mimeType = content.getContentTypeValue();
+ int idx = mimeType.indexOf(";charset");
+ if (idx >= 0)
+ mimeType = mimeType.substring(0, idx);
+ this.contentType = mimeType + ";charset=" + this.characterEncoding;
+ }
- if ((_welcomeServlets || _welcomeExactServlets) && welcomeServlet == null)
- {
- MatchedResource entry = _servletHandler.getMatchedServlet(welcomeInContext);
- if (entry != null && entry.getResource().getServletHolder().getServletInstance() != this &&
- (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcomeInContext))))
- welcomeServlet = welcomeInContext;
- }
+ @Override
+ public HttpField getContentType()
+ {
+ return new HttpField(HttpHeader.CONTENT_TYPE, this.contentType);
}
- return welcomeServlet;
+
+ @Override
+ public String getContentTypeValue()
+ {
+ return this.contentType;
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ return this.characterEncoding;
+ }
+ }
+
+ /**
+ * The different modes a welcome resource may be served by a Servlet.
+ */
+ private enum WelcomeServletMode
+ {
+ /**
+ * Welcome targets are not served by Servlets.
+ * The welcome target must exist as a file on the filesystem.
+ */
+ NONE,
+ /**
+ * Welcome target that exist as files on the filesystem are
+ * served, otherwise a matching Servlet may serve the welcome target.
+ */
+ MATCH,
+ /**
+ * Welcome target that exist as files on the filesystem are
+ * served, otherwise an exact matching Servlet may serve the welcome target.
+ */
+ EXACT
}
}
-// CHECKSTYLE:ON