/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.citrus.webx.support;
import static com.alibaba.citrus.service.requestcontext.util.RequestContextUtil.*;
import static com.alibaba.citrus.springext.util.SpringExtUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ExceptionUtil.*;
import static com.alibaba.citrus.util.FileUtil.*;
import static com.alibaba.citrus.util.ServletUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static java.util.Collections.*;
import static org.springframework.beans.factory.config.AutowireCapableBeanFactory.*;
import java.io.IOException;
import java.util.Comparator;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.citrus.service.pipeline.Pipeline;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.RequestContextChainingService;
import com.alibaba.citrus.service.requestcontext.buffered.BufferedRequestContext;
import com.alibaba.citrus.service.requestcontext.lazycommit.LazyCommitRequestContext;
import com.alibaba.citrus.service.requestcontext.util.RequestContextUtil;
import com.alibaba.citrus.util.ClassLoaderUtil;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.webx.BadRequestException;
import com.alibaba.citrus.webx.ResourceNotFoundException;
import com.alibaba.citrus.webx.WebxComponents;
import com.alibaba.citrus.webx.WebxException;
import com.alibaba.citrus.webx.WebxRootController;
import com.alibaba.citrus.webx.config.WebxConfiguration;
import com.alibaba.citrus.webx.handler.ErrorHandlerMapping;
import com.alibaba.citrus.webx.handler.RequestHandler;
import com.alibaba.citrus.webx.handler.RequestHandlerContext;
import com.alibaba.citrus.webx.handler.RequestHandlerMapping;
import com.alibaba.citrus.webx.handler.RequestHandlerNameAware;
import com.alibaba.citrus.webx.handler.impl.MainHandler;
import com.alibaba.citrus.webx.handler.impl.error.DetailedErrorHandler;
import com.alibaba.citrus.webx.handler.impl.error.PipelineErrorHandler;
import com.alibaba.citrus.webx.handler.impl.error.SendErrorHandler;
import com.alibaba.citrus.webx.servlet.PassThruSupportable;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper.ExceptionCodeMapping;
import com.alibaba.citrus.webx.util.RequestURIFilter;
import com.alibaba.citrus.webx.util.WebxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.PropertiesLoaderUtils;
public abstract class AbstractWebxRootController implements WebxRootController, PassThruSupportable {
protected final Logger log = LoggerFactory.getLogger(getClass());
/** 在request中保存request context owner的键名。 */
private static final String REQUEST_CONTEXT_OWNER_KEY = "_request_context_owner_";
/** 用来注册request handler的文件名。 */
private static final String REQUEST_HANDLER_LOCATION = "META-INF/webx.internal-request-handlers";
/** Error页面的前缀。 */
private static final String ERROR_PREFIX = "error";
private WebxComponents components;
private InternalRequestHandlerMapping internalHandlerMapping;
private RequestContextChainingService requestContexts;
private RequestURIFilter passthruFilter;
public WebxComponents getComponents() {
return components;
}
public WebxConfiguration getWebxConfiguration() {
return getComponents().getParentWebxConfiguration();
}
public ServletContext getServletContext() {
return getComponents().getParentApplicationContext().getServletContext();
}
public void setPassthruFilter(RequestURIFilter passthru) {
this.passthruFilter = passthru;
}
/** 此方法在创建controller时被调用。 */
public void init(WebxComponents components) {
this.components = components;
}
/** 此方法在创建或刷新WebApplicationContext时被调用。 */
public void onRefreshContext() throws BeansException {
initWebxConfiguration();
initInternalRequestHandler();
initRequestContexts();
}
private void initWebxConfiguration() {
WebxConfiguration webxConfiguration = getWebxConfiguration();
log.debug("Initializing Webx root context in {} mode, according to <webx-configuration>",
webxConfiguration.isProductionMode() ? "production" : "development");
}
private void initInternalRequestHandler() {
internalHandlerMapping = new InternalRequestHandlerMapping();
}
private void initRequestContexts() {
requestContexts = getWebxConfiguration().getRequestContexts();
log.debug("Using RequestContextChainingService: {}", requestContexts);
}
public void onFinishedProcessContext() {
}
public final void service(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws Exception {
RequestContext requestContext = null;
try {
requestContext = assertNotNull(getRequestContext(request, response), "could not get requestContext");
// 如果请求已经结束,则不执行进一步的处理。例如,当requestContext已经被重定向了,则立即结束请求的处理。
if (isRequestFinished(requestContext)) {
return;
}
// 请求未结束,则继续处理...
request = requestContext.getRequest();
response = requestContext.getResponse();
// 如果是一个内部请求,则执行内部请求
if (handleInternalRequest(request, response)) {
return;
}
// 如果不是内部的请求,并且没有被passthru,则执行handleRequest
if (isRequestPassedThru(request) || !handleRequest(requestContext)) {
// 如果请求被passthru,或者handleRequest返回false(即pipeline放弃请求),
// 则调用filter chain,将控制交还给servlet engine。
giveUpControl(requestContext, chain);
}
} catch (Throwable e) {
handleException(requestContext, e);
} finally {
commitRequest(requestContext);
}
}
/**
* 执行内部请求。
*
* @return 如果是内部请求,并且被执行了,则返回<code>true</code>。
*/
private boolean handleInternalRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
RequestHandlerContext internalRequestHandlerContext = internalHandlerMapping.getRequestHandlerContext(request, response);
if (internalRequestHandlerContext == null) {
return false;
}
// 如果是一个内部请求,则执行内部请求
internalRequestHandlerContext.handleRequest();
return true;
}
/**
* 处理异常e的过程:
* <p/>
* 1. 首先调用errorHandler处理异常e,errorHandler将生成友好的错误页面。
* errorHandler也负责记录日志 ─ 如果必要的话。
* <p/>
* 2. Handler可以直接把异常抛回来,这样servlet engine就会接管这个异常。通常是显示web.xml中指定的错误页面。
* 这种情况下,errorHandler还是要负责记录日志。
* <p/>
* 3. 假如不幸errorHandler本身遇到异常,则servlet engine就会接管这个异常。通常是显示web.xml中指定的错误页面。
* 这种情况下,新老异常都会被记录到日志中。
*/
private void handleException(RequestContext requestContext, Throwable e) throws ServletException, IOException {
HttpServletRequest request = requestContext.getRequest();
HttpServletResponse response = requestContext.getResponse();
try {
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception ee) {
// ignore this exception
}
clearBuffer(requestContext, response);
// 取得并执行errorHandler
RequestHandlerContext errorRequestHandlerContext = internalHandlerMapping.getRequestHandlerContextForError(request, response, e);
assertNotNull(errorRequestHandlerContext, "Could not get exception handler for exception: %s", e);
try {
// 对于error处理过程,设置component为特殊的root component。
WebxUtil.setCurrentComponent(request, components.getComponent(null));
// error handler要负责记录日志,可以通过ErrorHandlerHelper.logError()来做。
errorRequestHandlerContext.handleRequest();
} finally {
WebxUtil.setCurrentComponent(request, null);
}
} catch (Throwable ee) {
// 有两种情况:
// 1. ee causedBy e,这个表明是errorHandler特意将异常重新抛出,转交给servlet engine来处理
// 2. ee和e无关,这个表明是errorHandler自身出现错误。对于这种情况,需要记录日志。
if (!getCauses(ee).contains(e)) {
Throwable rootCause = getRootCause(e);
String originalExceptionMessage = rootCause.getClass().getSimpleName() + ": " + rootCause.getMessage();
log.error("Failed to handle an error caused by " + originalExceptionMessage, ee);
log.error("Full stack trace of the error " + originalExceptionMessage, e);
}
clearBuffer(requestContext, response);
if (e instanceof ServletException) {
throw (ServletException) e;
} else if (e instanceof IOException) {
throw (IOException) e;
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else if (e instanceof Error) {
throw (Error) e;
} else {
throw new ServletException(e);
}
}
}
/**
* 如果定义了passthru filter,则判断request是否被passthru,
* 对于需要被passthru的request,不执行handleRequest,而是立即把控制交还给filter chain。
* 该功能适用于仅将webx视作普通的filter,而filter chain的接下来的部分将可使用webx所提供的request contexts。
*/
private boolean isRequestPassedThru(HttpServletRequest request) {
if (passthruFilter != null) {
String path = getResourcePath(request);
if (passthruFilter.matches(path)) {
log.debug("Passed through request: {}", path);
return true;
}
}
return false;
}
/** 放弃控制,将控制权返回给servlet engine。 */
private void giveUpControl(RequestContext requestContext, FilterChain chain) throws IOException, ServletException {
// 1. 关闭buffering
BufferedRequestContext brc = findRequestContext(requestContext, BufferedRequestContext.class);
if (brc != null) {
try {
brc.setBuffering(false);
} catch (IllegalStateException e) {
// getInputStream或getWriter已经被调用了,不能更改buffering参数。
}
}
// 2. 取消contentType的设置
try {
requestContext.getResponse().setContentType(null);
} catch (Exception e) {
// ignored, 有可能有的servlet engine不支持null参数
}
// 调用filter chain
chain.doFilter(requestContext.getRequest(), requestContext.getResponse());
}
/** 判断请求是否已经结束。如果请求被重定向了,则表示请求已经结束。 */
protected boolean isRequestFinished(RequestContext requestContext) {
LazyCommitRequestContext lcrc = findRequestContext(requestContext, LazyCommitRequestContext.class);
return lcrc != null && lcrc.isRedirected();
}
/** 提交request。 */
private void commitRequest(RequestContext requestContext) {
if (requestContext == null) {
return;
}
try {
if (this == requestContext.getRequest().getAttribute(REQUEST_CONTEXT_OWNER_KEY)) {
requestContext.getRequest().removeAttribute(REQUEST_CONTEXT_OWNER_KEY);
requestContexts.commitRequestContext(requestContext);
}
} catch (Exception e) {
log.error("Exception occurred while commit rundata", e);
}
}
/** 处理请求。 */
protected abstract boolean handleRequest(RequestContext requestContext) throws Exception;
/** 清除buffer。 */
private void clearBuffer(RequestContext requestContext, HttpServletResponse response) {
// 有可能是在创建requestContext时出错,此时requestContext为空。
if (requestContext != null) {
response = requestContext.getResponse();
}
if (!response.isCommitted()) {
response.resetBuffer();
}
}
/** 取得request context对象。 */
private RequestContext getRequestContext(HttpServletRequest request, HttpServletResponse response) {
RequestContext requestContext = RequestContextUtil.getRequestContext(request);
if (requestContext == null) {
requestContext = requestContexts.getRequestContext(getServletContext(), request, response);
request.setAttribute(REQUEST_CONTEXT_OWNER_KEY, this);
}
return requestContext;
}
/** 代表webx内部请求的相关信息。 */
private class InternalRequestHandlerContext extends RequestHandlerContext {
private final RequestHandler handler;
public InternalRequestHandlerContext(HttpServletRequest request, HttpServletResponse response,
String internalBaseURL, String baseURL, String resourceName,
RequestHandler handler) {
super(request, response, AbstractWebxRootController.this.getServletContext(), internalBaseURL, baseURL,
resourceName);
this.handler = handler;
}
@Override
public RequestHandler getRequestHandler() {
return handler;
}
}
/** 用来处理webx内部请求的mapping。 */
private class InternalRequestHandlerMapping implements RequestHandlerMapping, ErrorHandlerMapping {
private final Pattern homepagePattern = Pattern.compile("(^|\\?|&)home(=|&|$)");
private final boolean productionMode;
private String internalPathPrefix;
private RequestHandler mainHandler;
private RequestHandler errorHandler;
private Map<String, RequestHandler> internalHandlers = emptyMap();
public InternalRequestHandlerMapping() {
productionMode = getWebxConfiguration().isProductionMode();
// 将mapping放到application context中,以便注入到handler中。
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) components
.getParentApplicationContext()).getBeanFactory();
beanFactory.registerResolvableDependency(RequestHandlerMapping.class, this);
// internalPathPrefix
internalPathPrefix = getWebxConfiguration().getInternalPathPrefix();
internalPathPrefix = normalizeAbsolutePath(internalPathPrefix, true); // 规格化成/internal
if (isEmpty(internalPathPrefix)) {
throw new IllegalArgumentException("Invalid internalPathPrefix: "
+ getWebxConfiguration().getInternalPathPrefix());
}
// 创建并初始化errorHandler
// 在production mode下,假如config中指定了exception pipeline,则执行之;
// 否则sendError,由web.xml中指定的错误页面来处理。
if (productionMode) {
Pipeline exceptionPipeline = getWebxConfiguration().getExceptionPipeline();
if (exceptionPipeline == null) {
log.debug("No exceptionPipeline configured in <webx-configuration>.");
errorHandler = new SendErrorHandler();
} else {
errorHandler = new PipelineErrorHandler(exceptionPipeline);
}
}
// 在开发者模式下,显示详细出错页面。
else {
errorHandler = new DetailedErrorHandler();
((DetailedErrorHandler) errorHandler).setName(ERROR_PREFIX);
}
autowireAndInitialize(errorHandler, components.getParentApplicationContext(), AUTOWIRE_NO, ERROR_PREFIX);
log.debug("Using Exception Handler: {}.", errorHandler.getClass().getName());
// 只在开发者模式下显示主页和其它handlers
if (!productionMode) {
// 从META-INF/webx.internal-request-handlers,不包含error handler和main handler
internalHandlers = loadInternalHandlers(REQUEST_HANDLER_LOCATION);
// 创建并初始化mainHandler
mainHandler = new MainHandler();
((MainHandler) mainHandler).setName(EMPTY_STRING);
autowireAndInitialize(mainHandler, components.getParentApplicationContext(), AUTOWIRE_NO, ERROR_PREFIX);
}
}
public String[] getRequestHandlerNames() {
return internalHandlers.keySet().toArray(new String[internalHandlers.size()]);
}
public RequestHandlerContext getRequestHandlerContext(HttpServletRequest request, HttpServletResponse response) {
String baseURL = getBaseURL(request);
String path = getResourcePath(request).replace(' ', '+'); // 将空白换成+,因为internalHandlers的key不会包含空白。
String internalBaseURL = baseURL + internalPathPrefix;
// 如果是/首页,并且mainHandler存在(开发模式),则进入内部首页
if (mainHandler != null && (EMPTY_STRING.equals(path) || "/".equals(path))) {
// 除非参数中指定了?home
String qs = request.getQueryString();
if (isEmpty(qs) || !homepagePattern.matcher(qs).find()) {
return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL, path,
mainHandler);
}
}
// 如果是/internal
if (startsWithElement(path, internalPathPrefix)) {
path = removeStartElement(path, internalPathPrefix);
// 如果是/error,仅开发模式才进入
if (errorHandler != null && !productionMode && startsWithElement(path, ERROR_PREFIX)) {
path = removeStartElement(path, ERROR_PREFIX);
return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL + "/"
+ ERROR_PREFIX, path, errorHandler);
}
// internalHandlers中注册的前缀
for (Map.Entry<String, RequestHandler> entry : internalHandlers.entrySet()) {
String prefix = entry.getKey();
if (startsWithElement(path, prefix)) {
RequestHandler handler = entry.getValue();
path = removeStartElement(path, prefix);
return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL
+ "/" + prefix, path, handler);
}
}
// 默认由main page来处理
if (mainHandler != null) {
return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL, path,
mainHandler);
}
// 如果未匹配
throw new ResourceNotFoundException(request.getRequestURI());
}
return null;
}
public RequestHandlerContext getRequestHandlerContextForError(HttpServletRequest request, HttpServletResponse response,
Throwable exception) {
// servletName == ""
ErrorHandlerHelper helper = ErrorHandlerHelper.getInstance(request);
helper.init(EMPTY_STRING, exception, exceptionCodeMapping);
response.setStatus(helper.getStatusCode());
String internalBaseURL = getBaseURL(request) + internalPathPrefix;
return new InternalRequestHandlerContext(request, response, internalBaseURL, internalBaseURL + "/"
+ ERROR_PREFIX, "", errorHandler);
}
/** 相当于正则表达式:<code>^element/|^element$</code>。 */
private boolean startsWithElement(String path, String element) {
if (path.equals(element)) {
return true;
}
if (path.startsWith(element) && path.charAt(element.length()) == '/') {
return true;
}
return false;
}
/** 除去开头的<code>^element/|^element$</code>。 */
private String removeStartElement(String path, String element) {
if (path.equals(element)) {
return EMPTY_STRING;
}
return path.substring(element.length() + 1);
}
private Map<String, RequestHandler> loadInternalHandlers(String location) {
ClassLoader loader = components.getParentApplicationContext().getClassLoader();
Properties handlerNames;
try {
handlerNames = PropertiesLoaderUtils.loadAllProperties(location, loader);
} catch (IOException e) {
throw new WebxException("Could not load " + location, e);
}
// 装载handlers
Map<String, RequestHandler> handlers = createTreeMap(new Comparator<String>() {
public int compare(String s1, String s2) {
int lenDiff = s2.length() - s1.length();
if (lenDiff != 0) {
return lenDiff; // 先按名称长度倒排序
} else {
return s1.compareTo(s2); // 再按字母顺序排序
}
}
});
for (Map.Entry<?, ?> entry : handlerNames.entrySet()) {
String name = normalizeRelativePath((String) entry.getKey(), true); // 规格化:xxx/yyy/zzz
String handlerClass = trimToNull((String) entry.getValue());
// 忽略空的值
if (!isEmpty(name) && handlerClass != null) {
if (ERROR_PREFIX.equals(name)) {
log.warn("Ignored request handler with reserved name [" + ERROR_PREFIX + "]: " + handlerClass);
continue;
}
try {
Object handler = ClassLoaderUtil.newInstance(handlerClass, loader);
if (handler instanceof RequestHandlerNameAware) {
((RequestHandlerNameAware) handler).setName(name);
}
autowireAndInitialize(handler, components.getParentApplicationContext(), AUTOWIRE_NO, name);
try {
handlers.put(name, RequestHandler.class.cast(handler));
} catch (ClassCastException e) {
// 如果有一个handler出错,也不退出。
log.error("Declared internal request handler must implement InternalRequestHandler: "
+ name + "=" + handlerClass, e);
}
} catch (Exception e) {
// 如果有一个handler出错,也不退出。
log.error("Could not create internal request handler: " + name + "=" + handlerClass, e);
}
}
}
if (log.isDebugEnabled()) {
log.debug(new ToStringBuilder().append("loading internal request handlers:").append(handlers)
.toString());
}
return handlers;
}
/** Exception和statusCode的映射。 */
private final ExceptionCodeMapping exceptionCodeMapping = new ExceptionCodeMapping() {
public int getExceptionCode(Throwable exception) {
if (exception instanceof ResourceNotFoundException) {
return HttpServletResponse.SC_NOT_FOUND;
} else if (exception instanceof BadRequestException) {
return HttpServletResponse.SC_BAD_REQUEST;
}
return 0;
}
};
}
}