请求映射
从上面的文章,我们知道,当请求经过Adapter处理之后,会调用从容器的pipeline中获取第一个Valve,调用其invoke方法,从而将请求交给容器进行处理
最上层容器是StandardEngine,由其Basic Valve将请求交给下层多个Host容器中的一个,首先看下其Basic Valve是哪个类
public StandardEngine() {
super();
// 添加基础Valve
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
可以看到这里的Basic Valve是StandardEngineValve,接下来看下其invoke方法
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
Host host = request.getHost();
if (host == null) {
// HTTP 0.9 or HTTP 1.0 request without a host when no default host
// is defined.
// Don't overwrite an existing error
if (!response.isError()) {
response.sendError(404);
}
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
}
这里做的主要就是从Request对象中获取Host容器,然后执行该容器的Pipeline
接下来看下Request.getHost
public Host getHost() {
return mappingData.host;
}
可以看出,并没有进行什么计算,只是从MappingData这个属性中获取host,因此可以断定请求映射并不是发生在容器端
那么一定是发生在Connector端,从上篇文章知道,Processor负责生成Request对象,所以我们回过头看下那里的处理逻辑
执行映射
这里看下CoyoteAdapter的service方法中的这一行代码
postParseSuccess = postParseRequest(req, request, res, response);
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
org.apache.coyote.Response res, Response response) throws IOException, ServletException {
// 省略部分代码
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
}
接着看下map方法
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);
}
// 将请求的域名转换为char数组
host.toChars();
// 将uri转换为char数组
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
当请求全称为http://localhost:8080/demo/test/hello时,此时的host是localhost,uri是/demo/test/hello
Host和Context的映射
下面接着看internalMap
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
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();
}
// Virtual host mapping
// 获取当前配置的所有虚拟域名
MappedHost[] hosts = this.hosts;
// 找到当前请求的虚拟域名
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;
}
}
}
// 将mappedHost的object对象赋值给请求的mappingData的host对象,这里的mappedHost.object就是对应虚拟域名的StandardHost对象
mappingData.host = mappedHost.object;
if (uri.isNull()) {
// Can't map context or wrapper without a uri
return;
}
uri.setLimit(-1);
// Context mapping
// 进行应用的映射
// ContextList对象保存了当前虚拟域名下面部署的所有应用名称
// 以我当前运行的实例为例,一共运行如下几个应用:/demo /docs /host-manager /manager
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];
// 判断uri是否以应用的名称开头
if (uri.startsWith(context.name)) {
length = context.name.length();
// uri和应用的名称完全匹配
if (uri.getLength() == length) {
found = true;
break;
} else if (uri.startsWithIgnoreCase("/", length)) {
// uri将前面和应用名称匹配的部分去除之外,第一个字符是/
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);
if (!found) {
if (contexts[0].name.equals("")) {
context = contexts[0];
} else {
context = null;
}
}
if (context == null) {
return;
}
// 设置请求对象的mappding的contextPath,设置为当前应用的名称
mappingData.contextPath.setString(context.name);
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];
}
// 将对应的StandardContext对象赋值给请求的mappingData
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
// Wrapper mapping
if (!contextVersion.isPaused()) {
// 开始进行servlet的映射
internalMapWrapper(contextVersion, uri, mappingData);
}
}
Servlet的映射
private final void internalMapWrapper(ContextVersion contextVersion,
CharChunk path,
MappingData mappingData) throws IOException {
int pathOffset = path.getOffset()