/*
* Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package org.ireland.jnetty.webapp;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ireland.jnetty.dispatch.HttpInvocation;
import org.ireland.jnetty.http.HttpServletRequestImpl;
import org.ireland.jnetty.http.HttpServletResponseImpl;
import org.ireland.jnetty.http.wrapper.ErrorRequest;
import org.ireland.jnetty.http.wrapper.ForwardRequest;
import org.ireland.jnetty.http.wrapper.IncludeRequest;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* 对于每一个请求,要生成一个与之对应的RequestDispatcher
*
* 对于相同的rawContextURI的多个不同的请求,其生成的RequestDispatcherImpl是一样的, 故可以根据rawContextURI来缓存RequestDispatcherImpl
*
* 只要rawContextURI相同(即包括参数也相同),并发的情况下也是可重用的和共享的,像单例一般的重用
*
* @author KEN
*
*/
public class RequestDispatcherImpl implements RequestDispatcher
{
private final static Log log = LogFactory.getLog(RequestDispatcherImpl.class.getName());
static final int MAX_DEPTH = 64;
// WebApp the request dispatcher was called from
private final WebApp _webApp;
private final String _rawContextURI;
private HttpInvocation _dispatchInvocation;
private HttpInvocation _forwardInvocation;
private HttpInvocation _includeInvocation;
private HttpInvocation _errorInvocation;
// private HttpInvocation _asyncInvocation;
public RequestDispatcherImpl(WebApp webApp, String rawContextURI)
{
_webApp = webApp;
_rawContextURI = rawContextURI;
}
/**
* This method sets the dispatcher type of the given request to DispatcherType.REQUEST.
*
* 通常,DispatcherType.REQUEST是第一次被处理的类型.
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void dispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// jsp/15m8
if (response.isCommitted())
throw new IllegalStateException("dispatch() not allowed after buffer has committed.");
// build invocation,if not exist
if (_dispatchInvocation == null)
{
_dispatchInvocation = buildDispatchInvocation(_rawContextURI);
}
doDispatch(request, response, _dispatchInvocation);
}
private void doDispatch(HttpServletRequest request, HttpServletResponse response, HttpInvocation invocation) throws ServletException, IOException
{
// 到这里,response的buffer一定为空的,TODO: need resetBuffer()?
response.resetBuffer();
// Set the invocation into HttpServlerRequestImpl
if (request instanceof HttpServletRequestImpl)
{
((HttpServletRequestImpl) request).setInvocation(_dispatchInvocation);
((HttpServletRequestImpl) request).setDispatcherType(DispatcherType.REQUEST);
}
boolean isValid = false;
try
{
invocation.getFilterChainInvocation().service(request, response);
isValid = true;
}
finally
{
if (request.getAsyncContext() != null)
{
// An async request was started during the forward, don't close the
// response as it may be written to during the async handling
return;
}
// server/106r, ioc/0310
if (isValid)
{
finishResponse(response);
}
}
}
@Override
public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
// jsp/15m8
if (response.isCommitted())
throw new IllegalStateException("forward() not allowed after buffer has committed.");
// build invocation,if not exist
if (_forwardInvocation == null)
{
_forwardInvocation = buildForwardInvocation(_rawContextURI);
}
doForward((HttpServletRequest) request, (HttpServletResponse) response, _forwardInvocation);
}
private void doForward(HttpServletRequest request, HttpServletResponse response, HttpInvocation invocation) throws ServletException, IOException
{
// Reset any output that has been buffered, but keep headers/cookies
response.resetBuffer(); // Servlet-3_1-PFD 9.4
//Wrap the request
ForwardRequest wrequest = new ForwardRequest(request, response, invocation);
// If we have already been forwarded previously, then keep using the established
// original value. Otherwise, this is the first forward and we need to establish the values.
// Note: the established value on the original request for pathInfo and
// for queryString is allowed to be null, but cannot be null for the other values.
if (request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) == null)
{
// 只有在第一次请求转发,记下最开始的请求的相关属性
wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI, request.getRequestURI());
wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH, request.getContextPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH, request.getServletPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO, request.getPathInfo());
wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING, request.getQueryString());
}
boolean isValid = false;
try
{
invocation.getFilterChainInvocation().service(wrequest, response);
isValid = true;
}
finally
{
if (request.getAsyncContext() != null)
{
// An async request was started during the forward, don't close the
// response as it may be written to during the async handling
return;
}
// server/106r, ioc/0310
if (isValid)
{
finishResponse(response);
}
}
}
@Override
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
// jsp/15m8
if (response.isCommitted())
throw new IllegalStateException("include() not allowed after buffer has committed.");
// build invocation,if not exist
if (_includeInvocation == null)
{
_includeInvocation = buildIncludeInvocation(_rawContextURI);
}
doInclude((HttpServletRequest) request, (HttpServletResponse) response, _forwardInvocation);
}
private void doInclude(HttpServletRequest request, HttpServletResponse response, HttpInvocation invocation) throws ServletException, IOException
{
//Wrap the request
IncludeRequest wrequest = new IncludeRequest(request, response, invocation);
// If we have already been include previously, then keep using the established
// original value. Otherwise, this is the first include and we need to establish the values.
// Note: the established value on the original request for pathInfo and
// for queryString is allowed to be null, but cannot be null for the other values.
if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) == null)
{
// 只有在第一次Include请求,记下最开始的请求的相关属性
wrequest.setAttribute(RequestDispatcher.INCLUDE_REQUEST_URI, request.getRequestURI());
wrequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH, request.getContextPath());
wrequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, request.getServletPath());
wrequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, request.getPathInfo());
wrequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING, request.getQueryString());
}
boolean isValid = false;
try
{
invocation.getFilterChainInvocation().service(wrequest, response);
isValid = true;
}
finally
{
if (request.getAsyncContext() != null)
{
// An async request was started during the forward, don't close the
// response as it may be written to during the async handling
return;
}
// server/106r, ioc/0310
if (isValid)
{
finishResponse(response);
}
}
}
/**
* This method Wrap the dispatcher type of the given request to DispatcherType.ERROR.
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
// jsp/15m8
if (response.isCommitted())
throw new IllegalStateException("error() not allowed after buffer has committed.");
// build invocation,if not exist
if (_errorInvocation == null)
{
_errorInvocation = buildErrorInvocation(_rawContextURI);
}
doError((HttpServletRequest) request, (HttpServletResponse) response, _errorInvocation);
}
private void doError(HttpServletRequest request, HttpServletResponse response, HttpInvocation invocation) throws ServletException, IOException
{
// Reset any output that has been buffered, but keep headers/cookies
response.resetBuffer(); // Servlet-3_1-PFD 9.4
//Wrap the request
ErrorRequest wrequest = new ErrorRequest(request, response, invocation);
boolean isValid = false;
try
{
invocation.getFilterChainInvocation().service(wrequest, response);
isValid = true;
}
finally
{
if (request.getAsyncContext() != null)
{
// An async request was started during the forward, don't close the
// response as it may be written to during the async handling
return;
}
// server/106r, ioc/0310
if (isValid)
{
finishResponse(response);
}
}
}
// -----------------------------------------------------------------------------------
/**
* Fills the invocation with uri.
*
* @throws IOException
*/
private HttpInvocation buildDispatchInvocation(String rawContextURI) throws ServletException
{
return _webApp.buildDispatchInvocation(rawContextURI);
}
/**
* Fills the invocation for a forward request.
*
* @throws IOException
*/
private HttpInvocation buildForwardInvocation(String rawContextURI) throws ServletException
{
return _webApp.buildForwardInvocation(rawContextURI);
}
/**
* Fills the invocation for an include request.
*
* @throws IOException
*/
private HttpInvocation buildIncludeInvocation(String rawContextURI) throws ServletException
{
return _webApp.buildIncludeInvocation(rawContextURI);
}
/**
* Fills the invocation for an error request.
*
* @throws IOException
*/
private HttpInvocation buildErrorInvocation(String rawContextURI) throws ServletException
{
return _webApp.buildErrorInvocation(rawContextURI);
}
// ------------------------------------------------------------------------------------
private void finishResponse(ServletResponse res) throws ServletException, IOException
{
if (res instanceof HttpServletResponseImpl)
{
res.flushBuffer(); // we sure that all data has already put to the ByteBuf?
}
else
{
try
{
OutputStream os = res.getOutputStream();
os.flush();
// os.close();
}
catch (Exception e)
{
}
try
{
PrintWriter out = res.getWriter();
out.flush();
// out.close();
}
catch (Exception e)
{
}
}
}
@Override
public String toString()
{
return (getClass().getSimpleName() + "[" + _dispatchInvocation.getRawContextURI() + "]");
}
// Util------------------------------------------------------------
/**
* 将reques解包装,返回未包装的ServletRequest
*
* @param request
* @return
*/
private static ServletRequest unwarp(ServletRequest request)
{
ServletRequest _request = request;
while (_request instanceof ServletRequestWrapper)
{
_request = ((ServletRequestWrapper) _request).getRequest();
}
return _request;
}
/**
* 将response解包装,返回未包装的ServletResponse
*
* @param response
* @return
*/
private static ServletResponse unwarp(ServletResponse response)
{
ServletResponse _response = response;
while (_response instanceof ServletResponseWrapper)
{
_response = ((ServletResponseWrapper) _response).getResponse();
}
return _response;
}
}