1、HTTP 1.1新特性
1)持久连接:connection: keep-alive
2)块编码:使用长连接后,发送方大部分时候无法计算出要发送的内容长度,也不能等所有资源都准备好了再发送,HTTP 1.1 使用transfer-encoding header来处理这个问题。transfer-encoding表示有多少以块形式的字节流将会被发送给接收方,对于每一个块数据,长度(16进制)+ CR/LF + 数据将会被发送,整个事物以0\r\n结束。例如:I'm as helpless as a kitten up a tree.这个数据如果以3个块发送的话,其发送格式为:
1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n
3) Expect: 100-continue Header 使用,当要发送长内容请求的时候,客户端在无法保证服务器一定会接收的时候,可以先发送这个header到服务器询问是否允许客户端的长内容请求,如果允许,服务端会返回HTTP/1.1 100 Continue + CRLF + CRLF给客户端。
2、连接器的职责:
1)接收客户端的请求,解析HTTP协议:请求行(GET /api/xxx HTTP/1.1 ...)解析,header解析,数据解析
2)构建Request对象
3)构建Response对象
3、连接器处理流程
整个类图分为三块,类图1为连接器和容器、连接器和处理器之间的关系;类图2为Request结构图,类图3为Response结构图。
3、1 HttpConnector启动阶段
1)通过调用initialize()方法和open()方法,创建了一个ServerSocket对象
2)调用start()方法,一是启动接收客户端请求的线程,二是初始化HttpProcessor对象池,该对象池为Stack,这里主要看一下HttpProcessor的创建和启动。
private HttpProcessor newProcessor() {
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
if (processor instanceof Lifecycle) {
try {
/**
* HttpProcessor 本身是一个Runnable和Lifecycle,在这里对其进行了启动操作,
* 其实就是启动了一个线程,来执行HttpProcessor这个Runnable
*/
((Lifecycle) processor).start();
} catch (LifecycleException e) {
log("newProcessor", e);
return (null);
}
}
created.addElement(processor);
return (processor);
}
3、2 HttpConnector对请求处理过程
public void run() {
socket = serverSocket.accept();
HttpProcessor processor = createProcessor();
processor.assign(socket);
}
run()主体逻辑就上面三行代码,在接收到请求后,先通过createProcessor方法获取一个HttpProcessor对象,createProcessor获取HttpProcessor对象的逻辑比较简单,直接看代码就行。
private HttpProcessor createProcessor() {
synchronized (processors) {
if (processors.size() > 0) {
// 对象池中还有对象,直接从池子里面获取
return ((HttpProcessor) processors.pop());
}
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
// 池子里面没有了,只要没有超过设置的最大容量,就创建一个对象
return (newProcessor());
} else {
if (maxProcessors < 0) {
// 如果最大容量设置为小于0,则直接创建一个对象
return (newProcessor());
} else {
return (null);
}
}
}
}
获取到HttpProcessor之后,调用其assign方法。
3、3 HttpProcessor启动过程
通过上面知道,HttpProcessor是在HttpConnector的newProcessor()中进行创建和启动的,在start()中,其主要工作是启动了处理线程
public void start() throws LifecycleException {
if (started)
throw new LifecycleException
(sm.getString("httpProcessor.alreadyStarted"));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// 启动线程,Runnable为本身
threadStart();
}
现在看一下这个线程的主要工作是啥:
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
// 1、调用await()方法获取一个socket
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
// 2、对socket进行处理
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
// 3、对象回收
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
可以看到,其工作主要是:从await()方法获取socket,交给process()方法处理,然后HttpConnector对该对象进行回收再利用。
看一下socket是如何获取的:
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
/**
* 进入等待状态,直到HTTPConnector线程调用了notifyAll().
* 这个唤醒操作在assign()中完成。
*/
wait();//
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
/**
* 这里是唤醒assign()中的wait(),让其可以继工作了。
*/
notifyAll();
return socket;
}
await首先进入一个等待状态,只有被其他线程唤醒了,才会进行下一步的工作,也就是返回socket,那么这个等待由谁唤醒呢?在HTTPConnector的run()方法中可以知道,socket来源于HTTPConnector,然后其把这个socket传给了HttpProcessor的assign()方法:
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
// 让当前线程进入等待状态,assign方法由HTTPConnector所在的线程
// 调用,wait是在HttpProcessor对象中起调的,所以这里是
// HttpProcessor让HTTPConnector所在的线程进行等待
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
// 唤醒其他线程中的所有wait()方法,这里的调用者是HttpProcessor,
//也就是唤醒HttpProcessor线程中所有的wait()方法
notifyAll();
}
assign()方法接收来自于HTTPConnector的socket,然后赋值在HttpProcessor的全局变量this.socket上,在await()方法中就能获取到这个socket了,因为assign()工作在HTTPConnector所属的线程,而await()工作在HttpProcessor所属的线程中,两者之间通过wait()和notifyAll()来互相通信。
这里有个疑问,不知道assign()里面为什么要wait(),await()里面为什么要notifyAll(),根据设计,HttpProcessor在当前socket没有处理完的时候,其不会被回收到对象池中,也就根本没有机会去处理下一个socket,但是这里却这样设计了,而且看await方法返回的socket也不是全局变量,而是用了一个局部变量来存储然后返回的是这个局部变量。官方的解释是说全局的变量用来放置下一个到来的socket,以防当前socket在没有完全处理完成而下一个socket又到来的情况,但是这种情况是怎么出现的,不解。
3、4 HttpProcessor解析HTTP协议过程简析
tomcat默认连接器对socket的处理逻辑,主要在HttpProcessor类中的process()方法,其主要工作是解析连接,解析请求行,解析头部,这里它没有对参数进行解析,参数是在需要的时候才会进行解析,主要是不过多的占用CPU时间,使CPU有更多的时间来处理客户的请求,提升并发量。
private void process(Socket socket) {
// 用来记录处理过程是否正确
boolean ok = true;
// 用来标记Response接口中的 finishResponse 方法是否应该被调用
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;// 输出流
// Construct and initialize the objects we will need
try {
// 包装了一个InputStream,用来解析请求行和头部信息
input = new SocketInputStream(socket.getInputStream(),
connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
// 是否持久连接,HTTP/1.1才设置为true,见parseRequest()
keepAlive = true;
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {// request/response基本配置,输入输出流
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
ok = false;
}
// Parse the incoming request
if (ok) {
// 解析服务器地址和端口号
parseConnection(socket);
// 这个主要是用来解析请求行: GET /api/xxx?a=11&b=22.... HTTP/1.1
parseRequest(input, output);
// Header解析
if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
parseHeaders(input);
}
// 如果是HTTP/1.1,回复"HTTP/1.1 100 Continue\r\n\r\n"
if (http11) {
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed()) {
response.setAllowChunking(true);
}
}
}
// Ask our Container to process this request
try {
if (ok) {
// 交给Container来处理请求
connector.getContainer().invoke(request, response);
}
}
// Finish up the handling of the request
if (finishResponse) {
try {
response.finishResponse();
}
try {
request.finishRequest();
}
try {
if (output != null)
output.flush();
} catch (IOException e) {
ok = false;
}
}
// We have to check if the connection closure has been requested
// by the application or the response stream (in case of HTTP/1.0
// and keep-alive).
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
try {
shutdownInput(input);
socket.close();
}
socket = null;
}
整体流程上还是比较清晰的,具体怎么解析的这里就不描述了,在处理完成后,是交给了容器的invoke方法进行处理的,至于容器是如何处理这个请求的,这个只有在分析完容器后才知晓了。