Tomcat 源码分析(二)-请求分析(2)
二.Socket 转换为内部请求对象-request
一.处理线程的产生
Tomcat 作为Java实现的一种WEB服务,WEB服务的解释是:在网络环境下可以向发出请求的浏览器提供文档的程序 ;
辣么,这里使用Java实现,进行网络通信必然是用到Socket编程,服务作为Socket服务端,浏览器作为Socket客户端。浏览器与服务器的一次交互就分四步:连接,请求,响应,关闭。接下来就具体看下Tomcat是如何实现的。
Socket 请求连接监听
这里承接上一篇文章,已经分析道,Connector 节点创建启动的最后,作为线程启动的具体实现类是
org.apache.tomcat.util.net.JIoEndpoint.Acceptor
JIoEndpoint.Acceptor 类的工作:作为监听传入TCP/IP连接的后台线程并将它们交给适当的处理器;
源码如下:(会删减掉一些异常处理等一堆细节代码)
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
countUpOrAwaitConnection();
Socket socket = null;
try {
//这里是第一个要注意的地方
// Accept the next incoming connection from the server
//接受服务器的下一个传入连接(具体怎么来的,我现在也不知道╮(╯_╰)╭)
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
countDownConnection();
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (running && !paused && setSocketOptions(socket)) {
// Hand this socket off to an appropriate processor
//这里是第二个,会把连接的socket给处理器进行处理↓
//就是processSocket()方法 【也在JIoEndpoint类中】
if (!processSocket(socket)) {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} else {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} catch ........
}
state = AcceptorState.ENDED;
}
}
以上源码中就两个步骤,获取新来的Socket连接,然后把它对给处理方processSocket
;
processSocket方法的源码像这样:
//处理来自客户机的新连接,并设置keep-alive等属性,并对给执行类进行处理
protected boolean processSocket(Socket socket) {
// Process the request from this socket
try {
//这里Socket包装成SocketWrapper,并设置一些东东。
SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
wrapper.setSecure(isSSLEnabled());
// During shutdown, executor may be null - avoid NPE
if (!running) {
return false;
}
//这里对给SocketProcessor类去处理了,
//这里是另外启动一个线程来处理,以保证并发情况下的另一个请求的响应
getExecutor().execute(new SocketProcessor(wrapper));
} catch 。。。。。。。。
return true;
}
方法所做的事情就是把Socket打包为SocketWrapper,然后新开一个线程去处理这个连接。处理类为SocketProcessor;
启动新线程处理Socket的方法调用
处理类SocketProcessor
作为处理类,是作为新线程执行的。
代码如下:
protected class SocketProcessor implements Runnable {
protected SocketWrapper<Socket> socket = null;
protected SocketStatus status = null;
public SocketProcessor(SocketWrapper<Socket> socket) {
if (socket==null) throw new NullPointerException();
this.socket = socket;
}
@Override
public void run() {
boolean launch = false;
synchronized (socket) {
try {
SocketState state = SocketState.OPEN;
try {
// SSL handshake
serverSocketFactory.handshake(socket.getSocket());
} catch (Throwable t) {... }
if ((state != SocketState.CLOSED)) {
if (status == null) {
//通常是会跑到这里,给Http11ConnectionHandler。process处理了
state = handler.process(socket, SocketStatus.OPEN_READ);
} else {
state = handler.process(socket,status);
}
}
if (state == SocketState.CLOSED) {
。。。。。。。。。
} else if (state == SocketState.OPEN ||
state == SocketState.UPGRADING ||
state == SocketState.UPGRADING_TOMCAT ||
state == SocketState.UPGRADED){
socket.setKeptAlive(true);
socket.access();
launch = true;
} else if (state == SocketState.LONG) {
socket.access();
waitingRequests.add(socket);
}
} finally ......
}
socket = null;
// Finish up this request
}
}
SocketProcessor作为启动新线程来处理连接,这里在判断结束之后,又丢给了handler.process来处理,这里的handler就是Http11ConnectionHandler
,在Http11Protocol
的构造方法中定义的:
public Http11Protocol() {
endpoint = new JIoEndpoint();
cHandler = new Http11ConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
。。。。。
}
而.process
方法,则在Http11ConnectionHandler
的父类AbstractConnectionHandler
中定义的。
丢到AbstractConnectionHandler.process方法来处理数据流,方法的源码像这样:↓
代码太长了,只保留点重点的代码,具体的可以去查看Tomcat源码
@SuppressWarnings("deprecation") // Old HTTP upgrade method has been deprecated
public SocketState process(SocketWrapper<S> wrapper,
SocketStatus status) { //按照上面传进来的数据是OPEN_READ
if (wrapper == null) {
return SocketState.CLOSED;
}
S socket = wrapper.getSocket();
if (socket == null)
return SocketState.CLOSED;
}
Processor<S> processor = connections.get(socket);
if (status == SocketStatus.DISCONNECT && processor == null) {
return SocketState.CLOSED;
}
wrapper.setAsync(false);
ContainerThreadMarker.markAsContainerThread();
try {
if (processor == null) {
processor = recycledProcessors.poll();
}
if (processor == null) {
//这里的时候会调用createProcessor来创建processor对象
//而这个方法的实现在子类中,这里是Http11ConnectionHandler中
processor = createProcessor();
}
initSsl(wrapper, processor);
SocketState state = SocketState.CLOSED;
do {
if (status == SocketStatus.DISCONNECT &&
!processor.isComet()) {
} else if (processor.isAsync() || state == SocketState.ASYNC_END) {
state = processor.asyncDispatch(status);
if (state == SocketState.OPEN) { //接下在会执行到这里
getProtocol().endpoint.removeWaitingRequest(wrapper);
//然后这里就是具体的处理连接数据的地方了
state = processor.process(wrapper);
}
} else if
。。。。。
} while (state == SocketState.ASYNC_END ||
state == SocketState.UPGRADING ||
state == SocketState.UPGRADING_TOMCAT);
if (state == SocketState.LONG) {。。。
} else if (state == SocketState.OPEN) {。。。
} else if (state == SocketState.SENDFILE) {。。。
} else if (state == SocketState.UPGRADED) {。。。
} else {。。。。}
return state;
} catch。。。。。
connections.remove(socket);
// Don't try to add upgrade processors back into the pool
if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor)
&& !processor.isUpgrade()) {
release(wrapper, processor, true, false);
}
return SocketState.CLOSED;
}
从上面↑的代码,可以看到,首先是调用createProcessor 来创建处理对象processor ,而这个方法在这里只是个抽象方法,具体的实现在Http11ConnectionHandler 类中:
@Override
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);
processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
processor.setConnectionUploadTimeout(
proto.getConnectionUploadTimeout());
processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
processor.setCompressionMinSize(proto.getCompressionMinSize());
processor.setCompression(proto.getCompression());
processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
processor.setSocketBuffer(proto.getSocketBuffer());
processor.setMaxSavePostSize(proto.getMaxSavePostSize());
processor.setServer(proto.getServer());
processor.setDisableKeepAlivePercentage(
proto.getDisableKeepAlivePercentage());
processor.setMaxCookieCount(proto.getMaxCookieCount());
register(processor);
return processor;
}
这个没什么解释的了,创建了Http11Processor类的实例,而依据前文代码中:会执行到的处理方法↓
handler.process(socket, SocketStatus.OPEN_READ);
所以,这里调用的方法实际上是:Http11Processor.process方法;
当然,process方法的实现并不在Http11Processor中,而是在父类org.apache.coyote.http11.AbstractHttp11Processor 中实现的。
处理方法的调用,到这里完成了。【具体处理方法接下来分析】
先来理清楚一下,上面提到的乱七八糟的调用:
【20190312 继续学习】
具体的处理Socket数据流方法分析
具体的处理方法是
org.apache.coyote.http11.AbstractHttp11Processor
类的process
方法在这个方法里面处理的hhttp请求的字节流信息,并且封装到了内置对象request中。
具体的代码如下:【源码太多的处理,这里忽略很多,只保留用以分析的部分】
@Override
public SocketState process(SocketWrapper<S> socketWrapper)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Setting up the I/O 从 Socket 中获取输入输出流
//getInputBuffer这返回了inputBuffer ,在构造方法中已经初始化了
//构造方法:inputBuffer = new InternalInputBuffer(request, headerBufferSize);
//构造方法:request.setInputBuffer(inputBuffer);
setSocketWrapper(socketWrapper);
getInputBuffer().init(socketWrapper, endpoint);
getOutputBuffer().init(socketWrapper, endpoint);
// Flags
//....
while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
upgradeInbound == null &&
httpUpgradeHandler == null && !endpoint.isPaused()) {
// Parsing the request header 解析请求行和请求头 【这里就是重点了】
try {
setRequestLineReadTimeout();
//这里 就是处理请求行的方法了↓↓↓↓↓↓↓↓↓↓
if (!getInputBuffer().parseRequestLine(keptAlive)) {
if (handleIncompleteRequestLineRead()) {
break;
}
}
if (endpoint.isPaused()) {
。。。。。。
} else {
。。。。。。
//这里 就是处理请求头的方法了↓↓↓↓↓↓↓↓↓↓
if (!getInputBuffer().parseHeaders()) {
openSocket = true;
readComplete = false;
break;
}
if (!disableUploadTimeout) {
setSocketTimeout(connectionUploadTimeout);
}
}
} catch (IOException e) {
。。。。。。
}
if (!getErrorState().isError()) {
// Setting up filters, and parse some request headers
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
try {
prepareRequest(); // 校验和解析请求头中的属性
} catch (Throwable t) {
。。。。。
}
}
if (maxKeepAliveRequests == 1) {
keepAlive = false;
} else if (maxKeepAliveRequests > 0 &&
socketWrapper.decrementKeepAlive() <= 0) {
keepAlive = false;
}
// Process the request in the adapter 调用适配器的 service 方法
if (!getErrorState().isError()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
adapter.service(request, response);
if(keepAlive && !getErrorState().isError() && (
response.getErrorException() != null ||
(!isAsync() &&
statusDropsConnection(response.getStatus())))) {
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
setCometTimeouts(socketWrapper);
} catch (InterruptedIOException e) {
。。。。。。。
}
}
// Finish the handling of the request 完成请求的处理
rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
if (!isAsync() && !comet) {
if (getErrorState().isError()) {
getInputBuffer().setSwallowInput(false);
} else {
checkExpectationAndResponseStatus();
}
endRequest(); //结束
}
//。。。。。...
rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
}
//。。。。。...
}
ok,这里稍微说明一下,大概的处理时这个样子的:
- 从Socket中获取输入输出流
- 解析请求行和请求头
- 校验并解析请求头中的属性
- 调用适配器的service方法
- 请求处理结束
还有说明一下的是hHttp 协议中的标准请求信息数据的格式: * 请求行(request line) 例如GET /images/logo.gif HTTP/1.1,表示从/images目录下请求logo.gif这个文件。 * 请求头(request header),空行 例如Accept-Language: en * 其他消息体 请求行和标题必须以`<CR><LF>`作为结尾。空行内必须只有`<CR><LF>`而无其他空格。在 HTTP/1.1 协议中,所有的请求头,除 Host 外,都是可选的。 请求行、请求头数据的格式具体看 Http 协议中的描述。所以在从输入流中读取到字节流数据之后必须按照请求行、请求头、消息体的顺序来解析。 这里以请求行数据的解析为例,在 Http 协议中该行内容格式为: Request-Line = Method SP Request-URI SP HTTP-Version CRLF 即请求类型、要访问的资源( URI )以及使用的HTTP版本,中间以特殊字符空格来分隔,以`\r\n`字符结尾。
这里分析下一个,处理请求行的方法:getInputBuffer().parseRequestLine(keptAlive)
:
实现类为org.apache.coyote.http11.InternalInputBuffer
中的parseRequestLine
方法:
代码如下面这个样子的:↓↓↓↓↓↓↓↓
@Override
public boolean parseRequestLine(boolean useAvailableDataOnly)
throws IOException {
int start = 0;
byte chr = 0;
do {
if (pos >= lastValid) {
if (!fill()) //这里调用当前类的fill方法
throw new EOFException(sm.getString("iib.eof.error"));
}
if (request.getStartTime() < 0) {
request.setStartTime(System.currentTimeMillis());
}
chr = buf[pos++];
} while ((chr == Constants.CR) || (chr == Constants.LF));
pos--;
start = pos;
boolean space = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
//根据请求头协议的格式,从中取出表示请求方法的字节数据并设置到内置实例变量 request
//这里循环到行尾,把当前一行的参数设置到request中
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
request.method().setBytes(buf, start, pos - start);
} else if (!HttpParser.isToken(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
}
pos++;
}
//解析 method 和 uri 之间的空格字节 SP
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
pos++;
} else {
space = false;
}
}
// Mark the current buffer position
start = pos;
int end = 0;
int questionPos = -1;
// Reading the URI
//读取表示请求的 URI 的字节数据并放到 request 变量中
boolean eol = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Spec says single SP but it also says be tolerant of HT
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
end = pos;
} else if ((buf[pos] == Constants.CR)
|| (buf[pos] == Constants.LF)) {
// HTTP/0.9 style request
eol = true;
space = true;
end = pos;
} else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {
questionPos = pos;
} else if (questionPos != -1 && !httpParser.isQueryRelaxed(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
} else if (httpParser.isNotRequestTargetRelaxed(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
}
pos++;
}
//这里获取到参数结束位置,然后把字节流信息放到request中
request.unparsedURI().setBytes(buf, start, end - start);
。。。
// Mark the current buffer position
start = pos;
end = 0;
//读取表示请求的 Http 协议版本的字节数据并放到 request 变量中
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.CR) {
end = pos;
} else if (buf[pos] == Constants.LF) {
if (end == 0)
end = pos;
eol = true;
} else if (!HttpParser.isHttpProtocol(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
pos++;
}
if ((end - start) > 0) {
request.protocol().setBytes(buf, start, end - start);
} else {
request.protocol().setString("");
}
return true;
}
protected boolean fill(boolean block) throws IOException {
int nRead = 0;
if (parsingHeader) {
if (lastValid == buf.length) {
throw new IllegalArgumentException
(sm.getString("iib.requestheadertoolarge.error"));
}
nRead = inputStream.read(buf, pos, buf.length - lastValid);
if (nRead > 0) {
lastValid = pos + nRead;
}
} else {
if (buf.length - end < 4500) {
buf = new byte[buf.length];//从输入流中读取数据到缓冲区 buf
end = 0;
}
pos = end;
lastValid = pos;
nRead = inputStream.read(buf, pos, buf.length - lastValid);
if (nRead > 0) {
lastValid = pos + nRead;
}
}
return (nRead > 0);
}
以上就是,根据 Http 协议解析请求行( request line )的代码实现部分 ,其他的解析请求找对应的方法看源码就行了,跟这个差不多的。
【就是按照HTTP的协议,解析数据的各种参数之类的,然后设置到request里面去】
参考资料
小杭 时间忘记了 ╮(╯_╰)╭