1、HTTP 1.1新特性
1)持久連接:connection: keep-alive
2)塊編碼:使用長連接后嫌褪,發(fā)送方大部分時候無法計算出要發(fā)送的內容長度,也不能等所有資源都準備好了再發(fā)送,HTTP 1.1 使用transfer-encoding header來處理這個問題强窖。transfer-encoding表示有多少以塊形式的字節(jié)流將會被發(fā)送給接收方泪姨,對于每一個塊數據游沿,長度(16進制)+ CR/LF + 數據將會被發(fā)送,整個事物以0\r\n結束肮砾。例如:I'm as helpless as a kitten up a tree.這個數據如果以3個塊發(fā)送的話诀黍,其發(fā)送格式為:
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 使用,當要發(fā)送長內容請求的時候仗处,客戶端在無法保證服務器一定會接收的時候眯勾,可以先發(fā)送這個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()方法替蛉,創(chuàng)建了一個ServerSocket對象
2)調用start()方法贯溅,一是啟動接收客戶端請求的線程,二是初始化HttpProcessor對象池躲查,該對象池為Stack,這里主要看一下HttpProcessor的創(chuàng)建和啟動它浅。
private HttpProcessor newProcessor() {
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
if (processor instanceof Lifecycle) {
try {
/**
* HttpProcessor 本身是一個Runnable和Lifecycle,在這里對其進行了啟動操作镣煮,
* 其實就是啟動了一個線程姐霍,來執(zhí)行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)) {
// 池子里面沒有了炎咖,只要沒有超過設置的最大容量赃泡,就創(chuàng)建一個對象
return (newProcessor());
} else {
if (maxProcessors < 0) {
// 如果最大容量設置為小于0,則直接創(chuàng)建一個對象
return (newProcessor());
} else {
return (null);
}
}
}
}
獲取到HttpProcessor之后乘盼,調用其assign方法升熊。
3、3 HttpProcessor啟動過程
通過上面知道绸栅,HttpProcessor是在HttpConnector的newProcessor()中進行創(chuàng)建和啟動的僚碎,在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 {
/**
* 進入等待狀態(tài),直到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首先進入一個等待狀態(tài),只有被其他線程喚醒了玩焰,才會進行下一步的工作由驹,也就是返回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 {
// 讓當前線程進入等待狀態(tài),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有更多的時間來處理客戶的請求纳猪,提升并發(fā)量。
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方法進行處理的,至于容器是如何處理這個請求的强饮,這個只有在分析完容器后才知曉了由桌。