Implement Servlet 6.1 Error dispatch changes (#11821)

Implement Servlet 6.1 Error dispatch changes

+ Error dispatches are done as GET methods
+ New ERROR_METHOD and ERROR_QUERY_STRING attributes
+ Moved most error attributes to Dispatcher.ErrorRequest
+ removed duplicate attributes

* updates from review

* Update jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ErrorHandler.java

Co-authored-by: Jan Bartel <janb@webtide.com>
This commit is contained in:
Greg Wilkins 2024-05-23 14:49:00 +10:00 committed by GitHub
parent c10df2d7ab
commit 1e95caa1ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 118 additions and 28 deletions

View File

@ -39,9 +39,11 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.ee11.servlet.util.ServletOutputStreamWrapper;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
@ -765,9 +767,12 @@ public class Dispatcher implements RequestDispatcher
private class ErrorRequest extends ParameterRequestWrapper
{
public ErrorRequest(HttpServletRequest httpRequest)
private final HttpServletRequest _httpServletRequest;
public ErrorRequest(HttpServletRequest httpServletRequest)
{
super(httpRequest);
super(httpServletRequest);
_httpServletRequest = Objects.requireNonNull(httpServletRequest);
}
@Override
@ -776,16 +781,22 @@ public class Dispatcher implements RequestDispatcher
return DispatcherType.ERROR;
}
@Override
public String getMethod()
{
return HttpMethod.GET.asString();
}
@Override
public String getPathInfo()
{
return _servletPathMapping.getPathInfo();
return Objects.requireNonNull(_servletPathMapping).getPathInfo();
}
@Override
public String getServletPath()
{
return _servletPathMapping.getServletPath();
return Objects.requireNonNull(_servletPathMapping).getServletPath();
}
@Override
@ -797,7 +808,7 @@ public class Dispatcher implements RequestDispatcher
@Override
public String getRequestURI()
{
return _uri.getPath();
return Objects.requireNonNull(_uri).getPath();
}
@Override
@ -809,6 +820,28 @@ public class Dispatcher implements RequestDispatcher
.port(getServerPort())
.asString());
}
@Override
public Object getAttribute(String name)
{
return switch (name)
{
case ERROR_METHOD -> _httpServletRequest.getMethod();
case ERROR_REQUEST_URI -> _httpServletRequest.getRequestURI();
case ERROR_QUERY_STRING -> _httpServletRequest.getQueryString();
case ERROR_STATUS_CODE -> super.getAttribute(ErrorHandler.ERROR_STATUS);
case ERROR_MESSAGE -> super.getAttribute(ErrorHandler.ERROR_MESSAGE);
default -> super.getAttribute(name);
};
}
@Override
public Enumeration<String> getAttributeNames()
{
List<String> names = new ArrayList<>(List.of(ERROR_METHOD, ERROR_REQUEST_URI, ERROR_QUERY_STRING, ERROR_STATUS_CODE, ERROR_MESSAGE));
names.addAll(Collections.list(super.getAttributeNames()));
return Collections.enumeration(names);
}
}
@Override

View File

