——以请求首页为例分析lighttpd处理HTTP请求的流程
http://bbs.chinaunix.net/thread-1640195-1-1.html
lighttpd代码版本号为1.4.22,lighttpd只配置了index.html其它的什么都没有。发送请求http://192.168.1.14:80请求到lighttpd的时候,lighttpd会返回首页内容。
具体配置如下,一共有四个文件可供选择,
在配置文件lighttpd.conf中有一项
index-file.names += (
"index.xhtml", "index.html", "index.htm", "default.htm", "index.php"
)
同时在文件lighttpd.conf中配置index.html的路径如下:
server.document-root = "/home/lighttpd/www"
启动时如下:./lighttpd -D -f /etc/lighttpd/lighttpd.conf
另外还需要提到的是lighttpd加载的模块, lighttpd在启动加载模块的时候会加载三个默认模块:mod_indexfile,mod_dirlisting,mod_staticfile。
本文直接从connection_state_machine讲起。http_request_parse解析HTTP请求,然后将状态置为CON_STATE_HANDLE_REQUEST。http_request_parse函数位于request.c当中。这个函数比较长,有接近一千行的规模,这个函数由两个循环构成:
第一循环的作用是获取HTTP协议版本号,HTTP请求类型,请求的URI,并且会检查HTTP版本是否合法,URI是否含有非法字符。处理的流程是,寻找第一行中的空格,如果找到第一个空格,那么空格前的字符串为请求类型,记下当前位置。寻找第一行中的第二个空格,如果找到,那么空格前从上一个位置起的字符串为URI,这代表要请求的资源。如果在第一行中找到第三个空格,这代表出错了。
1. 496 case ' ':
2. 497 switch(request_line_stage) {
3. 498 case 0:
4. 499 /* GET|POST|... */
5. 500 method = con->parse_request->ptr + first;
6. 501 first = i + 1;
7. 502 break;
8. 503 case 1:
9. 504 /* /foobar/... */
10. 505 uri = con->parse_request->ptr + first;
11. 506 first = i + 1;
12. 507 break;
13. 508 default:
14. 509 /* ERROR, one space to much */
15. 510 con->http_status = 400;
16. 511 con->response.keep_alive = 0;
17. 512 con->keep_alive = 0;
18. 513
19. 514 if (srv->srvconf.log_request_header_on_error) {
20. 515 log_error_write(srv, __FILE__, __LINE__, "s", "overlong request line -> 400");
21. 516 log_error_write(srv, __FILE__, __LINE__, "Sb",
22. 517 "request-header:\n",
23. 518 con->request.request);
24. 519 }
25. 520 return 0;
26. 521 }
27. 522
28. 523 request_line_stage++; //注意这一行
29. 524 break;
下一步是找到表示行结束的\r\n。
1. switch(*cur) {
2. 324 case '\r':
3. 325 if (con->parse_request->ptr[i+1] == '\n') {
4. 330 /* \r\n -> \0\0 */
5. 331 con->parse_request->ptr[i] = '\0';
6. 332 con->parse_request->ptr[i+1] = '\0';
7. ....
8. 349
9. 350 proto = con->parse_request->ptr + first;
10. 351
11. 352 *(uri - 1) = '\0'; // 确定URI
12. 353 *(proto - 1) = '\0'; //确定协议
13. ...
14. 371 con->request.http_method = r; //确定请求类型,为GET
15. ...
16. 487 buffer_copy_string_buffer(con->request.orig_uri, con->request.uri);
17. 488
18. 489 con->http_status = 0;
19. 490
20. 491 i++;
21. 492 line++; // 注意这一行,如果执行到这里,下轮for循环就退出
22. 493 first = i+1;
23. 494 }
24. 495 break;
第二个循环的作用是取HTTP头,同时还检查是否有非法字符。处理的流程是,先寻找:号,这个是分隔符,用来分隔头名称和值。代码中分别使用key和value来表示,另外使用is_key这个变量来表示是否已经找到了key。代码分为if(is_key) ... else 两块。is_key的初始值为1,所以代码一开始进入的是if(is_key)的部分,一直到下面的代码。
1. 558 case ':':
2. 559 is_key = 0;
3. 560
4. 561 value = cur + 1;
5. 562
6. 563 if (is_ws_after_key == 0) {
7. 564 key_len = i - first;
8. 565 }
9. 566 is_ws_after_key = 0;
10. 567
11. 568 break;
复制代码
如果找到了:号,下一次循环进入的就是else部分了。这里说的只是正常情况,如果出错了或者有异常的话,就退出了。再看看else部分里的代码。
1. 760 int s_len;
2. 761 key = con->parse_request->ptr + first; //取得key
3. 762
4. 763 s_len = cur - value;
5. 764
6. 765 /* strip trailing white-spaces */
7. 766 for (; s_len > 0 &&
8. 767 (value[s_len - 1] == ' ' ||
9. 768 value[s_len - 1] == '\t'); s_len--);
10. 769
11. 770 value[s_len] = '\0'; //取得value
12. 771
复制代码
如果一行结束了,就把key和value存起来。
1. 772 if (s_len > 0) {
2. 773 int cmp = 0;
3. 774 if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
4. 775 ds = data_string_init();
5. 776 }
6. 777 buffer_copy_string_len(ds->key, key, key_len);
7. 778 buffer_copy_string_len(ds->value, value, s_len);
8.
9. 把取到的HTTP头加入队列中。然后再对几个头做一些处理,"Connection", "Content-Length","Content-Type","Expect","Host","If-Modified-Since"等等……最后做一下初始化,进入下一轮循环。
10. [code]
11. 996 i++;
12. 997 first = i+1;
13. 998 is_key = 1; //注意这一行,恢复原始值
14. 999 value = 0;
复制代码
循环结束以后,所有的HTTP头都读出来了。最后还要做一些后期工作,这里不详细讲了。到这里为止,这个复杂的函数就讲完了。接着上面的讲,现在设置状态为CON_STATE_HANDLE_REQUEST,要开始处理请求了。
2.2 CON_STATE_HANDLE_REQUEST,处理HTTP请求
处理HTTP请求只调用一个函数http_response_prepare,当然了,这个函数也是不短,有500行的样子,复杂度没有上面的解析函数高,但是与其它源文件的关联度却要大得多。
1. 1427 switch (r = http_response_prepare(srv, con)) {
如果http_response_prepare函数调用成功了,就把HTTP状态设置为200,即OK状态。然后把状态机的状态改为CON_STATE_RESPONSE_START,开始回应。
1. 1465 if (con->http_status == 0) con->http_status = 200;
2. 1466
3. 1467 /* we have something to send, go on */
4. 1468 connection_set_state(srv, con, CON_STATE_RESPONSE_START);
下面开始讲解http_response_prepare这个函数,这个函数位于response.c文件当中。这个函数前面有许多准备工作还有许多检查,我没有认真跟进去看过,这个函数还调了许多插件函数,我没有一一跟进去了,所以很多内容都略过了。我是从这个函数看起的。
1. 645 switch(r = plugins_call_handle_subrequest_start(srv, con)) {
plugins_call_handle_subrequest_start这个函数调用插件中的handle_subrequest_start函数指针,真正做了事情的是前面提到的mod_indexfile模块还有mod_staticfile模块,mod_indexfile模块中在mod_indexfile_plugin_init函数中将handle_subrequest_start 函数指针指向了
mod_indexfile_subrequest函数。
1. 214 p->handle_subrequest_start = mod_indexfile_subrequest;
那这个函数到底做了什么呢?它把配置文件中保存的四个首页名字取出来,一个个加到doc root后面,然后看看这个文件是否存在,如果存在就完成任务。
1. 155 /* indexfile */
2. 156 for (k = 0; k < p->conf.indexfiles->used; k++) {
3. 157 data_string *ds = (data_string *)p->conf.indexfiles->data[k]; //取出首页名
4. 158
5. 159 if (ds->value && ds->value->ptr[0] == '/') {
6. 160 /* if the index-file starts with a prefix as use this file as
7. 161 * index-generator */
8. 162 buffer_copy_string_buffer(p->tmp_buf, con->physical.doc_root);
9. 163 } else {
10. 164 buffer_copy_string_buffer(p->tmp_buf, con->physical.path);
11. 165 }
12. 166 buffer_append_string_buffer(p->tmp_buf, ds->value); //附加
13. 167
14. 168 if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) { //检
下面将这个完整的文件名保存到phsical.path当中。
1. 194 /* rewrite uri.path to the real path (/ -> /index.php) */
2. 195 buffer_append_string_buffer(con->uri.path, ds->value);
3. 196 buffer_copy_string_buffer(con->physical.path, p->tmp_buf);
4.
5. mod_staticfile模块中的mod_staticfile_plugin_init函数中将handle_subrequest_start 指针指向mod_staticfile_subrequest函数。
6. [code]
7. 540 p->handle_subrequest_start = mod_staticfile_subrequest;
这个函数又做了什么呢?它的任务是填好回应包的HTTP头,并且把index.html添加到回应队列当中去。这个函数还涉及到cache,cache部分我一无所知,没有看过,就无能为力了。catche完以后,开始填HTTP头,包括以下几个部分:"Content-Type","Accept-Ranges","ETag","Last-Modified",最后将文件附加到回应队列里去。
1. 522 /* we add it here for all requests
2. 523 * the HEAD request will drop it afterwards again
3. 524 */
4. 525 http_chunk_append_file(srv, con, con->physical.path, 0, sce->st.st_size);
在http_chunk_append_file函数中,会调用
1. 65 chunkqueue_append_file(cq, fn, offset, len);
而chunkqueue_append_file这个函数中会附加一个类型为FILE_CHUNK;的chunk结构到回应队列里。
1. 168 c = chunkqueue_get_unused_chunk(cq);
2. 169
3. 170 c->type = FILE_CHUNK; //注意这里
4. 171
5. 172 buffer_copy_string_buffer(c->file.name, fn);
6. 173 c->file.start = offset;
7. 174 c->file.length = len;
8. 175 c->offset = 0;
9. 176
10. 177 chunkqueue_append_chunk(cq, c);
这里为什么要对FILE_CHUNK特别说明一下,因为在发送数据的时候,如果是FILE_CHUNK这个类型会调一个单独的函数,这个等后面再详细讲了。
在调完 plugins_call_handle_subrequest以后,http_response_prepare函数就成功返回了。接着上面的讲,这个时候该改状态机的状态了。状态机的状态被改为CON_STATE_RESPONSE_START。
准备发送的HTTP头还有index.html文件内容,是否可以发送了呢?答案是否定的。还需要做一些其它工作才能准备好。
1. 1496 /*
2. 1497 * the decision is done
3. 1498 * - create the HTTP-Response-Header
4. 1499 *
5. 1500 */
6. 1501
7. 1502 if (srv->srvconf.log_state_handling) {
8. 1503 log_error_write(srv, __FILE__, __LINE__, "sds",
9. 1504 "state for fd", con->fd, connection_get_state(con->state));
10. 1505 }
11. 1506
12. 1507 if (-1 == connection_handle_write_prepare(srv, con)) {
13. 1508 connection_set_state(srv, con, CON_STATE_ERROR);
14. 1509
15. 1510 break;
16. 1511 }
17. 1512
18. 1513 connection_set_state(srv, con, CON_STATE_WRITE);
connection_handle_write_prepare函数主要是写一下"Content-Length"这个HTTP头,然后调用一个函数
1. 590 http_response_write_header(srv, con);
其它的工作都在http_response_write_header函数当中完成,http_response_write_header函数位于response.c当中。这个函数写的是前面一些东西,其实就是HTTP/1.1 200 OK这一行。然后再检查几个HTTP头,如果没有的话就添加一份。
1. 35 b = chunkqueue_get_prepend_buffer(con->write_queue);
2. 36
3. 37 if (con->request.http_version == HTTP_VERSION_1_1) {
4. 38 buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.1 ")); // HTTP/1.1
5. 39 } else {
6. 40 buffer_copy_string_len(b, CONST_STR_LEN("HTTP/1.0 "));
7. 41 }
8. 42 buffer_append_long(b, con->http_status); // 200
9. 43 buffer_append_string_len(b, CONST_STR_LEN(" "));
10. 44 buffer_append_string(b, get_http_status_name(con->http_status)); // OK
11.
下面开始添加前面遗漏的HTTP头,Date还有Server。
1. 86 if (!have_date) {
2. 87 /* HTTP/1.1 requires a Date: header */
3. 88 buffer_append_string_len(b, CONST_STR_LEN("\r\nDate: "));
4. 89
5. 90 /* cache the generated timestamp */
6. 91 if (srv->cur_ts != srv->last_generated_date_ts) {
7. 92 buffer_prepare_copy(srv->ts_date_str, 255);
8. 93
9. 94 strftime(srv->ts_date_str->ptr, srv->ts_date_str->size - 1,
10. 95 "%a, %d %b %Y %H:%M:%S GMT", gmtime(&(srv->cur_ts)));
11. 96
12. 97 srv->ts_date_str->used = strlen(srv->ts_date_str->ptr) + 1;
13. 98
14. 99 srv->last_generated_date_ts = srv->cur_ts;
15. 100 }
16. 101
17.
18. 105 if (!have_server) {
19. 106 if (buffer_is_empty(con->conf.server_tag)) {
20. 107 buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: " PACKAGE_NAME "/" PACKAGE_VERSION));
21. 108 } else if (con->conf.server_tag->used > 1) {
22. 109 buffer_append_string_len(b, CONST_STR_LEN("\r\nServer: "));
23. 110 buffer_append_string_encoded(b, CONST_BUF_LEN(con->conf.server_tag), ENCODING_HTTP_HEADER);
24. 111 }
25. 112 }
26. 113
27. 114 buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n"));
28. 115
好了,到此为止,所有的数据都已经准备齐全了,可以发送了。终于可以把状态设成CON_STATE_WRITE了。
2.4 CON_STATE_WRITE
CON_STATE_WRITE,这个地方只有一点需要讲一下,那就是发送文件的时候lighttpd做了一个单独处理。
connection_handle_write->network_write_chunkqueue-> srv->network_backend_write,好了, srv->network_backend_write这个地方就是我要讲一下的。这又是一个绕来绕去的过程,确实比较痛苦……首先要从network.c说起……network.c中有一个函数名为network_init,这个函数是在server.c当中被main函数调用的。在network_init函数当中,有一个名为network_backends的数组
1. 474 struct nb_map {
2. 475 network_backend_t nb;
3. 476 const char *name;
4. 477 } network_backends[] = {
5. 478 /* lowest id wins */
6. 479 #if defined USE_LINUX_SENDFILE
7. 480 { NETWORK_BACKEND_LINUX_SENDFILE, "linux-sendfile" },
8. 481 #endif
9. 482 #if defined USE_FREEBSD_SENDFILE
10. 483 { NETWORK_BACKEND_FREEBSD_SENDFILE, "freebsd-sendfile" },
11. 484 #endif
12. 485 #if defined USE_SOLARIS_SENDFILEV
13. 486 { NETWORK_BACKEND_SOLARIS_SENDFILEV, "solaris-sendfilev" },
14. 487 #endif
15. 488 #if defined USE_WRITEV
16. 489 { NETWORK_BACKEND_WRITEV, "writev" },
17. 490 #endif
18. 491 { NETWORK_BACKEND_WRITE, "write" },
19. 492 { NETWORK_BACKEND_UNSET, NULL }
20. 493 };
因为我用的是debian,所以是480行那一项了。这一项位于数组0的位置。再往下看
1. 510 /* get a usefull default */
2. 511 backend = network_backends[0].nb;
backend赋值了,赋成了NETWORK_BACKEND_LINUX_SENDFILE。这还没完,还有下文,请不要着急-_-!!!
正主终于上场了……
1. 533 switch(backend) {
2. 534 case NETWORK_BACKEND_WRITE:
3. 535 srv->network_backend_write = network_write_chunkqueue_write;
4. 536 break;
5. 537 #ifdef USE_WRITEV
6. 538 case NETWORK_BACKEND_WRITEV:
7. 539 srv->network_backend_write = network_write_chunkqueue_writev;
8. 540 break;
9. 541 #endif
10. 542 #ifdef USE_LINUX_SENDFILE
11. 543 case NETWORK_BACKEND_LINUX_SENDFILE:
12. 544 srv->network_backend_write = network_write_chunkqueue_linuxsendfile; //请注意
13. 545 break;
14. 546 #endif
15. 547 #ifdef USE_FREEBSD_SENDFILE
16. 548 case NETWORK_BACKEND_FREEBSD_SENDFILE:
17. 549 srv->network_backend_write = network_write_chunkqueue_freebsdsendfile;
18. 550 break;
19. 551 #endif
20. 552 #ifdef USE_SOLARIS_SENDFILEV
21. 553 case NETWORK_BACKEND_SOLARIS_SENDFILEV:
22. 554 srv->network_backend_write = network_write_chunkqueue_solarissendfilev;
23. 555 break;
24. 556 #endif
25. 557 default:
26. 558 return -1;
27. 559 }
也就是说前面看到的那个network_backend_write函数其实就是network_write_chunkqueue_linuxsendfile; 这个函数位于linux_network_sendfile.c文件里面。在发送内容里,它会判断chunk类型,因为这里只针对index.html这个FILE_CHUNK类型,所以贴一下FILE_CHUNK部分的代码。
1. 129 case FILE_CHUNK: {
2. 130 ssize_t r;
3. 131 off_t offset;
4. 132 size_t toSend;
5. 133 stat_cache_entry *sce = NULL;
6. 134
7. 135 offset = c->file.start + c->offset;
8. 136 /* limit the toSend to 2^31-1 bytes in a chunk */
9. 137 toSend = c->file.length - c->offset > ((1 << 30) - 1) ?
10. 138 ((1 << 30) - 1) : c->file.length - c->offset;
11. 139
12. 140 /* open file if not already opened */
13. 141 if (-1 == c->file.fd) {
14. 142 if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { //请注意
15. 143 log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));
16. 144
17. 145 return -1;
18. 146 }
19. 147 #ifdef FD_CLOEXEC
20. 148 fcntl(c->file.fd, F_SETFD, FD_CLOEXEC);
21. 149 #endif
22. 150 #ifdef HAVE_POSIX_FADVISE
23. 151 /* tell the kernel that we want to stream the file */
24. 152 if (-1 == posix_fadvise(c->file.fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
25. 153 if (ENOSYS != errno) {
26. 154 log_error_write(srv, __FILE__, __LINE__, "ssd",
27. 155 "posix_fadvise failed:", strerror(errno), c->file.fd);
28. 156 }
29. 157 }
30. 158 #endif
31. 159 }
32. 160
33. 161 if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) { //请注意
由此可见,最终调的是sendfile来发送文件内容。