好久沒顧這個了,最近比較清閒,重新拾掇一下,有始有終。
回到正題,前一篇介紹完了請求的處理,先面lighttpd將會把處理的結果返回給客戶端。狀態機進入CON_STATE_RESPONST_START。在這個狀態中,服務器主要的工作在函數connection_handle_write_prepare。這個函數不算復雜,主要是根據客戶端請求的method來設置response的headers,其實就是設置“Content-Length”的值。下面是函數代碼,做了一些刪減:
1 static int connection_handle_write_prepare(server * srv, connection * con)
2 {
3 if (con->mode == DIRECT)
4 {
5 /*
6 * static files
7 */
8 switch (con->request.http_method)
9 {
10 case HTTP_METHOD_GET:
11 case HTTP_METHOD_POST:
12 case HTTP_METHOD_HEAD:
13 case HTTP_METHOD_PUT:
14 case HTTP_METHOD_MKCOL:
15 case HTTP_METHOD_DELETE:
16 case HTTP_METHOD_COPY:
17 case HTTP_METHOD_MOVE:
18 case HTTP_METHOD_PROPFIND:
19 case HTTP_METHOD_PROPPATCH:
20 case HTTP_METHOD_LOCK:
21 case HTTP_METHOD_UNLOCK:
22 break;
23 case HTTP_METHOD_OPTIONS:
24 /*
25 * 400 is coming from the request-parser BEFORE uri.path is set
26 * 403 is from the response handler when noone else catched it
27 * */
28 if ((!con->http_status || con->http_status == 200)
29 && con->uri.path->used && con->uri.path->ptr[0] != '*')
30 {
31 response_header_insert(srv, con, CONST_STR_LEN("Allow"),
32 CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));
33
34 con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
35 con->parsed_response &= ~HTTP_CONTENT_LENGTH;
36
37 con->http_status = 200;
38 con->file_finished = 1;
39
40 chunkqueue_reset(con->write_queue);
41 }
42 break;
43 default:
44 switch (con->http_status)
45 {
46 case 400: /* bad request */
47 case 414: /* overload request header */
48 case 505: /* unknown protocol */
49 case 207: /* this was webdav */
50 break;
51 default:
52 con->http_status = 501;
53 break;
54 }
55 break;
56 }
57 }
58
59 if (con->http_status == 0)
60 {
61 con->http_status = 403;
62 }
63
64 switch (con->http_status)
65 {
66 case 204: /* class: header only */
67 case 205:
68 case 304:
69 /*
70 * disable chunked encoding again as we have no body
71 */
72 con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
73 con->parsed_response &= ~HTTP_CONTENT_LENGTH;
74 chunkqueue_reset(con->write_queue);
75
76 con->file_finished = 1;
77 break;
78 default: /* class: header + body */
79 if (con->mode != DIRECT)
80 break;
81
82 /*
83 * only custom body for 4xx and 5xx
84 */
85 if (con->http_status < 400 || con->http_status >= 600)
86 break;
87
88 con->file_finished = 0;
89 buffer_reset(con->physical.path);
90
91 /*
92 * try to send static errorfile
93 */
94 if (!buffer_is_empty(con->conf.errorfile_prefix))
95 {
96 //設置對應的錯誤提示文件
97 }
98
99 if (!con->file_finished)
100 {
101 //沒有對應的錯誤提示文件,設置默認錯誤提示。
102 }
103 break;
104 }
105
106 if (con->file_finished)
107 {
108 /*
109 * we have all the content and chunked encoding is not used, set a
110 * content-length
111 */
112 if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) &&
113 (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)
114 {
115 off_t qlen = chunkqueue_length(con->write_queue);
116 /**
117 * The Content-Length header only can be sent if we have content:
118 * - HEAD doesn't have a content-body (but have a content-length)
119 * - 1xx, 204 and 304 don't have a content-body (RFC 2616 Section 4.3)
120 *
121 * Otherwise generate a Content-Length header as chunked encoding is not
122 * available
123 */
124 if ((con->http_status >= 100 && con->http_status < 200) ||
125 con->http_status == 204 || con->http_status == 304)
126 {
127 data_string *ds;
128 /*
129 * no Content-Body, no Content-Length
130 */
131 if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Content-Length")))
132 {
133 buffer_reset(ds->value); /* Headers with empty values
134 * are ignored for output */
135 }
136 }
137 else if (qlen > 0 || con->request.http_method != HTTP_METHOD_HEAD)
138 {
139 /*
140 * qlen = 0 is important for Redirects (301, ...) as they MAY
141 * have a content. Browsers are waiting for a Content otherwise
142 */
143 buffer_copy_off_t(srv->tmp_buf, qlen);
144
145 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf));
146 }
147 }
148 }
149 else
150 {
151 /**
152 * the file isn't finished yet, but we have all headers
153 *
154 * to get keep-alive we either need:
155 * - Content-Length: ... (HTTP/1.0 and HTTP/1.0) or
156 * - Transfer-Encoding: chunked (HTTP/1.1)
157 */
158
159 if (((con->parsed_response & HTTP_CONTENT_LENGTH) == 0) &&
160 ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0))
161 {
162 con->keep_alive = 0;
163 }
164
165 /**
166 * if the backend sent a Connection: close, follow the wish
167 *
168 * NOTE: if the backend sent Connection: Keep-Alive, but no Content-Length, we
169 * will close the connection. That's fine. We can always decide the close
170 * the connection
171 *
172 * FIXME: to be nice we should remove the Connection: ...
173 */
174 if (con->parsed_response & HTTP_CONNECTION)
175 {
176 /*
177 * a subrequest disable keep-alive although the client wanted it
178 */
179 if (con->keep_alive && !con->response.keep_alive)
180 {
181 con->keep_alive = 0;
182 }
183 }
184 }
185
186 if (con->request.http_method == HTTP_METHOD_HEAD)
187 {
188 /**
189 * a HEAD request has the same as a GET
190 * without the content
191 */
192 con->file_finished = 1;
193
194 chunkqueue_reset(con->write_queue);
195 con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
196 }
197
198 http_response_write_header(srv, con);
199
200 return 0;
201 }
首先,該函數判斷連接的模式(mode)是否是DIRECT,如果是,說明連接沒有經過插件處理,是由服務器自身處理的。這裡判斷連接的請求method,如果是OPTION,則設置Allow的值。同時清空write_queue,因為沒有數據需要返回。 接著,在下面這個switch語句中,比較http_status的值,如果為204,205,304,說明服務器不需要給客戶端返回文件,僅僅返回 response中headers及其之前的部分。這裡和前面處理OPTION方法都設置con->file_finished為1。 file_finished用來標記客戶端請求的靜態文件是否已經發送完了(這個file_finished的含義比較模糊,目前還沒搞清楚是表示文件發送完畢,還是要發送的文件設置完畢可以發送。。。也有可能是個bug。。。如果各位讀者有什麼高見,還望不吝賜教!)。這兩處都不需要給客戶端發送文件,因此將其設置為1,發送程序將直接跳過文件的發送。switch的default分支處理4xx錯誤,返回相應的錯誤提示文件。
出了switch語句之後,接著是一個if判斷file_finished的值。如果值為1,說明不需要給客戶端返回文件數據。對於1xx,204和 304狀態,將Content-Length設置為空值。如果method是HEAD,那麼服務器可能需要返回一些數據,這時候要設置對應的 Content-Length。如果file_finished的值為0,那麼要設置一下keep_alive的值。在 connection_handle_write_prepare函數的最後,調用http_response_write_header將 headers寫入write_queue,等待返回給客戶端。如果一切順利,那麼狀態機進入CON_STATE_WRITE。
由於數據可能不會一次寫完,尤其是發送大文件的時候。因此,在CON_STATE_WRITE狀態中首先判斷write_queue時候為空,也就是有沒有數據還沒有發送。同時判斷連接是否可寫。如果有數據且可寫,那麼調用connection_handle_write發送數據。如果沒有數據可寫或者連接不可寫,那麼會退出switch(con->state)這個語句。同時,由於狀態機狀態沒有發生改變,switch後面的if語句使得服務器退出了大while循環,進入循環後面的小switch(con->state)語句。這個switch語句在前面已經介紹過。在這裡,進入 CON_STATE_WRITE分支,如果有數據可寫且連接可寫且沒有達到流量限制,那麼在fdevent中注冊這個連接,否則,刪除這個連接。
下面我們進入connection_handle_write函數,看看有數據可寫且連接可寫的情況。connection_handle_write函數會首先在switch語句中調用network_write_chunkqueue函數。顧名思義,這個函數就是將write_queue中的數據寫回給客戶端的。函數network_write_chunkqueue首先判斷當前連接的流量是否超過了限制,如果是,則不發送任何數據,直接將連接加到作業列表(joblist)中,讓其他連接發送數據。如果沒有超限,那麼首先設置TCP_CORK選項。這個選項可以將多個write調用的數據一起發送,提高發送效率。有關TCP_CORK的內容讀者可自行google之。接下來就是調用srv->network_backend_wirte()函數進行真正的寫數據了。這個函數的真正定義有多個,在network_*.c文件中。服務器在network.c的network_init函數中會根據當前的運行環境設置不同的值。為了提高服務器的效率,不同的OS會提供一些不同的方法,提高發送文件的速度。通過傳統的先read在write的方法,需要4次數據拷貝(從磁盤到內核緩沖區,從內核緩沖區到用戶緩沖區,從用戶緩沖區到網絡接口的內核緩沖區,最後,從網絡接口的內核緩沖區到網絡設備緩沖區。),OS會提供一些特定的方法來減少拷貝的次數,具體讀者可以google“直接IO”,有不少介紹這個的文章。為了充分利用這些方法來提高服務器的性能,lighttpd在不同的OS上都會去使用這些特定的接口,因此就需要多個network_backend_wirte函數的實現。這些實現基本上大同小異,因此這裡只介紹network_write.c中的實現。函數的主體是個大循環,遍歷所有的chunk。如果chunk的類型是 MEM_CHUNK,那麼這個chunk中的數據是在內存中的,直接調用write或者windows下的send函數發送數據。如果是 FILE_CHUNK類型,說明這個chunk表示的是一個文件,那麼如果運行環境有mmap函數,就使用mmap映射文件並發送,否則就read在 write。如果這個chunk發送完了,那麼繼續發送下一個chunk。如果沒有發送完(chunk_finished=0),那麼退出循環,接著也就退出了這個函數。服務器這時返回到network_write_chunkqueue中,下面做一些統計工作,再一次檢查該連接的流量是否超限。最後服務器返回到connection_handle_write中。如果network_write_chunkqueue返回-1,表示服務器出錯。狀態機進入CON_STATE_ERROR。如果返回-2,則客戶端關閉連接,狀態機也進入CON_STATE_ERROR。返回0表示發送完畢,進入下一個狀態。返回1說明數據沒有發送完,標記is_wirtable為0。
從connection_handler_write函數返回後,如果數據沒有發送完畢,那麼狀態機還在
CON_STATE_WRITE狀態,接著連接被加到fdevent系統中,等待下一次數據發送。重復上述過程知道發送完畢或出錯。
如果數據發送完畢,狀態機進入CON_STATE_RESPONSE_END狀態。在這個狀態中,服務器首先調用 plugins_call_handle_request_done通知所有插件連接處理完畢。然後判斷是否保持連接,如果保持,將狀態機設置為 CON_STATE_REQUEST_START。如果不保持,那麼先調用plugins_call_handle_connection_close通知所有插件連接關閉。然後關閉連接。最後,重置con,清除前一次請求的數據。
至此,請求處理結束。總的來說,返回response的過程還算直接,沒有多少難懂的地方。比較不好懂的地方都是關於http協議中一些情況的細節處理,讀者可以參照rfc理解。
下面一片文章將會分析一些錯誤處理過程。