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:
parent
c10df2d7ab
commit
1e95caa1ef
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()));
|
||||
|
|
Loading…
Reference in New Issue