前言
tomcat从架构上看,包含Service,Engine,Host,Context,Wrapper。那么,当用户发起一个请求时,tomcat是如何将url映射到具体的Wrapper上的呢,就这是本文要阐述的问题。
Mapper组件机制
Tomcat 设计了 Mapper(映射)组件 完成 url和Host、Context、Wrapper等组件容器的映射。
Mapper组件的核心功能是提供请求路径的路由映射,根据某个请求路径通过计算得到相应的Servlet(Wrapper)。这节看下Mapper的实现细节,包括Host容器、Context容器、Wrapper容器等的映射关系以及映射算法。
与url到Wrapper映射相关的类位于org.apache.catalina.mapper包下,包含四个类:
- Mapper:映射关系最核心的、最重要的类。完成url与Host,Context,Wrapper映射关系的初始化、变更、存储及映射
- MapperListener:实现了ContainerListener与 LifecycleListener接口,监听tomcat组件的变化,当有Host,Context及Wrapper变更时,调用Mapper相关方法,增加或者删除Host,Context,Wrapper等。
- MappingData:url映射后的数据,表示一个url具体映射到哪个host,哪个context,哪个wrapper上。
- WrapperMappingInfo:表示一个Wrapper的信息,是一个普通的类,不太重要。
Mapper主要功能是完成url到Wrapper的映射,有三个主要的功能
- 1、映射关系存储:存储所有的Host,context及Wrapper的对应关系;
- 2、映射关系初始化及变更:当新增一个组件或者移除一个组件时,mapper如何维护url到Wrapper的映射关系;
- 3、映射关系使用:根据url,映射到具体的host,context和wrapper。
总体结论
一个Service有一个Engine,而一个Engine中有一个Mapper。根据Engine,Host,Context及Wrapper的对应关系,易得到以下的结论。
一个Mapper中,应该保存有多个Host对象(的确是这样的,每个Host对象称之为MappedHost,多个MappedHost以数组形式组合,各元素通过其name进行排序)
一个Host对象包含多个context(Context在Mapper中定义为MappedContext,也是通过数组形式存在,并且元素根据MappedContext的命名排序。但是与组件不同的是,每一个Context可以有多个版本,因此每一个MappedContext 包含了多个ContextVersion,每一个MappedContext下的多个ContextVersion表示同一个Context的多个版本)
一个Context包含多个Wrapper(此处的Context在Mapper中为ContextVersion,包含多个Wrapper,这些Wrapper分成四类,精确匹配的Wrapper,前缀匹配的Wrapper,扩展名匹配的Wrapper,默认的Wrapper,在Mapper中,每一个Wrapper都通过一个MappedWrapper表示)
因此,Mapper的构成可以用下图表示
详细代码
辅助类MapElement
个人感觉这个名字起的不太好,让人以为是Map,其实就是一个含有名字的Object。
public final class Mapper {
protected abstract static class MapElement<T> {
public final String name;
public final T object;
public MapElement(String name, T object) {
this.name = name;
this.object = object;
}
}
}
mapper 的属性
public final class Mapper {
//定义所有的Host组合,表示一个Engine下所有Host
volatile MappedHost[] hosts = new MappedHost[0];
/**
* Default host name.
*/
private volatile String defaultHostName = null;
private volatile MappedHost defaultHost = null;
/**
* Mapping from Context object to Context version to support
* RequestDispatcher mappings.
*/
private final Map<Context, ContextVersion> contextObjectToContextVersionMap =
new ConcurrentHashMap<>();
}
MappedHost定义
public final class Mapper {
protected static final class MappedHost extends MapElement<Host> {
// 包含的COntext信息,定义的是一个Object,其实内部是一个数组,下面可以看到
public volatile ContextList contextList;
/**
* Link to the "real" MappedHost, shared by all aliases.
* 因为一个Host,可能是真正的一个Host,也可能只是起、一个别名,而在Mapper中,两者都是MapperHost,
* 只是真正的MapperHost的realHost是自身,而alias的MapperHost的realHost是其对应的真实的MapperHost
*/
private final MappedHost realHost;
/**
* 所有注册的别名的MapperHost
* 对于alias MapperHost,这个值为null。当一个 real MapperHost 没有alias的时候,这个值也为空
*/
private final List<MappedHost> aliases;
}
}
ContextList 定义
public final class Mapper {
protected static final class ContextList {
// 如在MappedHost定义中提到的,这个里面存储的是一系列的MapperContext
public final MappedContext[] contexts;
// 这个值主要是为了 url 到wrapper的映射时更快的定位目标,在看代码时可以忽略
public final int nesting;
}
}
MappedContext定义
public final class Mapper {
protected static final class MappedContext extends MapElement<Void> {
// 一个MappedContext 中又有多个ContextVersion,表示多个版本的context
public volatile ContextVersion[] versions;
public MappedContext(String name, ContextVersion firstVersion) {
super(name, null);
this.versions = new ContextVersion[] { firstVersion };
}
}
}
ContextVersion的定义
public final class Mapper {
protected static final class ContextVersion extends MapElement<Context> {
// context 的匹配路径
public final String path;
// 没有直接的用处,主要目的是为了计算Context中的nesting
public final int slashCount;
public final WebResourceRoot resources;
//context 的欢迎页面
public String[] welcomeResources;
// 默认的wrapper
public MappedWrapper defaultWrapper = null;
// 精确匹配路径的 wrapper
public MappedWrapper[] exactWrappers = new MappedWrapper[0];
// 通配符结束的wrapper
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
// 扩展名匹配的wrapper
public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
public int nesting = 0;
// 这个context是否还可用,是否被暂停
private volatile boolean paused;
}
}
MappedWrapper 定义
仅有两个属性
public final class Mapper {
protected static class MappedWrapper extends MapElement<Wrapper> {
public final boolean jspWildCard;
public final boolean resourceOnly;
public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,
boolean resourceOnly) {
super(name, wrapper);
this.jspWildCard = jspWildCard;
this.resourceOnly = resourceOnly;
}
}
}
小结:
从定义可以看出,Mapper中包含了这个Engine中所有的映射关系,首先包含了多个MapperHost,MapperHost分两类,一个是real MapperHost,另一类是 alias的MapperHost。每一个MapperHost都包含有多个MappedContext,代表不同的Context,每一个MappedContext又有多个ContextVersion,表示每一个Context的不同的版本号,这样在请求参数中可以带版本号进行请求,每一个ContextVersion可以包含一个默认MapperWrapper,多个精确匹配的Wrapper,多个扩展名匹配的Wrapper,多个通配符匹配的Wrapper,这其中多种不同的Wrapper就是根据web.xml中的servlet的 url-pattern不同类型确定的。
映射关系初始化及变更
刚初始化的Mapper,映射关系为空,通过增加Host,增加Context,增加Wrapper,实现对映射关系的初始化及变更。
addHost
public final class Mapper {
/**
* Add a new host to the mapper.
*
* @param name Virtual host name
* @param aliases Alias names for the virtual host
* @param host Host object
*/
public synchronized void addHost(String name, String[] aliases,
Host host) {
// 如果name 以 *. 打头,则去掉 *.
// 因此,当用户请求的Host为 abc.def.ge.com时,可能匹配abc.def.ge.com,也可能会匹配def.ge.com
name = renameWildcardHost(name);
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
MappedHost newHost = new MappedHost(name, host);
// 将新的MapperHost插入MapperHost数组中,
// 如果没有以name命名的MapperHost,则插入,返回true,如果有相同的名字,则返回失败。
if (insertMap(hosts, newHosts, newHost)) {
// 插入成功,用新的的数组替换原来的MapperHost数组
hosts = newHosts;
// 如果是默认的host,则设为defaultHost
if (newHost.name.equals(defaultHostName)) {
defaultHost = newHost;
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.success", name));
}
} else {
// 说明有相同名字的MapperHost,找到已经存在的Host的对象,根据相同或者不同,打不同的日志
MappedHost duplicate = hosts[find(hosts, name)];
if (duplicate.object == host) {
// The host is already registered in the mapper.
// E.g. it might have been added by addContextVersion()
if (log.isDebugEnabled()) {
log.debug(sm.getString("mapper.addHost.sameHost", name));
}
newHost = duplicate;
} else {
log.error(sm.getString("mapper.duplicateHost", name,
duplicate.getRealHostName()));
// Do not add aliases, as removeHost(hostName) won't be able to
// remove them
return;
}
}
// 如果有别名,处理别名,根据这个Host对应的别名,根据新的MapperHost(readHost相同),插入MapperHost数组中。
// 并且对当前的Mapperhost对象设置aliasHost属性。
List<MappedHost> newAliases = new ArrayList<>(aliases.length);
for (String alias : aliases) {
alias = renameWildcardHost(alias);
MappedHost newAlias = new MappedHost(alias, newHost);
if (addHostAliasImpl(newAlias)) {
newAliases.add(newAlias);
}
}
// 将aliasHost增加到Mapper的 hosts数组中
newHost.addAliases(newAliases);
}
}
addContextVersion
public final class Mapper {
/**
* Add a new Context to an existing Host.
*
* @param hostName Virtual host name this context belongs to
* @param host Host object
* @param path Context path
* @param version Context version
* @param context Context object
* @param welcomeResources Welcome files defined for this context
* @param resources Static resources of the context
* @param wrappers Information on wrapper mappings
*/
public void addContextVersion(String hostName, Host host, String path,
String version, Context context, String[] welcomeResources,
WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
// 同样,将 *. 开头的去掉 *
hostName = renameWildcardHost(hostName);
// 根据hostName查找相应的host
MappedHost mappedHost = exactFind(hosts, hostName);
if (mappedHost == null) {
// 如果没有找到,就增加一个
addHost(hostName, new String[0], host);
// 再次查找一次
mappedHost = exactFind(hosts, hostName);
// 这个应该不会发生,因为当不存在时,根据上面的addHost的代码,发现肯定会增加一个以hostName命名的MappedHost
if (mappedHost == null) {
log.error("No host found: " + hostName);
return;
}
}
// 如果是一个aliasHost,不处理
if (mappedHost.isAlias()) {
log.error("No host found: " + hostName);
return;
}
// 获取 path,即url中 “/”的长度
int slashCount = slashCount(path);
synchronized (mappedHost) {
// 新建一个ContextVersion,并且如果有wrappers,也增加到相应的ContextVersion中
ContextVersion newContextVersion = new ContextVersion(version,
path, slashCount, context, resources, welcomeResources);
if (wrappers != null) {
// 增加wrapper到ContextVersion中,逻辑下面细说
addWrappers(newContextVersion, wrappers);
}
ContextList contextList = mappedHost.contextList;
// 从这个可以看出,contextList中不同的MapperContext是通过path区分的,而这个path就是context的path
MappedContext mappedContext = exactFind(contextList.contexts, path);
// 如是没有这个path的mapperContext
if (mappedContext == null) {
// 新建一个mapperContext,并获取已有的mappedContext与这个mappedContext的新的组合
mappedContext = new MappedContext(path, newContextVersion);
ContextList newContextList = contextList.addContext(
mappedContext, slashCount);
// 如果不为空时,更新mapperHost。
// 当mappedContext为空时,会返回null,而看上面,是查找不到的时候才会填加,因此,这里正常情况下不为null
if (newContextList != null) {
updateContextList(mappedHost, newContextList);
// 并将这个context 与 ContextVersion建立关联
contextObjectToContextVersionMap.put(context, newContextVersion);
}
} else {
// mappedContext 不为空,说明不是第一次添加,则直接将这个version的context增加到所有具有相同path的context中,形成多version的MappedContext
ContextVersion[] contextVersions = mappedContext.versions;
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
if (insertMap(contextVersions, newContextVersions,
newContextVersion)) {
mappedContext.versions = newContextVersions;
contextObjectToContextVersionMap.put(context, newContextVersion);
} else {
// Re-registration after Context.reload()
// Replace ContextVersion with the new one
int pos = find(contextVersions, version);
if (pos >= 0 && contextVersions[pos].name.equals(version)) {
contextVersions[pos] = newContextVersion;
contextObjectToContextVersionMap.put(context, newContextVersion);
}
}
}
}
}
}
addWrapper
addWrapper有多个重载方法,大都是参数不同而已,最终都是调用下面的方法实现。
public final class Mapper {
/**
* Adds a wrapper to the given context.
*
* @param context The context to which to add the wrapper
* @param path Wrapper mapping,注意这个路径,这个路径不带context的路径
* @param wrapper The Wrapper object
* @param jspWildCard true if the wrapper corresponds to the JspServlet
* and the mapping path contains a wildcard; false otherwise
* @param resourceOnly true if this wrapper always expects a physical
* resource to be present (such as a JSP)
*/
protected void addWrapper(ContextVersion context, String path,
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
synchronized (context) {
if (path.endsWith("/*")) {
// Wildcard wrapper
// 通配符匹配的 wrapper,放到通配符匹配的 mappedWrapper中
String name = path.substring(0, path.length() - 2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.wildcardWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.wildcardWrappers = newWrappers;
int slashCount = slashCount(newWrapper.name);
// 将ContextVersion的nesting值设置为path的 “/”的最大数量
if (slashCount > context.nesting) {
context.nesting = slashCount;
}
}
// 扩展名匹配,将结果放到扩展名匹配的 mappedwrapper中
} else if (path.startsWith("*.")) {
// Extension wrapper
String name = path.substring(2);
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.extensionWrappers;
MappedWrapper[] newWrappers =
new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.extensionWrappers = newWrappers;
}
// 默认Wrapper
} else if (path.equals("/")) {
// Default wrapper
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
jspWildCard, resourceOnly);
context.defaultWrapper = newWrapper;
// 精确匹配的wrapper
} else {
// Exact wrapper
final String name;
if (path.length() == 0) {
// Special case for the Context Root mapping which is
// treated as an exact match
name = "/";
} else {
name = path;
}
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
jspWildCard, resourceOnly);
MappedWrapper[] oldWrappers = context.exactWrappers;
MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1];
if (insertMap(oldWrappers, newWrappers, newWrapper)) {
context.exactWrappers = newWrappers;
}
}
}
}
}
根据上面的代码,我们可以看出,增加Wrapper时,需要提供往哪个ContextVersion中增加,这个wrapper匹配的路径是什么。然后会根据路径是分为通配符匹配,扩展名匹配,默认匹配及精确匹配。同时可以看出对于扩展名匹配,精确路径匹配以及通配符匹配,都是先配置的生效,因为如果已经配置过一个,后面插入会失败,而对于默认的Wrapper,正是相反,后配置的生效。同时,可以看到ContextVersion中的nesting存的是这个ContextVersion下,通配符wrapper对应的路径中含最多的“/”的“/”的长度。
这里没有上MapperListener的相关源码,MapperListener监听各组件的变化,当有组件变化时,调用mapper的相关的方法,增加相应在的组件,或者删除相应的组件。
小结
addHost
- 对host的名字做处理,如果以 *. 打头,去掉 *.
- 将host插入已存在的MappedHost数组中
- 插入成功:当hostName不存在时,会插入成功
- 插入失改:打日志,并退出
- 处理别名
- 根据别名,新建MappedHost,增加到原来的MappedHost数组中
- 将对应的aliasHost与原来的realHost建立关联
addContextVersion
- 查找MappedHost,如果没有找到,就新建一个
- 如果找到了,但是是aliasHost,则不进行处理,退出
- 新建ContextVersion
- 根据path,查找MappedContext
- 找到,将ContextVersion 加入到MappedContext中
- 没有找到,新建一个,将Context加入到MappedContext中
addWrapper
- 如果是前缀匹配:即path 以 /* 结尾,则将之放到前缀匹配的MappedWrapper数组中(当数组中已经存在该匹配字符串时,不能再进行插入,先插入的先生效)
- 扩展名匹配:即path以 *. 打头,将之放到扩展名匹配的MappedWrapper数组中(先插入的生效,同path不能再次插入)
- 默认Wrapper:path 等于”/”,表示默认的匹配,后配置的生效
- 精确匹配:除上述以前的,放到精确匹配MappedWrapper数组中(先插入的生效,同path的不能重复插入)
映射关系使用
映射后的数据表示(MappingData
)
public class MappingData {
// 映射后的虚拟Host
public Host host = null;
public Context context = null;
public int contextSlashCount = 0;
public Context[] contexts = null;
public Wrapper wrapper = null;
public boolean jspWildCard = false;
public final MessageBytes contextPath = MessageBytes.newInstance();
public final MessageBytes requestPath = MessageBytes.newInstance();
public final MessageBytes wrapperPath = MessageBytes.newInstance();
public final MessageBytes pathInfo = MessageBytes.newInstance();
public final MessageBytes redirectPath = MessageBytes.newInstance();
// Fields used by ApplicationMapping to implement javax.servlet.http.Mapping
public MappingMatch matchType = null;
}
public enum MappingMatch {
CONTEXT_ROOT,
DEFAULT,
EXACT,
EXTENSION,
PATH
}
映射处理
映射是通过用户请求的host、请求url及version,将结果映射到输入的mappingData中。
public final class Mapper {
// 映射的入口,此方法只做一件事,如果host为空时,就设置为默认host
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData) throws IOException {
if (host.isNull()) {
String defaultHostName = this.defaultHostName;
if (defaultHostName == null) {
return;
}
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
// 调用映射方法,这个是核心方法
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
// 详细的map过程
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
// 如果mappingData.host不为空,说明mappingData是有问题的。抛出异常
if (mappingData.host != null) {
// The legacy code (dating down at least to Tomcat 4.1) just
// skipped all mapping work in this case. That behaviour has a risk
// of returning an inconsistent result.
// I do not see a valid use case for it.
throw new AssertionError();
}
// host 匹配
// Virtual host mapping
MappedHost[] hosts = this.hosts;
// 查找的MappedHost,可以分为三步
// 1. 根据当前的hostName查找具体的host
// 2. 如果没有找到的话,就去掉第一个 "."之前的部分,再次查找。
// 例如,当输入时 abc.bde.ef.com,时,如果没有找到相应匹配的host时,则查找bde.ef.com。
// 因为看到在构建mapper,增加host上,当有会把host name的前导* 去掉的。
// 3.如果还没有找到,则设置为 默认的host
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
if (mappedHost == null) {
// Note: Internally, the Mapper does not use the leading * on a
// wildcard host. This is to allow this shortcut.
int firstDot = host.indexOf('.');
if (firstDot > -1) {
int offset = host.getOffset();
try {
host.setOffset(firstDot + offset);
mappedHost = exactFindIgnoreCase(hosts, host);
} finally {
// Make absolutely sure this gets reset
host.setOffset(offset);
}
}
if (mappedHost == null) {
mappedHost = defaultHost;
if (mappedHost == null) {
return;
}
}
}
mappingData.host = mappedHost.object;
if (uri.isNull()) {
// Can't map context or wrapper without a uri
return;
}
uri.setLimit(-1);
// Context mapping
// 根据url选择相应的Context,选择的依据是 以path作为url的前缀,并且contextPath 一定是url中 “/”分割的。
// 比如,url为 /test/abc/def ,contextpath 分别为 /test/abc 与/test/abcd,则会选择前者
// 当没有匹配时,如果有默认的context,则选择默认的context,否则直接返回
ContextList contextList = mappedHost.contextList;
MappedContext[] contexts = contextList.contexts;
int pos = find(contexts, uri);
if (pos == -1) {
return;
}
int lastSlash = -1;
int uriEnd = uri.getEnd();
int length = -1;
boolean found = false;
MappedContext context = null;
while (pos >= 0) {
context = contexts[pos];
if (uri.startsWith(context.name)) {
length = context.name.length();
if (uri.getLength() == length) {
found = true;
break;
} else if (uri.startsWithIgnoreCase("/", length)) {
found = true;
break;
}
}
if (lastSlash == -1) {
lastSlash = nthSlash(uri, contextList.nesting + 1);
} else {
lastSlash = lastSlash(uri);
}
uri.setEnd(lastSlash);
pos = find(contexts, uri);
}
uri.setEnd(uriEnd);
// 如果没有找到,如果第一个context的name为空,即全匹配时,则设置为默认的context
if (!found) {
if (contexts[0].name.equals("")) {
context = contexts[0];
} else {
context = null;
}
}
if (context == null) {
return;
}
//设置 context的路径
mappingData.contextPath.setString(context.name);
// 匹配version, 如果有传version,则匹配相应的version,如果没有传或者没有匹配上,则匹配版本最大的version
ContextVersion contextVersion = null;
ContextVersion[] contextVersions = context.versions;
final int versionCount = contextVersions.length;
if (versionCount > 1) {
Context[] contextObjects = new Context[contextVersions.length];
for (int i = 0; i < contextObjects.length; i++) {
contextObjects[i] = contextVersions[i].object;
}
mappingData.contexts = contextObjects;
if (version != null) {
contextVersion = exactFind(contextVersions, version);
}
}
if (contextVersion == null) {
// Return the latest version
// The versions array is known to contain at least one element
contextVersion = contextVersions[versionCount - 1];
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
// Wrapper mapping
// 最后匹配 Wrapper
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
}
//匹配 wrapper 逻辑
// 总的来说可以分为以下几个步骤
// 1. 精确匹配
// 2. 前缀匹配/通配符匹配
// 3. 扩展名匹配
// 4. 查找欢迎文件,在path的路径上增加上欢迎文件,再依次精确匹配,前缀匹配以及文件夹匹配, 如果依然没有匹配上,再找扩展名匹配。
// 4. 默认Wrapper
/**
* Wrapper mapping.
* @throws IOException if the buffers are too small to hold the results of
* the mapping.
*/
private final void internalMapWrapper(ContextVersion contextVersion,
CharChunk path,
MappingData mappingData) throws IOException {
int pathOffset = path.getOffset();
int pathEnd = path.getEnd();
boolean noServletPath = false;
int length = contextVersion.path.length();
if (length == (pathEnd - pathOffset)) {
noServletPath = true;
}
int servletPath = pathOffset + length;
path.setOffset(servletPath);
// Rule 1 -- Exact Match
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
internalMapExactWrapper(exactWrappers, path, mappingData);
// Rule 2 -- Prefix Match
boolean checkJspWelcomeFiles = false;
MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
if (mappingData.wrapper == null) {
internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
path, mappingData);
if (mappingData.wrapper != null && mappingData.jspWildCard) {
char[] buf = path.getBuffer();
if (buf[pathEnd - 1] == '/') {
/*
* Path ending in '/' was mapped to JSP servlet based on
* wildcard match (e.g., as specified in url-pattern of a
* jsp-property-group.
* Force the context's welcome files, which are interpreted
* as JSP files (since they match the url-pattern), to be
* considered. See Bugzilla 27664.
*/
mappingData.wrapper = null;
checkJspWelcomeFiles = true;
} else {
// See Bugzilla 27704
mappingData.wrapperPath.setChars(buf, path.getStart(),
path.getLength());
mappingData.pathInfo.recycle();
}
}
}
if(mappingData.wrapper == null && noServletPath &&
contextVersion.object.getMapperContextRootRedirectEnabled()) {
// The path is empty, redirect to "/"
path.append('/');
pathEnd = path.getEnd();
mappingData.redirectPath.setChars
(path.getBuffer(), pathOffset, pathEnd - pathOffset);
path.setEnd(pathEnd - 1);
return;
}
// Rule 3 -- Extension Match
MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
internalMapExtensionWrapper(extensionWrappers, path, mappingData,
true);
}
// Rule 4 -- Welcome resources processing for servlets
if (mappingData.wrapper == null) {
boolean checkWelcomeFiles = checkJspWelcomeFiles;
if (!checkWelcomeFiles) {
char[] buf = path.getBuffer();
checkWelcomeFiles = (buf[pathEnd - 1] == '/');
}
if (checkWelcomeFiles) {
for (int i = 0; (i < contextVersion.welcomeResources.length)
&& (mappingData.wrapper == null); i++) {
path.setOffset(pathOffset);
path.setEnd(pathEnd);
path.append(contextVersion.welcomeResources[i], 0,
contextVersion.welcomeResources[i].length());
path.setOffset(servletPath);
// Rule 4a -- Welcome resources processing for exact macth
internalMapExactWrapper(exactWrappers, path, mappingData);
// Rule 4b -- Welcome resources processing for prefix match
if (mappingData.wrapper == null) {
internalMapWildcardWrapper
(wildcardWrappers, contextVersion.nesting,
path, mappingData);
}
// Rule 4c -- Welcome resources processing
// for physical folder
if (mappingData.wrapper == null
&& contextVersion.resources != null) {
String pathStr = path.toString();
WebResource file =
contextVersion.resources.getResource(pathStr);
if (file != null && file.isFile()) {
internalMapExtensionWrapper(extensionWrappers, path,
mappingData, true);
if (mappingData.wrapper == null
&& contextVersion.defaultWrapper != null) {
mappingData.wrapper =
contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars
(path.getBuffer(), path.getStart(),
path.getLength());
mappingData.wrapperPath.setChars
(path.getBuffer(), path.getStart(),
path.getLength());
mappingData.requestPath.setString(pathStr);
mappingData.wrapperPath.setString(pathStr);
}
}
}
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}
/* welcome file processing - take 2
* Now that we have looked for welcome files with a physical
* backing, now look for an extension mapping listed
* but may not have a physical backing to it. This is for
* the case of index.jsf, index.do, etc.
* A watered down version of rule 4
*/
if (mappingData.wrapper == null) {
boolean checkWelcomeFiles = checkJspWelcomeFiles;
if (!checkWelcomeFiles) {
char[] buf = path.getBuffer();
checkWelcomeFiles = (buf[pathEnd - 1] == '/');
}
if (checkWelcomeFiles) {
for (int i = 0; (i < contextVersion.welcomeResources.length)
&& (mappingData.wrapper == null); i++) {
path.setOffset(pathOffset);
path.setEnd(pathEnd);
path.append(contextVersion.welcomeResources[i], 0,
contextVersion.welcomeResources[i].length());
path.setOffset(servletPath);
internalMapExtensionWrapper(extensionWrappers, path,
mappingData, false);
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}
// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;
}
// Redirection to a folder
char[] buf = path.getBuffer();
if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
String pathStr = path.toString();
// Note: Check redirect first to save unnecessary getResource()
// call. See BZ 62968.
if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {
WebResource file;
// Handle context root
if (pathStr.length() == 0) {
file = contextVersion.resources.getResource("/");
} else {
file = contextVersion.resources.getResource(pathStr);
}
if (file != null && file.isDirectory()) {
// Note: this mutates the path: do not do any processing
// after this (since we set the redirectPath, there
// shouldn't be any)
path.setOffset(pathOffset);
path.append('/');
mappingData.redirectPath.setChars
(path.getBuffer(), path.getStart(), path.getLength());
} else {
mappingData.requestPath.setString(pathStr);
mappingData.wrapperPath.setString(pathStr);
}
} else {
mappingData.requestPath.setString(pathStr);
mappingData.wrapperPath.setString(pathStr);
}
}
}
path.setOffset(pathOffset);
path.setEnd(pathEnd);
}
}
小结
现在再来总结一下匹配的逻辑
-
1、匹配Host(不区分大小写)
- 匹配 输入的hostName
- 没有匹配上 ,将hostName第一个“.”及其之前的内容去掉,再次进行匹配
- 没有匹配上,采用默认的host
-
2、匹配Context
- 根据url与context的path进行匹配(前缀匹配)。
- 没有匹配上,如果第一个context的path 为空,则采用第一个
- 没有匹配上,则返回
-
3、匹配ContextVersion
- 根据传入的version进行精确匹配
- 当没有找到时,就采用最大的version对应的ContextVersion
-
4、匹配Wrapper
- 先找精确匹配Wrapper
- 当没有找到时,再找扩展名匹配
- 当没有找到时,再找前缀匹配
- 当没有找到时,根据欢迎文件查找
- 当没有找到时,采用默认的Wrapper