
org.glassfish.grizzly.servlet.ApplicationDispatcher Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
* 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.
*/
package org.glassfish.grizzly.servlet;
import static jakarta.servlet.DispatcherType.INCLUDE;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.server.util.Globals;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestWrapper;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletResponseWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Standard implementation of RequestDispatcher
that allows a request to be forwarded to a different
* resource to create the ultimate response, or to include the output of another resource in the response from this
* resource. This implementation allows application level servlets to wrap the request and/or response objects that are
* passed on to the called resource, as long as the wrapping classes extend
* jakarta.servlet.ServletRequestWrapper
and jakarta.servlet.ServletResponseWrapper
.
*
* @author Craig R. McClanahan
* @author Bongjae Chang
*/
final class ApplicationDispatcher implements RequestDispatcher {
// This attribute corresponds to a String[] which acts like a stack
// containing the last two pushed elements
public static final String LAST_DISPATCH_REQUEST_PATH_ATTR = "org.apache.catalina.core.ApplicationDispatcher.lastDispatchRequestPathAttr";
private final static Logger LOGGER = Grizzly.logger(ApplicationDispatcher.class);
private class PrivilegedDispatch implements PrivilegedExceptionAction {
private final ServletRequest request;
private final ServletResponse response;
private final DispatcherType dispatcherType;
PrivilegedDispatch(ServletRequest request, ServletResponse response, DispatcherType dispatcherType) {
this.request = request;
this.response = response;
this.dispatcherType = dispatcherType;
}
@Override
public Void run() throws java.lang.Exception {
doDispatch(request, response, dispatcherType);
return null;
}
}
private class PrivilegedInclude implements PrivilegedExceptionAction {
private final ServletRequest request;
private final ServletResponse response;
PrivilegedInclude(ServletRequest request, ServletResponse response) {
this.request = request;
this.response = response;
}
@Override
public Void run() throws ServletException, IOException {
doInclude(request, response);
return null;
}
}
/**
* Used to pass state when the request dispatcher is used. Using instance variables causes threading issues and state is
* too complex to pass and return single ServletRequest or ServletResponse objects.
*/
private static class State {
/**
* Outermost request that will be passed on to the invoked servlet
*/
ServletRequest outerRequest = null;
/**
* Outermost response that will be passed on to the invoked servlet.
*/
ServletResponse outerResponse = null;
/**
* Request wrapper we have created and installed (if any).
*/
ServletRequest wrapRequest = null;
/**
* Response wrapper we have created and installed (if any).
*/
ServletResponse wrapResponse = null;
/**
* The type of dispatch we are performing
*/
final DispatcherType dispatcherType;
/**
* Outermost HttpServletRequest in the chain
*/
HttpServletRequest hrequest = null;
/**
* Outermost HttpServletResponse in the chain
*/
HttpServletResponse hresponse = null;
State(ServletRequest request, ServletResponse response, DispatcherType dispatcherType) {
this.outerRequest = request;
this.outerResponse = response;
this.dispatcherType = dispatcherType;
}
}
// ----------------------------------------------------------- Constructors
/**
* Construct a new instance of this class, configured according to the specified parameters. If both servletPath and
* pathInfo are null
, it will be assumed that this RequestDispatcher was acquired by name, rather than by
* path.
*
* @param wrapper The Wrapper associated with the resource that will be forwarded to or included (required)
* @param requestURI The request URI to this resource (if any)
* @param servletPath The revised servlet path to this resource (if any)
* @param pathInfo The revised extra path information to this resource (if any)
* @param queryString Query string parameters included with this request (if any)
* @param name Servlet name (if a named dispatcher was created) else null
*/
public ApplicationDispatcher(ServletHandler wrapper, String requestURI, String servletPath, String pathInfo, String queryString, String name) {
super();
// Save all of our configuration parameters
this.wrapper = wrapper;
this.requestURI = requestURI;
this.servletPath = servletPath;
this.pathInfo = pathInfo;
this.queryString = queryString;
this.name = name;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "servletPath={0}, pathInfo={1}, queryString={2}, name={3}",
new Object[] { this.servletPath, this.pathInfo, queryString, this.name });
}
}
/**
* is this dispatch cross context
*/
private Boolean crossContextFlag = null;
/**
* The servlet name for a named dispatcher.
*/
private String name = null;
/**
* The extra path information for this RequestDispatcher.
*/
private String pathInfo = null;
/**
* The query string parameters for this RequestDispatcher.
*/
private String queryString = null;
/**
* The request URI for this RequestDispatcher.
*/
private String requestURI = null;
/**
* The servlet path for this RequestDispatcher.
*/
private String servletPath = null;
/**
* The Wrapper associated with the resource that will be forwarded to or included.
*/
private ServletHandler wrapper = null;
// --------------------------------------------------------- Public Methods
/**
* Forwards the given request and response to the resource for which this dispatcher was acquired.
*
*
* Any runtime exceptions, IOException, or ServletException thrown by the target will be propagated to the caller.
*
* @param request The request to be forwarded
* @param response The response to be forwarded
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet exception occurs
*/
@Override
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
dispatch(request, response, DispatcherType.FORWARD);
}
/**
* Dispatches the given request and response to the resource for which this dispatcher was acquired.
*
*
* Any runtime exceptions, IOException, or ServletException thrown by the target will be propagated to the caller.
*
* @param request The request to be forwarded
* @param response The response to be forwarded
* @param dispatcherType The type of dispatch to be performed
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet exception occurs
* @throws IllegalArgumentException if the dispatcher type is different from FORWARD, ERROR, and ASYNC
*/
@SuppressWarnings("unchecked")
public void dispatch(ServletRequest request, ServletResponse response, DispatcherType dispatcherType) throws ServletException, IOException {
if (!DispatcherType.FORWARD.equals(dispatcherType) && !DispatcherType.ERROR.equals(dispatcherType) && !DispatcherType.ASYNC.equals(dispatcherType)) {
throw new IllegalArgumentException("Illegal dispatcher type");
}
boolean isCommit = DispatcherType.FORWARD.equals(dispatcherType) || DispatcherType.ERROR.equals(dispatcherType);
if (System.getSecurityManager() != null) {
try {
PrivilegedDispatch dp = new PrivilegedDispatch(request, response, dispatcherType);
AccessController.doPrivileged(dp);
// START SJSAS 6374990
if (isCommit && !request.isAsyncStarted()) {
closeResponse(response);
}
// END SJSAS 6374990
} catch (PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException) {
throw (ServletException) e;
}
throw (IOException) e;
}
} else {
doDispatch(request, response, dispatcherType);
// START SJSAS 6374990
if (isCommit && !request.isAsyncStarted()) {
closeResponse(response);
}
// END SJSAS 6374990
}
}
private void doDispatch(ServletRequest request, ServletResponse response, DispatcherType dispatcherType) throws ServletException, IOException {
if (!DispatcherType.ASYNC.equals(dispatcherType)) {
// Reset any output that has been buffered, but keep
// headers/cookies
if (response.isCommitted()) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(" Forward on committed response --> ISE");
}
throw new IllegalStateException("Cannot forward after response has been committed");
}
try {
response.resetBuffer();
} catch (IllegalStateException e) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Forward resetBuffer() returned ISE: {0}", e);
}
throw e;
}
}
if (DispatcherType.INCLUDE != dispatcherType) {
DispatchTargetsInfo dtInfo = (DispatchTargetsInfo) request.getAttribute(LAST_DISPATCH_REQUEST_PATH_ATTR);
if (dtInfo == null) {
dtInfo = new DispatchTargetsInfo();
request.setAttribute(LAST_DISPATCH_REQUEST_PATH_ATTR, dtInfo);
}
if (servletPath == null && pathInfo == null) {
// Handle an HTTP named dispatcher forward
dtInfo.addDispatchTarget(wrapper.getName(), true);
} else {
dtInfo.addDispatchTarget(getCombinedPath(), false);
}
}
// Set up to handle the specified request and response
State state = new State(request, response, dispatcherType);
ServletRequest sr = wrapRequest(state);
wrapResponse(state);
// Identify the HTTP-specific request and response objects (if any)
HttpServletRequest hrequest = state.hrequest;
HttpServletResponse hresponse = state.hresponse;
if (hrequest == null || hresponse == null) {
// Handle a non-HTTP forward
processRequest(request, response, state);
} else if (servletPath == null && pathInfo == null) {
// Handle an HTTP named dispatcher forward
DispatchedHttpServletRequest wrequest = (DispatchedHttpServletRequest) sr;
wrequest.setRequestURI(hrequest.getRequestURI());
wrequest.setContextPath(hrequest.getContextPath());
wrequest.setServletPath(hrequest.getServletPath());
wrequest.setPathInfo(hrequest.getPathInfo());
wrequest.setQueryString(hrequest.getQueryString());
processRequest(request, response, state);
} else {
// Handle an HTTP path-based forward
DispatchedHttpServletRequest wrequest = (DispatchedHttpServletRequest) sr;
// If the request is being FORWARD- or ASYNC-dispatched for
// the first time, initialize it with the required request
// attributes
if (DispatcherType.FORWARD.equals(dispatcherType) && hrequest.getAttribute(DispatcherConstants.FORWARD_REQUEST_URI) == null
|| DispatcherType.ASYNC.equals(dispatcherType) && hrequest.getAttribute(DispatcherConstants.ASYNC_REQUEST_URI) == null) {
wrequest.initSpecialAttributes(hrequest.getRequestURI(), hrequest.getContextPath(), hrequest.getServletPath(), hrequest.getPathInfo(),
hrequest.getQueryString());
}
String targetContextPath = wrapper.getContextPath();
// START IT 10395
HttpServletRequestImpl requestFacade = wrequest.getRequestFacade();
String originContextPath = requestFacade.getContextPath();
if (originContextPath != null && originContextPath.equals(targetContextPath)) {
targetContextPath = hrequest.getContextPath();
}
// END IT 10395
wrequest.setContextPath(targetContextPath);
wrequest.setRequestURI(requestURI);
wrequest.setServletPath(servletPath);
wrequest.setPathInfo(pathInfo);
if (queryString != null) {
wrequest.setQueryString(queryString);
wrequest.setQueryParams(queryString);
}
processRequest(request, response, state);
}
recycleRequestWrapper(state);
unwrapRequest(state);
unwrapResponse(state);
}
/**
* Prepare the request based on the filter configuration.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet error occurs
*/
private void processRequest(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException {
if (request != null) {
if (state.dispatcherType != DispatcherType.ERROR) {
state.outerRequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath());
invoke(state.outerRequest, response, state);
} else {
invoke(state.outerRequest, response, state);
}
}
}
/**
* Combines the servletPath and the pathInfo.
*
* If pathInfo is null
, it is ignored. If servletPath is null
, then null
is
* returned.
*
* @return The combined path with pathInfo appended to servletInfo
*/
private String getCombinedPath() {
if (servletPath == null) {
return null;
}
if (pathInfo == null) {
return servletPath;
}
return servletPath + pathInfo;
}
/**
* Include the response from another resource in the current response. Any runtime exception, IOException, or
* ServletException thrown by the called servlet will be propagated to the caller.
*
* @param request The servlet request that is including this one
* @param response The servlet response to be appended to
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet exception occurs
*/
@SuppressWarnings("unchecked")
@Override
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
if (System.getSecurityManager() != null) {
try {
PrivilegedInclude dp = new PrivilegedInclude(request, response);
AccessController.doPrivileged(dp);
} catch (PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException) {
throw (ServletException) e;
}
throw (IOException) e;
}
} else {
doInclude(request, response);
}
}
private void doInclude(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// Set up to handle the specified request and response
State state = new State(request, response, DispatcherType.INCLUDE);
// Create a wrapped response to use for this request
wrapResponse(state);
// Handle a non-HTTP include
/*
* GlassFish 6386229 if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
*
* if ( log.isDebugEnabled() ) log.debug(" Non-HTTP Include");
* request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
* Integer.valueOf(ApplicationFilterFactory.INCLUDE));
* request.setAttribute(ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR, //origServletPath); servletPath); try{
* invoke(request, state.outerResponse, state); } finally { unwrapResponse(state); } }
*
* // Handle an HTTP named dispatcher include else if (name != null) {
*/
// START GlassFish 6386229
// Handle an HTTP named dispatcher include
if (name != null) {
// END GlassFish 6386229
DispatchedHttpServletRequest wrequest = (DispatchedHttpServletRequest) wrapRequest(state);
wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name);
if (servletPath != null) {
wrequest.setServletPath(servletPath);
}
wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath());
try {
invoke(state.outerRequest, state.outerResponse, state);
} finally {
recycleRequestWrapper(state);
unwrapRequest(state);
unwrapResponse(state);
}
}
// Handle an HTTP path based include
else {
DispatchedHttpServletRequest wrequest = (DispatchedHttpServletRequest) wrapRequest(state);
wrequest.initSpecialAttributes(requestURI, wrapper.getContextPath(), servletPath, pathInfo, queryString);
wrequest.setQueryParams(queryString);
wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath());
try {
invoke(state.outerRequest, state.outerResponse, state);
} finally {
recycleRequestWrapper(state);
unwrapRequest(state);
unwrapResponse(state);
}
}
}
// -------------------------------------------------------- Private Methods
/**
* Ask the resource represented by this RequestDispatcher to process the associated request, and create (or append to)
* the associated response.
*
* IMPLEMENTATION NOTE: This implementation assumes that no filters are applied to a forwarded or
* included resource, because they were already done for the original request.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet error occurs
*/
private void invoke(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException {
// START OF 6364900 original invoke has been renamed to doInvoke
boolean crossContext = false;
if (crossContextFlag != null && crossContextFlag) {
crossContext = true;
}
try {
doInvoke(request, response, crossContext, state);
} finally {
crossContextFlag = null;
}
// END OF 6364900
}
/**
* Ask the resource represented by this RequestDispatcher to process the associated request, and create (or append to)
* the associated response.
*
* IMPLEMENTATION NOTE: This implementation assumes that no filters are applied to a forwarded or
* included resource, because they were already done for the original request.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param crossContext true if the request dispatch is crossing context boundaries, false otherwise
* @param state the state of this ApplicationDispatcher
*
* @throws IOException if an input/output error occurs
* @throws ServletException if a servlet error occurs
*/
private void doInvoke(ServletRequest request, ServletResponse response, boolean crossContext, State state) throws IOException, ServletException {
ClassLoader oldCCL = null;
try {
// Checking to see if the context classloader is the current context
// classloader. If it's not, we're saving it, and setting the context
// classloader to the Context classloader
if (crossContext) {
ClassLoader contextClassLoader = wrapper.getClassLoader();
if (contextClassLoader != null) {
oldCCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
wrapper.doServletService(request, response, state.dispatcherType);
} finally {
// Reset the old context class loader
if (oldCCL != null) {
Thread.currentThread().setContextClassLoader(oldCCL);
}
}
}
/**
* Unwrap the request if we have wrapped it.
*/
private void unwrapRequest(State state) {
if (state.wrapRequest == null) {
return;
}
ServletRequest previous = null;
ServletRequest current = state.outerRequest;
while (current != null) {
// If we run into the container request we are done
if (current instanceof HttpServletRequestImpl) {
break;
}
// Remove the current request if it is our wrapper
if (current == state.wrapRequest) {
ServletRequest next = ((ServletRequestWrapper) current).getRequest();
if (previous == null) {
state.outerRequest = next;
} else {
((ServletRequestWrapper) previous).setRequest(next);
}
break;
}
// Advance to the next request in the chain
previous = current;
current = ((ServletRequestWrapper) current).getRequest();
}
}
/**
* Unwrap the response if we have wrapped it.
*/
private void unwrapResponse(State state) {
if (state.wrapResponse == null) {
return;
}
ServletResponse previous = null;
ServletResponse current = state.outerResponse;
while (current != null) {
// If we run into the container response we are done
if (current instanceof HttpServletResponseImpl) {
break;
}
// Remove the current response if it is our wrapper
if (current == state.wrapResponse) {
ServletResponse next = ((ServletResponseWrapper) current).getResponse();
if (previous == null) {
state.outerResponse = next;
} else {
((ServletResponseWrapper) previous).setResponse(next);
}
break;
}
// Advance to the next response in the chain
previous = current;
current = ((ServletResponseWrapper) current).getResponse();
}
}
/**
* Create and return a request wrapper that has been inserted in the appropriate spot in the request chain.
*/
private ServletRequest wrapRequest(State state) {
// Locate the request we should insert in front of
ServletRequest previous = null;
ServletRequest current = state.outerRequest;
while (current != null) {
if (state.hrequest == null && current instanceof HttpServletRequest) {
state.hrequest = (HttpServletRequest) current;
}
if (!(current instanceof ServletRequestWrapper)) {
break;
}
// If we find container-generated wrapper, break out
if (current instanceof DispatchedHttpServletRequest) {
break;
}
previous = current;
current = ((ServletRequestWrapper) current).getRequest();
}
if (current == null) {
throw new IllegalStateException("Can't retrieve container request from " + state.outerRequest);
}
// Instantiate a new wrapper at this point and insert it in the chain
ServletRequest wrapperLocal;
// Compute a crossContext flag
HttpServletRequest hcurrent = (HttpServletRequest) current;
boolean crossContext = false;
if (state.outerRequest instanceof DispatchedHttpServletRequest || state.outerRequest instanceof HttpServletRequest) {
HttpServletRequest houterRequest = (HttpServletRequest) state.outerRequest;
Object contextPath = houterRequest.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH);
if (contextPath == null) {
// Forward
contextPath = houterRequest.getContextPath();
}
crossContext = !wrapper.getContextPath().equals(contextPath);
}
// START OF 6364900
crossContextFlag = crossContext;
// END OF 6364900
wrapperLocal = new DispatchedHttpServletRequest(hcurrent, wrapper.getServletCtx(), crossContext, state.dispatcherType);
if (previous == null) {
state.outerRequest = wrapperLocal;
} else {
((ServletRequestWrapper) previous).setRequest(wrapperLocal);
}
state.wrapRequest = wrapperLocal;
return wrapperLocal;
}
/**
* Create and return a response wrapper that has been inserted in the appropriate spot in the response chain.
*/
private ServletResponse wrapResponse(State state) {
// Locate the response we should insert in front of
ServletResponse previous = null;
ServletResponse current = state.outerResponse;
while (current != null) {
if (state.hresponse == null && current instanceof HttpServletResponse) {
state.hresponse = (HttpServletResponse) current;
if (DispatcherType.INCLUDE != state.dispatcherType) {
return null;
}
}
if (!(current instanceof ServletResponseWrapper)) {
break;
}
if (current instanceof DispatchedHttpServletResponse) {
break;
}
previous = current;
current = ((ServletResponseWrapper) current).getResponse();
}
HttpServletResponse hcurrent = (HttpServletResponse) current;
// Instantiate a new wrapper and insert it in the chain
DispatchedHttpServletResponse wrapperLocal = new DispatchedHttpServletResponse(hcurrent, INCLUDE.equals(state.dispatcherType));
if (previous == null) {
state.outerResponse = wrapperLocal;
} else {
((ServletResponseWrapper) previous).setResponse(wrapperLocal);
}
state.wrapResponse = wrapperLocal;
return wrapperLocal;
}
private static void closeResponse(ServletResponse response) {
try {
PrintWriter writer = response.getWriter();
writer.flush();
writer.close();
} catch (IllegalStateException e) {
try {
ServletOutputStream stream = response.getOutputStream();
stream.flush();
stream.close();
} catch (IllegalStateException ignored) {
} catch (IOException ignored) {
}
} catch (IOException ignored) {
}
}
private void recycleRequestWrapper(State state) {
if (state.wrapRequest instanceof DispatchedHttpServletRequest) {
((DispatchedHttpServletRequest) state.wrapRequest).recycle();
}
}
}