@ -50,9 +50,8 @@ import org.slf4j.LoggerFactory;
public class ErrorHandler implements Request.Handler
{
// TODO This classes API needs to be majorly refactored/cleanup in jetty-10
// TODO This class's API needs to be majorly refactored/cleanup
private static final Logger LOG = LoggerFactory.getLogger(ErrorHandler.class);
public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context";
public static final String ERROR_CHARSET = "org.eclipse.jetty.server.error_charset";
boolean _showServlet = true;

View File

@ -23,6 +23,8 @@ import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.server.handler.ErrorHandler.ERROR_STATUS;
/**
* An ErrorHandler that maps exceptions and status codes to URIs for dispatch using
* the internal ERROR style of dispatch.
@ -108,7 +110,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
pageSource = PageLookupTechnique.STATUS_CODE;
// look for an exact code match
errorStatusCode = (Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
errorStatusCode = (Integer)request.getAttribute(ERROR_STATUS);
if (errorStatusCode != null)
{
errorPage = _errorPages.get(Integer.toString(errorStatusCode));

View File

@ -843,19 +843,22 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public Enumeration<String> getAttributeNames()
{
Set<String> set = getRequest().getAttributeNameSet();
if (_async != null)
{
set = new HashSet<>(set);
set.add(AsyncContext.ASYNC_REQUEST_URI);
set.add(AsyncContext.ASYNC_CONTEXT_PATH);
set.add(AsyncContext.ASYNC_SERVLET_PATH);
set.add(AsyncContext.ASYNC_PATH_INFO);
set.add(AsyncContext.ASYNC_QUERY_STRING);
set.add(AsyncContext.ASYNC_MAPPING);
Set<String> names = new HashSet<>(Set.of(
AsyncContext.ASYNC_REQUEST_URI,
AsyncContext.ASYNC_CONTEXT_PATH,
AsyncContext.ASYNC_SERVLET_PATH,
AsyncContext.ASYNC_PATH_INFO,
AsyncContext.ASYNC_QUERY_STRING,
AsyncContext.ASYNC_MAPPING
));
names.addAll(getRequest().getAttributeNameSet());
return Collections.enumeration(names);
}
return Collections.enumeration(set);
return Collections.enumeration(getRequest().getAttributeNameSet());
}
@Override

View File

@ -44,6 +44,7 @@ import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.server.handler.ErrorHandler.ERROR_CONTEXT;
import static org.eclipse.jetty.util.thread.Invocable.InvocationType.NON_BLOCKING;
/**
@ -459,7 +460,7 @@ public class ServletChannel
// the following is needed as you cannot trust the response code and reason
// as those could have been modified after calling sendError
Integer code = (Integer)_servletContextRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
Integer code = (Integer)_servletContextRequest.getAttribute(org.eclipse.jetty.server.handler.ErrorHandler.ERROR_STATUS);
if (code == null)
code = HttpStatus.INTERNAL_SERVER_ERROR_500;
getServletContextResponse().setStatus(code);
@ -472,7 +473,7 @@ public class ServletChannel
if (!_httpInput.consumeAvailable())
ResponseUtils.ensureNotPersistent(_servletContextRequest, _servletContextRequest.getServletContextResponse());
ContextHandler.ScopedContext context = (ContextHandler.ScopedContext)_servletContextRequest.getAttribute(ErrorHandler.ERROR_CONTEXT);
ContextHandler.ScopedContext context = (ContextHandler.ScopedContext)_servletContextRequest.getAttribute(ERROR_CONTEXT);
Request.Handler errorHandler = ErrorHandler.getErrorHandler(getServer(), context == null ? null : context.getContextHandler());
// If we can't have a body or have no ErrorHandler, then create a minimal error response.
@ -527,7 +528,7 @@ public class ServletChannel
finally
{
// clean up the context that was set in Response.sendError
_servletContextRequest.removeAttribute(ErrorHandler.ERROR_CONTEXT);
_servletContextRequest.removeAttribute(ERROR_CONTEXT);
}
break;
}

View File

@ -36,10 +36,7 @@ import org.slf4j.LoggerFactory;
import static jakarta.servlet.RequestDispatcher.ERROR_EXCEPTION;
import static jakarta.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE;
import static jakarta.servlet.RequestDispatcher.ERROR_MESSAGE;
import static jakarta.servlet.RequestDispatcher.ERROR_REQUEST_URI;
import static jakarta.servlet.RequestDispatcher.ERROR_SERVLET_NAME;
import static jakarta.servlet.RequestDispatcher.ERROR_STATUS_CODE;
/**
* holder of the state of request-response cycle.
@ -1039,11 +1036,8 @@ public class ServletChannelState
response.setStatus(code);
servletContextRequest.errorClose();
request.setAttribute(org.eclipse.jetty.ee11.servlet.ErrorHandler.ERROR_CONTEXT, servletContextRequest.getErrorContext());
request.setAttribute(ERROR_REQUEST_URI, httpServletRequest.getRequestURI());
request.setAttribute(ERROR_SERVLET_NAME, servletContextRequest.getServletName());
request.setAttribute(ERROR_STATUS_CODE, code);
request.setAttribute(ERROR_MESSAGE, message);
// Additional servlet error attributes are provided in org.eclipse.jetty.ee11.servlet.Dispatcher.ErrorRequest
// Set Jetty Specific Attributes.
request.setAttribute(ErrorHandler.ERROR_CONTEXT, servletContextRequest.getServletContext());

View File

@ -153,6 +153,7 @@ public class ErrorPageTest
assertThat(body, containsString("ERROR_EXCEPTION: null"));
assertThat(body, containsString("ERROR_EXCEPTION_TYPE: null"));
assertThat(body, containsString("ERROR_SERVLET: " + errorContentServlet.getClass().getName()));
assertThat(body, containsString("ERROR_METHOD: GET"));
assertThat(body, containsString("ERROR_REQUEST_URI: /error-mime-charset-writer/"));
}
@ -734,6 +735,61 @@ public class ErrorPageTest
assertThat(responseBody, Matchers.containsString("getParameterMap()[ ]=[]"));
}
@Test
public void testErrorAttributes() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws IOException
{
response.sendError(599);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
doGet(req, resp);
}
};
contextHandler.addServlet(failServlet, "/fail/599");
contextHandler.addServlet(ErrorDumpServlet.class, "/error/*");
ErrorPageErrorHandler errorPageErrorHandler = new ErrorPageErrorHandler();
errorPageErrorHandler.addErrorPage(599, "/error/599");
contextHandler.setErrorHandler(errorPageErrorHandler);
startServer(contextHandler);
String rawRequest = """
POST /fail/599?name=value HTTP/1.1\r
Host: test\r
Connection: close\r
\r
""";
String rawResponse = _connector.getResponse(rawRequest);
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(599));
assertThat(response.get(HttpHeader.DATE), notNullValue());
String responseBody = response.getContent();
assertThat(responseBody, Matchers.containsString("ERROR_PAGE: /599"));
assertThat(responseBody, Matchers.containsString("ERROR_CODE: 599"));
assertThat(responseBody, Matchers.containsString("ERROR_EXCEPTION: null"));
assertThat(responseBody, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
assertThat(responseBody, Matchers.containsString("ERROR_SERVLET: " + failServlet.getClass().getName()));
assertThat(responseBody, Matchers.containsString("ERROR_METHOD: POST"));
assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599"));
assertThat(responseBody, Matchers.containsString("ERROR_QUERY_STRING: name=value"));
}
@Test
public void testErrorCode() throws Exception
{
@ -1733,7 +1789,7 @@ public class ErrorPageTest
public static class ErrorDumpServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (request.getDispatcherType() != DispatcherType.ERROR && request.getDispatcherType() != DispatcherType.ASYNC)
throw new IllegalStateException("Bad Dispatcher Type " + request.getDispatcherType());
@ -1746,7 +1802,9 @@ public class ErrorPageTest
writer.println("ERROR_EXCEPTION: " + request.getAttribute(Dispatcher.ERROR_EXCEPTION));
writer.println("ERROR_EXCEPTION_TYPE: " + request.getAttribute(Dispatcher.ERROR_EXCEPTION_TYPE));
writer.println("ERROR_SERVLET: " + request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
writer.println("ERROR_METHOD: " + request.getAttribute(Dispatcher.ERROR_METHOD));
writer.println("ERROR_REQUEST_URI: " + request.getAttribute(Dispatcher.ERROR_REQUEST_URI));
writer.println("ERROR_QUERY_STRING: " + request.getAttribute(Dispatcher.ERROR_QUERY_STRING));
writer.printf("getRequestURI()=%s%n", valueOf(request.getRequestURI()));
writer.printf("getRequestURL()=%s%n", valueOf(request.getRequestURL()));