/*
* 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.service.requestcontext.rewrite.impl;
import static com.alibaba.citrus.service.requestcontext.rewrite.impl.RewriteUtil.*;
import static com.alibaba.citrus.service.requestcontext.util.RequestContextUtil.*;
import static com.alibaba.citrus.util.ArrayUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.ServletUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.util.regex.MatchResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.parser.ParameterParser;
import com.alibaba.citrus.service.requestcontext.parser.ParserRequestContext;
import com.alibaba.citrus.service.requestcontext.rewrite.RewriteRequestContext;
import com.alibaba.citrus.service.requestcontext.rewrite.RewriteSubstitutionContext;
import com.alibaba.citrus.service.requestcontext.rewrite.RewriteSubstitutionHandler;
import com.alibaba.citrus.service.requestcontext.support.AbstractRequestContextWrapper;
import com.alibaba.citrus.service.requestcontext.support.AbstractRequestWrapper;
import com.alibaba.citrus.util.FileUtil;
import com.alibaba.citrus.util.ServletUtil;
import com.alibaba.citrus.util.StringEscapeUtil;
import com.alibaba.citrus.util.regex.MatchResultSubstitution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** 重写URL及参数的request context,类似于apache的mod_rewrite模块。 */
public class RewriteRequestContextImpl extends AbstractRequestContextWrapper implements RewriteRequestContext {
private final static Logger log = LoggerFactory.getLogger(RewriteRequestContext.class);
public static final String SERVER_SCHEME_HTTP = "http";
public static final String SERVER_SCHEME_HTTPS = "https";
public static final int SERVER_PORT_HTTP = 80;
public static final int SERVER_PORT_HTTPS = 443;
private final RewriteRule[] rules;
private ParserRequestContext parserRequestContext;
private HttpServletRequest wrappedRequest;
/**
* 包装一个<code>RequestContext</code>对象。
*
* @param wrappedContext 被包装的<code>RequestContext</code>
* @param rewriteConfig rewrite的配置文件信息
*/
public RewriteRequestContextImpl(RequestContext wrappedContext, RewriteRule[] rules) {
super(wrappedContext);
this.rules = defaultIfEmptyArray(rules, null);
// 取得parser request context,以便修改参数
this.parserRequestContext = assertNotNull(findRequestContext(wrappedContext, ParserRequestContext.class),
"Could not find ParserRequestContext in request context chain");
// 保存上一层的request对象,以便取得原来的servletPath、pathInfo之类的信息
this.wrappedRequest = wrappedContext.getRequest();
}
/** 开始一个请求。 */
@Override
public void prepare() {
if (rules == null) {
return;
}
// 取得servletPath+pathInfo,忽略contextPath
String originalPath = wrappedRequest.getServletPath()
+ defaultIfNull(wrappedRequest.getPathInfo(), EMPTY_STRING);
String path = originalPath;
boolean parameterSubstituted = false;
if (log.isDebugEnabled()) {
log.debug("Starting rewrite engine: path=\"{}\"", StringEscapeUtil.escapeJava(path));
}
// 开始匹配
int redirectCode = 0;
for (RewriteRule rule : rules) {
MatchResult ruleMatchResult = rule.match(path);
MatchResult conditionMatchResult = null;
RewriteSubstitution subs = rule.getSubstitution();
// 如果匹配,则查看conditions
if (ruleMatchResult != null) {
conditionMatchResult = rule.matchConditions(ruleMatchResult, wrappedRequest);
}
// 如果C标志被指定,则除非匹配,否则不去判断余下的规则
boolean chainRule = subs.getFlags().hasC();
if (conditionMatchResult == null) {
if (chainRule) {
break;
} else {
continue;
}
}
// 用rule和condition的匹配结果来替换变量
MatchResultSubstitution resultSubs = getMatchResultSubstitution(ruleMatchResult, conditionMatchResult);
// 替换path
log.debug("Rule conditions have been satisfied, starting substitution to uri");
path = subs.substitute(path, resultSubs);
if (!isFullURL(path)) {
path = FileUtil.normalizeAbsolutePath(path);
}
// 处理parameters
parameterSubstituted |= subs.substituteParameters(parserRequestContext.getParameters(), resultSubs);
// post substitution处理
path = firePostSubstitutionEvent(rule, path, parserRequestContext, resultSubs);
// 查看重定向标志
redirectCode = subs.getFlags().getRedirectCode();
// 如果L标志被指定,则立即结束
boolean lastRule = subs.getFlags().hasL();
if (lastRule) {
break;
}
}
// 如果path被改变了,则替换request或重定向
if (!isEquals(originalPath, path)) {
// 如果是重定向,则组合出新的URL
if (redirectCode > 0) {
StringBuffer uri = new StringBuffer();
HttpServletRequest request = getRequest();
if (!isFullURL(path)) {
uri.append(request.getScheme()).append("://").append(request.getServerName());
boolean isDefaultPort = false;
// http和80
isDefaultPort |= SERVER_SCHEME_HTTP.equals(request.getScheme())
&& request.getServerPort() == SERVER_PORT_HTTP;
// https和443
isDefaultPort |= SERVER_SCHEME_HTTPS.equals(request.getScheme())
&& request.getServerPort() == SERVER_PORT_HTTPS;
if (!isDefaultPort) {
uri.append(":");
uri.append(request.getServerPort());
}
uri.append(request.getContextPath());
}
uri.append(path);
String queryString = parserRequestContext.getParameters().toQueryString();
if (!isEmpty(queryString)) {
uri.append("?").append(queryString);
}
String uriLocation = uri.toString();
try {
if (redirectCode == HttpServletResponse.SC_MOVED_TEMPORARILY) {
getResponse().sendRedirect(uriLocation);
} else {
getResponse().setHeader("Location", uriLocation);
getResponse().setStatus(redirectCode);
}
} catch (IOException e) {
log.warn("Redirect to location \"" + uriLocation + "\" failed", e);
}
} else {
RequestWrapper requestWrapper = new RequestWrapper(wrappedRequest);
requestWrapper.setPath(path);
setRequest(requestWrapper);
}
} else {
if (!parameterSubstituted) {
log.trace("No rewrite substitution happend!");
}
}
}
private String firePostSubstitutionEvent(RewriteRule rule, String path, ParserRequestContext parser,
MatchResultSubstitution resultSubs) {
for (Object handler : rule.handlers()) {
RewriteSubstitutionContext context = null;
if (handler instanceof RewriteSubstitutionHandler) {
if (context == null) {
context = new RewriteSubstitutionContextImpl(path, parser, resultSubs);
}
if (log.isTraceEnabled()) {
log.trace("Processing post-substitution event for \"{}\" with handler: {}",
StringEscapeUtil.escapeJava(path), handler);
}
((RewriteSubstitutionHandler) handler).postSubstitution(context);
// path可以被改变
String newPath = context.getPath();
if (newPath != null && !isEquals(path, newPath)) {
if (log.isDebugEnabled()) {
log.debug("Rewriting \"{}\" to \"{}\"", StringEscapeUtil.escapeJava(path),
StringEscapeUtil.escapeJava(newPath));
}
}
path = newPath;
}
}
return path;
}
/** 实现<code>RewriteSubstitutionContext</code>。 */
private class RewriteSubstitutionContextImpl implements RewriteSubstitutionContext {
private String path;
private ParserRequestContext parser;
private MatchResultSubstitution resultSubs;
public RewriteSubstitutionContextImpl(String path, ParserRequestContext parser,
MatchResultSubstitution resultSubs) {
this.path = path;
this.parser = parser;
this.resultSubs = resultSubs;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public ParserRequestContext getParserRequestContext() {
return parser;
}
public ParameterParser getParameters() {
return parser.getParameters();
}
public MatchResultSubstitution getMatchResultSubstitution() {
return resultSubs;
}
}
/** 包装request。 */
private class RequestWrapper extends AbstractRequestWrapper {
private String path;
private final boolean prefixMapping;
private final String originalServletPath;
public RequestWrapper(HttpServletRequest request) {
super(RewriteRequestContextImpl.this, request);
// Servlet mapping有两种匹配方式:前缀匹配和后缀匹配。
// 对于前缀匹配,例如:/turbine/aaa/bbb,servlet path为/turbine,path info为/aaa/bbb
// 对于后缀匹配,例如:/aaa/bbb.html,servlet path为/aaa/bbb.html,path info为null
this.prefixMapping = ServletUtil.isPrefixServletMapping(request);
this.originalServletPath = request.getServletPath();
}
public void setPath(String path) {
this.path = trimToNull(path);
}
@Override
public String getServletPath() {
if (path == null) {
return super.getServletPath();
} else {
if (prefixMapping) {
if (startsWithPath(originalServletPath, path)) {
return originalServletPath; // 保持原有的servletPath
} else {
return "";
}
} else {
return path;
}
}
}
@Override
public String getPathInfo() {
if (path == null) {
return super.getPathInfo();
} else {
if (prefixMapping) {
if (startsWithPath(originalServletPath, path)) {
return path.substring(originalServletPath.length()); // 除去servletPath后剩下的部分
} else {
return path;
}
} else {
return null;
}
}
}
}
}