Http11InputBuffer的service()方法解析了请求行和请求头,完成了对org.apache.coyote.Request对象的初步封装。
Request的进一步封装
org.apache.catalina.connector.Request
Http11Processor->service():
getAdapter().service(request, response);
CoyoteAdapter->service():
......
postParseSuccess = postParseRequest(req, request, res, response);
req是调用getAdapter().service()方法传过来的对象,request的全限定名为org.apache.catalina.connector.Request,它存在对org.apache.coyote.Request的引用,它实现了HttpServletRequest接口,HttpServletRequest继承了ServletRequest,初始状态下,req的notes属性中没有request对象,会新生成,到下次就可以直接使用,response的产生同样如此。
CoyoteAdapter->postParseRequest():
......
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
......
postParseRequest()继续对org.apache.coyote.Request的一些属性赋值,这里会作进一步的解析。比如请求url中带有;name=value;name2=value2,会认为这是参数;在url请求中,特殊字符以%xx编码传递,对这些特殊字符转义,比如%20转义成空格;将双斜杠//转义成单斜杠/等等。接下来是一段解析请求头中的Cookie信息以及赋值sessionId的过程。
org.apache.catalina.connector.RequestFacade
虽然org.apache.catalina.connector.Request存在对org.apache.coyote.Request的引用,然而它的getRequest()方法返回的并不是它
Request->getRequest():
if (facade == null) {
facade = new RequestFacade(this);
}
if (applicationRequest == null) {
applicationRequest = facade;
}
return applicationRequest;
业务逻辑中的request实际类是RequestFacade,使用外观模式主要是出于数据安全的考虑。系统中多个组件之间涉及数据交互,如果组件不想把自己内部的数据全部暴露给其组件,就可以使用外观模式,只暴露出一部分的数据,通过此类完成数据的访问。
RequestFacade存在对Request的引用,对外观的操作等同于对Request的操作。
路由映射器
Mapper->map():
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
getMapper()返回Mapper类,Mapper类有一个MappedHost数组类型的hosts成员变量,host就是我们所理解的虚拟主机,spring boot默认只有一个host容器的引用,名称为loacalhost,根据名称去找,找不到的话就用默认的host,所以无论输localhost还是127.0.0.1,都是找到默认的host(源代码见Mapper->internalMap)。
host类有一个contextList成员变量,其下是MappedContext数组类型的contexts成员变量,content是一个web应用,spring boot默认只有一个content容器的引用,字符串的startsWith("")方法永远返回true,,所以无论输什么url,都是找到默认的content(源代码见Mapper->internalMap)。
content类有一个ContextVersion数组类型的versions成员变量,spring boot默认只有一个,contextVersion有不同的wrapper,根据url的不同定位到使用哪一个wrapper,假设后面匹配的是默认的wrapper(源代码见Mapper->internalMapWrapper)。具体为:
- 精确匹配
- 前缀匹配
- 后缀匹配
- 资源匹配
- 资源精确匹配
- 资源前缀匹配
- 资源后缀匹配
- 默认匹配
假如url为xxxx.jsp,那么就会匹配到extensionWrappers下标为0的wrapper
匹配完成后,可用getMappingData()、getHost()、getContext()、getWrapper()查看匹配的结果。
管道
CoyoteAdapter->service():
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
getContainer()返回Engine,Engine为全局引擎容器,它的标准实现是StandardEngine。
getPipeline()返回Pipeline,Pipeline为管道,负责连接多个对象,它的标准实现是StandardPipeline。每个Pipeline下有first与basic两个阀门,basic又称为基础阀门,阀门指的是实现了Valve接口的类,getFirst(),如果first有就返回first,否则返回basic。
阀门的处理逻辑在invoke()方法中,下面按顺序介绍各阀门的作用:
StandardEngineValve
获取请求对应的主机Host对象,同时调用Host对象中管道的第一个阀门。
ErrorReportValve
将错误以HTML格式输出。
StandardHostValve
获取请求对应的上下文Context对象并调用Context对象中管道的第一个阀门。
AuthenticatorBase
Tomcat内部实现的权限认证,使用场景不多
StandardContextValve
首先判断是否访问了禁止目录WEB-INF或META-INF,获取请求对应的Wrapper对象,调用Wrapper对象中管道的第一个阀门
StandardWrapperValve
统计请求次数、统计处理时间、分配Servlet内存,执行Servlet过滤器、调用Servlet的service方法,释放Servlet内存
StandardWrapperValve->invoke():
filterChain.doFilter
(request.getRequest(), response.getResponse());
附录 多线程与Http11Processor
Http11Processor负责处理任务,它只有一个request成员变量,并且定义在类里,如果一个Http11Processor同时处理多个请求的话,肯定是线程不安全的。对此提出一个疑问,不同请求使用的HttpServletRequest是否是同一个对象?
写了一个demo
@RequestMapping("/test/**")
public String test2(HttpServletRequest request) throws InterruptedException {
log.info("{}", request.hashCode());
TimeUnit.SECONDS.sleep(5);
return "test**";
}
同时多次请求,发现打印的hashCode有时候不一样,有时候一样,猜想:一个Http11Processor处理一个请求,用到的request有缓存机制,不够的时候会生成,对应的Http11Processor也会生成。
对此,找到Http11Processor生成的地方,位于AbstractProtocol->ConnectionHandler->process():Processor processor = connections.get(socket);,connections是一个ConcurrentHashMap型集合,当processor为空时就会创建,然后放到map中。
关键是socket,socket是NioChannel的对象引用,从wrapper中获得,wrapper是SocketProcessorBase的成员变量,找到SocketProcessorBase生成的地方,位于AbstractEndpoint->processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch),socketWrapper是参数传过来的。
socketWrapper是attachment,它生成的地方位于NioEndpoint->Poller->run():NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();,注册的代码位于NioEndpoint->PollerEvent->run(),socketWrapper是PollerEvent的成员变量,找到PollerEvent的生成代码,位于NioEndpoint->Poller->register(),socket、wrapper、pollerEvent都位于此,socket是NioChannel的对象引用。
至此论证猜想,顺便复习了下前面的内容。