Tomcat 源码分析(二)-请求分析(3)
三、请求与容器中具体组件的匹配
到,前一篇为止,已经分析到了
org.apache.coyote.http11.AbstractHttp11Processor
类 process 方法 ,了解到了请求从Socket的IO流中取出字节流,很久http协议将字节流组装到Tomcat的内部对象org.apache.coyote.Request
中的相关属性中。
接下来这里,要分析了解的,就是:Tomcat的内部请求对象怎么从 Connector 到 Engine 到 Host 到 Context 最后到 Servlet 的过程的。
开始进行内部传递处理的地方
adapter.service(request, response);
这一行代码就是在AbstractHttp11Processor的process 方法中,使用适配器处理的地方。【是在处理socket的数据,解析请求头之后那里】
这里的adapter对象,是在Http11Processor 创建的时候设置的:
//org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler类的 createProcessor 方法
protected Http11Protocol proto;
protected Http11Processor createProcessor() {
Http11Processor processor = new Http11Processor(
proto.getMaxHttpHeaderSize(), proto.getRejectIllegalHeaderName(),
(JIoEndpoint)proto.endpoint, proto.getMaxTrailerSize(),
proto.getAllowedTrailerHeadersAsSet(), proto.getMaxExtensionSize(),
proto.getMaxSwallowSize(), proto.getRelaxedPathChars(),
proto.getRelaxedQueryChars());
processor.setAdapter(proto.adapter); //可以看到在这里进行的设置
.....
register(processor);
return processor;
}
这里可以看的出来,adapter 使用的是Http11Protocol的adapter 变量,
变量的创建则是在Connector类的初始化方法initInternal 中:(配置文件Connector节点的初始化过程中)
//org.apache.catalina.connector;
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter 在这里对adapter进行初始化,使用的是CoyoteAdapter类
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
//上面这种双向绑定对象的方式在Tomcat中相当的常见啊 _(¦3」∠)_
// Make sure parseBodyMethodsSet has a default
if( null == parseBodyMethodsSet ) {
setParseBodyMethods(getParseBodyMethods());
}
......
try {
protocolHandler.init();
} catch (Exception e) {
.......
}
// Initialize mapper listener
mapperListener.init();
}
实际执行的处理方法
这里可知道了adapter.service(request, response)
所调用的方法实际执行的是org.apache.catalina.connector.CoyoteAdapter
类的 service 方法。
部分关键的源码分析:
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
// 这里的时候,就已经把参数req 从org.apache.coyote.Request 转换为 org.apache.catalina.connector.Request,这个才是真正在Tomcat容器流转的对象,也是我们平常使用的。
// 这里的getNote(1)基本就是req差不多的东东,但是少了一些 headers,cookies之类的信息
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
//这里开始 就是给创建的Request 和 Response 对象设置各种的值和传递的参数
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringEncoding
(connector.getURIEncoding());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
boolean postParseSuccess = false;
try {
// Parse and set Catalina and configuration specific
// request parameters 分析并设置catalina和特定于配置的请求参数
// 这里是一个重点,postParseRequest方法会吧req对象里面的一些变量 host、context、warp 信息
// 设置到新的request,response中去,已达到在其中流转。
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
postParseSuccess = postParseRequest(req, request, res, response);
//*****************↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 这里是一个很重要的地方,进行了数据的传递流转
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//这个地方在下面说明的 阀机制原理的时候会分析到这里↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
if (request.isComet()) {
if (!response.isClosed() && !response.isError()) {
if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
// Invoke a read event right away if there are available bytes
if (event(req, res, SocketStatus.OPEN_READ)) {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
} else {
return;
}
} else {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
}
} else {
// Clear the filter chain, as otherwise it will not be reset elsewhere
// since this is a Comet request
request.setFilterChain(null);
}
}
}
service 方法的主要功能就是,创建了最终我们使用的Request和Response,并对其请求传递来的各种数据进行设置到新的对象里面去,包括host、context、warp 信息(这些请求接下来要在Tomcat中流转的信息)。
这个是在第37行的postParseRequest(req, request, res, response)
方法中实现的。
分析一下对其参数的匹配过程
具体的,就是来看一下这个postParseRequest方法:
/** Parse additional request parameters.分析其他请求参数,设置了各种参数到新的对象里面去*/
protected boolean postParseRequest(org.apache.coyote.Request req,
Request request,
org.apache.coyote.Response res,
Response response)
throws Exception {
if (! req.scheme().isNull()) {
request.setSecure(req.scheme().equals("https"));
} else {
//使用连接器方案和安全配置(defaults to "http" and false respectively)
req.scheme().setString(connector.getScheme());
request.setSecure(connector.getSecure());
}
// 配置端口 未显式设置。根据方案使用默认端口 代理端口 没配置这段代码就跳过了
String proxyName = connector.getProxyName();
int proxyPort = connector.getProxyPort();
if (proxyPort != 0) {
req.setServerPort(proxyPort);
} else if (req.getServerPort() == -1) {
if (req.scheme().equals("https")) {
req.setServerPort(443);
} else {
req.setServerPort(80);
}
}
if (proxyName != null) {
req.serverName().setString(proxyName);
}
// Copy the raw URI to the decodedURI
MessageBytes decodedURI = req.decodedURI();
decodedURI.duplicate(req.requestURI());
parsePathParameters(req, request);
// 这里看着是做一些URI的校验
try {
req.getURLDecoder().convert(decodedURI, false);
} catch (IOException ioe) {
res.setStatus(400);
res.setMessage("Invalid URI: " + ioe.getMessage());
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
if (!normalize(req.decodedURI())) {
res.setStatus(400);
res.setMessage("Invalid URI");
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
convertURI(decodedURI, request);
if (!checkNormalize(req.decodedURI())) {
res.setStatus(400);
res.setMessage("Invalid URI character encoding");
connector.getService().getContainer().logAccess(
request, response, 0, true);
return false;
}
// Request mapping.
MessageBytes serverName;
if (connector.getUseIPVHosts()) {
serverName = req.localName();
if (serverName.isNull()) {
res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
}
} else {
serverName = req.serverName();
}
if (request.isAsyncStarted()) {
request.getMappingData().recycle();
}
String version = null;
Context versionContext = null;
boolean mapRequired = true;
while (mapRequired) {
// wrapper,contxt,(request原本是空的) - 这里会做映射
// 这里的map方法会对request.getMappingData()->MappingData 按请求的信息设置成员变量 host、context、warp 的信息,然后再把本身的 MappingData 的值对应设置到request;
// 这里处理的 host、context、warp 的信息就确定了请求之后的处理方向了
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
// If there is no context at this point, it is likely no ROOT context has been deployed
// 做一些请求数据的校验
if (request.getContext() == null) {
res.setStatus(404);
res.setMessage("Not found");
// No context, so use host
Host host = request.getHost();
// Make sure there is a host (might not be during shutdown)
if (host != null) {
host.logAccess(request, response, 0, true);
}
return false;
}
// sessionID的解析
String sessionID;
if (request.getServletContext().getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.URL)) {
// Get the session ID if there was one
sessionID = request.getPathParameter(
SessionConfig.getSessionUriParamName(
request.getContext()));
if (sessionID != null) {
request.setRequestedSessionId(sessionID);
request.setRequestedSessionURL(true);
}
}
// Look for session ID in cookies and SSL session
parseSessionCookiesId(req, request);
parseSessionSslId(request);
sessionID = request.getRequestedSessionId();
。。还有一堆代码 。。就不看了。。反正我是看不懂。。最多看个大概意思。。设置参数,校验下参数之类的。。
这里对上面的代码稍微说明一下:
参数匹配,在从原来的req复制过来之后,对很多的参数进行了校验判断。
其中,Host、Context、Wrapper 参数的设置尤其的重(在85-88行),这里的map方法在设置MappingData中的Host、Context、Wrapper信息的时候,是做了匹配的,这些信息表示的就是本次的请求之后要处理的对象了。
在Tomcat 中,Host、Context、Wrapper为一对多的关系,在请求进来的时候,把他们用Map维护到一个Connector 中,确保了一个请求,就指定一个web应用进行处理了。
这里的Map是怎么进行匹配的,我也不懂了╮(╯_╰)╭ 我看着应该是根据URI地址进行匹配的
匹配完成了
到这里,容器使用的Request,Response 对象已经重新创建好了,并且已经对一些参数进行设置和复制。
同时,是用Map的方法,对Context,Wrapper 参数的值做了对应的匹配,设置。
之后的处理流转就按照配置的参数进行了。
参考资料
小杭 2019-03-15 [(--)]zzz