Lighttpd中发送index.html的流程

Lighttpd中发送index.html的流程

——以请求首页为例分析lighttpd处理HTTP请求的流程

http://bbs.chinaunix.net/thread-1640195-1-1.html

一.概述

lighttpd代码版本号为1.4.22lighttpd只配置了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

二.代码解析

2.1 解析HTTP请求

本文直接从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。

 

 

2.3CON_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来发送文件内容。

你可能感兴趣的:(Lighttpd中发送index.html的流程)