分类 wifidog源码 下的文章

wifidog自带HTTP 服务器 Lighttpd1.4.20源码分析之array.c(h) -----通用数组(2)

上文讲解到
array.h中还有一个定义:

 typedef struct {
  DATA_UNSET;
  array *value;
 } data_array;

这个定义了一个array类型的数据,也就是说,通用数组中存放的数据可以数通用数组,这样可以形成多维的通用数组。
在array.h中定义了如下的通用数组操作函数:
1、array *array_init(void);
初始化数组,分配空间。
2、array *array_init_array(array * a);
用数组a来初始化一个数组。也就是得到一个a的深拷贝。
3、void array_free(array * a);
释放数组。释放所有空间。
4、void array_reset(array * a);
重置data中的所有数据(调用UNSET类型数据中的reset函数),并将used设为0。相当于清空数组。
5、int array_insert_unique(array * a, data_unset * str);
将str插入到数组中。
6、data_unset *array_pop(array * a);
弹出data中的最后一个元素,返回奇指针,data中的最后一个位置设为NULL。
7、int array_print(array * a, int depth);
打印数组中的内容。depth参数用于在打印多维数组时,实现缩进。
8、a_unset *array_get_unused_element(array * a, data_type_t t);
返回第一个未使用的数据,也就是used位置的数据,这个数据不在数组中,返回这个数据指针后,将data[unsed]设为NULL。可能返回NULL。
9、data_unset *array_get_element(array * a, const char *key);
根据key值,返回数组中key值与之相同的数据
10、data_unset *array_replace(array * a, data_unset * du);
如果数组中有与du的key值相同的数据,则用du替换那个数据,并返回那个数据的指针。如果不存在,则把du插入到数组中。(调用data_insert_unique函数)
11、 int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
这个函数并没用实现,仅仅给出了上面的定义。也许这个是用来比较两个字符串,并且可能会忽略大小写。
12、void array_print_indent(int depth);
根据depth打印空白,实现缩进。
13、size_t array_get_max_key_length(array * a);
返回数组中最长的key的长度。

另外,在array.c中定义了一个辅助函数static intarray_get_index(array *a, const char *key, size_t keylen, int *rndx)。这个函数的作用是通过key值,查找数据,返回其在数组data中的下标位置,并通过参数rndx返回其下标在数组sorted中的位置。
函数的定义如下:

Code
static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) 
{
    /*参数keylen是key的长度*/
    int ndx = -1;
    int i, pos = 0;
    if (key == NULL) return -1;
    /* try to find the string */
    /* 
* sorted数组是个下标数组,存放的是排好序的输入元素的下标,
* 相当于一个排好序的数组。
     * 利用sorted数组进行二分查找。
     * 若找到,返回元素在data数组中的位置,并通过rndx返回
* 其在sorted数组中的位置。
     * 若没有找到,通过rndx返回此元素在sorted中的位置,并返回-1
     */
/* pos中存放的是元素在数组data中的位置 */
/*  
当data的空间不够时,通用数组每次为data增加16个空间,第一次初始化时,
data的长度为16。因此,size始终是16的倍数。
used可以为任何数值,当然要大于等于0,小于size。
而next_power_of_2是大于used最小的2的倍数,如used=5,那么
next_power_of_2就等于8。
这样,used始终大于等于next_power_of_2的1/2。
*/
/*
 在这儿的二分搜索中,next_power_of_2是个很有创意的技巧。
next_power_of_2类似于一个标杆,利用这个标杆进行二分搜索可以减少很多
出错的几率,也使程序更加易懂。效率上当然没有什么损失。下面的程序读者可
自行看看,并不是很难。
 */
    for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) 
{
 int cmp;
 if (pos < 0) {
 pos += i;
 } else if (pos >= (int)a->used) {
 pos -= i;
 } else {
 /* 比较两个元素的key值 */
 cmp = buffer_caseless_compare(key, keylen
, a->data[a->sorted[pos]]->key->ptr
, a->data[a->sorted[pos]]->key->used
);
 if (cmp == 0) {
 /* found */
 ndx = a->sorted[pos];
 break;
 } else if (cmp < 0) {/* 所找数据在前半部分 */
 pos -= i;
 } else { /*  所找数据在后半部分*/
 pos += i;
 }
 }
 if (i == 0) break;
    }
    if (rndx) *rndx = pos;
    return ndx;
}

在上面列出的函数中,还有一个函数要重点讲解一下,也是最复杂的一个函数:int array_insert_unique(array *a, data_unset *str)。这个函数将数据str插入到数组中,当并不是单纯的插入,如果数组中存在key于str相同的数据,则把str的内容拷贝到这个数据中。

Code
int array_insert_unique(array *a, data_unset *str) {
    int ndx = -1;
    int pos = 0;
    size_t j;
    /* generate unique index if neccesary */
    if (str->key->used == 0 || str->is_index_key) {
               buffer_copy_long(str->key, a->unique_ndx++);
                   str->is_index_key = 1;
    }
    /* 在数组中查找与str具有相同key的数据 */
    if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) 
        {
            /* 找到,复制 */
             if (a->data[ndx]->type == str->type) 
              {
                       str->insert_dup(a->data[ndx], str);
              } 
              else 
              {
                       fprintf(stderr, "a\n");
              }
              return 0;
    }
    /* 当数组的长度大于最大值时,不进行插入,并返回-1 */
    if (a->used+1 > INT_MAX) {
                    /* we can't handle more then INT_MAX entries: see array_get_index() */
                   return -1;
    }

    if (a->size == 0) {
               /* 数组为空 */
               /* 初始data的长度为16 */
               a->size   = 16;
               a->data   = malloc(sizeof(*a->data)     * a->size);
               a->sorted = malloc(sizeof(*a->sorted)   * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++) 
                            a->data[j] = NULL;
    } 
        else if (a->size == a->used) 
        {
          /* data已经满了,对data进行扩容,增加16个空间。 */
          /* 这就是为什么size一定是16的倍数 */
               a->size  += 16;
               a->data   = realloc(a->data, sizeof(*a->data) * a->size);
               a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++)
                             a->data[j] = NULL;
    }
    ndx = (int) a->used;
    a->data[a->used++] = str;
    /*
               在上面调用函数array_get_index的时候,
              已将str应该在数组sorted中位置存放在了pos中。 
        */
    if (pos != ndx /* 要插入的位置在中部 */&&((pos < 0) /* 在开始位置插入 */
                            ||buffer_caseless_compare(str->key->ptr
                                          , str->key->used
                                          , a->data[a->sorted[pos]]->key->ptr
                                          , a->data[a->sorted[pos]]->key->used
                            ) > 0)) 
        {
               /* 判断当前pos所对应的元素是否比str小,若是,这pos后移一位 */
               pos++;
    }
    /* 移动sorted数组中后面的数据,腾出位置。 */
    if (pos != ndx) {
               memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted));
    }
    /* insert */
    a->sorted[pos] = ndx;
    /* 如果used==next_power_of_2时,扩展next_power_of_2 */
    if (a->next_power_of_2 == (size_t)ndx) 
              a->next_power_of_2 <<= 1;
    return 0;
}

其他函数都很简单,读者可自己查看。另外,print函数虽然复杂,但对整个程序的意义不大,读者可自行查看。

总结:
  Lighttpd中的通用数组的设置主要是使用的面向对象的思想,使数组具有很好的扩展性和适应性。通用数组中二分查找的实现也是一个特色。还有就是使用sorted数组只对data中的数据的下标排序,这也是一个很有用的技巧。
  以上就是我的一点拙见,还望读者网友对疏漏之处进行批评指正。

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd%E5%88%86%E6%9E%90%E9%80%9A%E7%94%A8%E6%95%B0%E7%BB%84-2.html 整理编辑,转载请注明出处

wifidog自带HTTP 服务器 Lighttpd1.4.20源码分析之array.c(h) -----通用数组(1)

Lighttpd提供了一个通用数组,这个数组与程序的其他部分练习较少,因此可以单独进行分析。
首先要说一下Lighttpd中的定义的一些数据结构。
在array.h中有下面的定义:

Code
typedef enum { 
        TYPE_UNSET,         /* 数据的类型未设置,
                               这几种数据类型使用了面向对象的设计思想,
                               这个类型相当于父类型,继承关系见后面 
                             */
        TYPE_STRING,         /* 字符串类型 */
        TYPE_COUNT,         /* COUNT类型 */
        TYPE_ARRAY,         /* 数组类型 */
        TYPE_INTEGER,     /* 整数类型 */
        TYPE_FASTCGI,     /* FASTCGI类型 */
        TYPE_CONFIG         /* CONFIG类型 */
} data_type_t;

这是一个枚举类型,定义了各个数据类型的标志。从中可以看出程序中所定义使用的数据类型的种类和个数。
Lighttpd在定义数据类型的时候使用了面向对象的思想,因此,程序具有很好的扩展性和适应性。这些类型中,最重要的是UNSET类型,这个类型在所有的数据类型中,起到了父类型的作用。在array.h中,UNSET类型的定义如下:

Code
#define DATA_UNSET \
    data_type_t type; \
    buffer *key; \
    int is_index_key; /* 1 if key is a array index */ \
    struct data_unset *(*copy)(const struct data_unset *src); \
    void (* free)(struct data_unset *p); \
    void (* reset)(struct data_unset *p); \
        int (*insert_dup)(struct data_unset *dst, struct data_unset *src); \
    void (*print)(const struct data_unset *p, int depth)

typedef struct data_unset {
    DATA_UNSET;
} data_unset;

其中,UNSET类型数据的定义中,数据的实际定义部分使用宏DATA_UNSET,这样可以方便其他类型在定义中直接引用DATA_UNSET宏来模拟继承。在宏DATA_UNSET中,定义了下面五个函数指针:

Code
struct data_unset *(*copy)(const struct data_unset *src); 
void (* free)(struct data_unset *p); 
void (* reset)(struct data_unset *p); 
int (*insert_dup)(struct data_unset *dst, struct data_unset *src); 
void (*print)(const struct data_unset *p, int depth)

这五个函数指针相当于UNSET的成员函数,其他类型可以通过对这五个指针赋值来实现成员函数的重写(Overwrite)。每种类型都配有自己特有的初始化函数,形式为:data_XXXXX *data_XXXXX_init(void)。在这些初始化函数中,对上面这五个函数指针进行赋值,当然,赋值的函数都应先定义好。
这几种类型的继承关系图如下:(类图)

wifidog libhttpd源码分析.jpg

下面分析一下STRING类型的初始化函数data_string * data_string_init(void):

Code
data_string *data_string_init(void)
{
    data_string *ds;
    /*
分配内存空间。
这里用的是calloc函数,分配的空间会自动清零。
*/
    ds = calloc(1, sizeof(*ds));
    assert(ds);
    /*
初始化各个数据成员,
这里调用buffer_init函数,主要就是分配内存空间
*/
    ds->key = buffer_init();
    ds->value = buffer_init();
    /*确定成员函数的具体调用函数,对函数指针赋值*/
    ds->copy = data_string_copy;
    ds->free = data_string_free;
    ds->reset = data_string_reset;
    ds->insert_dup = data_string_insert_dup;
    ds->print = data_string_print;
    ds->type = TYPE_STRING;
    return ds;
}

其他类型的init函数,以及其他函数都不难,读者可自行查看代码。
至于各个类型的用处以及各个类型中个成员变量的含义,暂且不用关心,只要知道这七个类型之间的关系即可,除了UNSET类型,其他类型的操作函数的实现都在文件data_XXXXX.c中。这些函数的实现都很简单,不在一一介绍,读者可自己看看。这七个类型构成了通用数组所要处理的类型,其中,在数组的定义和实现中只使用UNSET类型,利用上面的定义,通用数组可以不用关心数组中存储的到底是哪种具体的类型,只需将其按照UNSET类型来处理就可以了。这就实现了通用数组。
下面这个定义是通用数组的核心定义,也就是定义了数组。。。

Code
typedef struct 
{
    /* UNSET类型的指针型数组,存放数组中的元素 */
    data_unset **data;
    /* 存放着排好序的各个元素的下标的数组 */
    size_t *sorted;
    size_t used;    /* data中使用了的长度,也就是数组中元素个数 */

        /* data的大小。data的大小会根据数据的多少变化,会为以后的数据预先分
           配空间 */
    size_t size;    

        size_t unique_ndx;        /*  */
        /* 比used大的最小的2的倍数。也就是离used最近的且比used大的2的倍
              数 ,用于在数组中利用二分法查找元素*/
    size_t next_power_of_2;
        /* data is weakref, don't bother the data */
        /* data就是一个指针,不用关系其所指向的内容 */
    int is_weakref;                
} array;

各个变量的含义见上。
array.h中还有一个定义:

typedef struct {
  DATA_UNSET;
  array *value;
} data_array;

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd%E5%88%86%E6%9E%90%E9%80%9A%E7%94%A8%E6%95%B0%E7%BB%84-1.html 整理编辑,转载请注明出处

wifidog自带http服务器wiLighttpd1.4.20源码分析之状态机(4) 错误处理和连接关闭

Lighttpd所要处理的错误分为两种。一种是http协议规定的错误,如404错误。另一种就是服务器运行过程中的错误,如write错误。

  对于http协议规定的错误,lighttpd返回相应的错误提示文件。其实对于lighttpd而言,这不算错误。在返回错误提示文件后,相当于顺利的完成了一次请求,只是结果和客户端想要的不一样而已。

对于服务器运行中的错误,状态机会直接进入CON_STATE_ERROR状态。大部分的情况下,这种错误都是由客户端提前断开连接所造成的。比如你不停的刷新页面,在你刷新的时候,前一次的连接没有完成,但被浏览器强行断开,这时,服务器就会出现连接错误。对于服务器而言,刷新前后的两个连接是不相干的。因此,服务器在接收后一个连接的时候仍然会继续处理前一次的连接。而这时前一次的连接已经断开,这就产生了连接错误。   

进入CON_STATE_ERROR状态后,如果前面的处理已经得到了结果。也就是http_status不为空。那么调用plugins_call_handle_request_done告诉插件请求处理结束。

  /*
  * even if the connection was drop we still have to write it to the
  * access log
  */
   if (con->http_status)
   {
        plugins_call_handle_request_done(srv, con);
   }

接着,如果使用了ssl,关闭ssl连接。关闭ssl连接的代码很长,但大部分都是错误处理。再朝后,如果连接模式不是DIRECT,调用plugins_call_handle_connection_close告诉插件连接已经关闭。到此,如果连接没有设置keep_alive,那么关闭连接并做一些清理工作之后就直接结束状态机的运行了。
如果设置了keep_alive,此时可能是服务器首先关闭连接的。调用shutdown关闭连接的读和写。如果关闭没有出错,状态机进入CON_STATE_CLOSE状态。

   /*
    * close the connection
    */
   if ((con->keep_alive == 1) && (0 == shutdown(con->fd, SHUT_WR)))
   {
       con->close_timeout_ts = srv->cur_ts;
       connection_set_state(srv, con, CON_STATE_CLOSE);、
       if (srv->srvconf.log_state_handling)
       {
           log_error_write(srv, __FILE__, __LINE__, "sd",
                           "shutdown for fd", con->fd);
       }
   }

上面的代码中有这么一句:con->close_timeout_ts = srv->cur_ts;将close_timeout_ts的值设置为当前时间。这时干吗用的?不着急,继续完后走。
进入CON_STATE_ERROR状态之后,如果是keep_alive,且缓冲区中有数据还没读,那么把这些数据读出来然后直接丢弃。如果没有数据可读,直接设置close_time_ts为0。接着执行下面的代码:

  if (srv->cur_ts - con->close_timeout_ts > 1)
   {
       connection_close(srv, con);
       if (srv->srvconf.log_state_handling)
       {
           log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed for fd", con->fd);
       }
   }

除了输出日志,这段代码就是调用了connection_close。connection_close做最后的清理工作,包括调用close,将对应的connection放回缓冲池中等。前面刚刚说过,在CON_STATE_ERROR中设置了close_time_ts为cur_ts。在出了CON_STATE_ERROR后,进入CON_STATE_CLOSE,这段时间cur_ts是没有改变的。如果前面的代码中测试缓冲区中没有数据可读,此时const_time_ts是等于cur_ts。也就是说状态机还在CON_CLOSE_STATE这个状态,然后就退出了状态机的大while循环。服务器进入了connection_state_mechine最后面的那个switch语句。在这个switch中,连接对应的fd被加入到fdevent系统中并监听读入事件。
这个时候连接都已经关闭了还有什么能读的啊?别急!细心的读者应该注意到了,到这个时候,服务器仅仅是对连接调用了shutdown函数,没有调用close函数。首先说一下shutdown和close的区别。close作用在socket fd上和作用在其他fd上的效果差不多,都是减少引用计数,当计数为0的时候释放资源,关闭连接。shutdown函数则是针对socket fd的专门函数。shutdown可以关闭socket连接的一个方向,也就是可以只关闭写或者只关闭读(socket连接是全双工的)。另外一个区别就是,如果连接是多个进程共享的,那么在一个进程中调用close不影响其他进程使用连接,因为仅仅是引用计数减少1。而如果一个进程调用了shutdown ,那么,不管三七二十一,系统直接咔嚓掉这个连接,其他进程看到的也是关闭的连接。还有一个区别,调用了close的socket fd,read,write函数不能再从这个fd上读取或发送数据。而调用了shutdown的socket fd,如果缓冲区中还有数据可读,由于此时对于进程而言,socket fd没有关闭,read依然可以从这个fd读数据。由于shutdown仅仅是关闭了连接,不会进行资源的释放,要想释放连接占用的资源,还必须调用close函数。
对于keep_alive的连接,关闭是服务器主动发起的。根据TCP协议,主动发起关闭的一方,其连接将进入TIME_WAIT状态,此时连接所占用的资源没有释放。更悲剧的是要在这个状态待很长时间。。。(默认4分钟)对于高并发的服务器,如果服务器主动关闭大量连接,那么服务器的资源很快就会被用光(端口,内存等),新的连接将无法建立。关于这个TIME_WAIT状态,读者可自行google之,网上有很多介绍的文章以及处理办法。虽然这个TIME_WAIT状态看似会给我们带来很多麻烦,但是,如果没有这个状态我们会更麻烦。。。《UNIX Network Programming Volum1》这本神作中有详细的介绍,有兴趣的读者可以看看。既然麻烦不可避免,那么就尽量将麻烦的影响见到最低。连接占用的资源主要就是端口和内存。端口是没办法的,占用就占用了。但是,内存还是可以节省一点的。前面说了,shutdown之后,我们依然可以把缓冲区的数据读出来。那么,我们把这些数据读出来以后,缓冲区所占用的内存就可以释放了。从而降低了内存的使用。
在CON_STATE_CLOSE中,下面的代码把缓冲区的数据读了出来:

  if (ioctl(con->fd, FIONREAD, &b))
  {
      log_error_write(srv, __FILE__, __LINE__, "ss", "ioctl() failed", strerror(errno));
  }
   if (b > 0)
  {
      char buf[1024];
      log_error_write(srv, __FILE__, __LINE__, "sdd", "CLOSE-read()", con->fd, b);

     read(con->fd, buf, sizeof(buf));
  }
else
   {
       /*
        * nothing to read
        */

     con->close_timeout_ts = 0;
  }

如果没有数据可读,那么设置close_timeout_ts会使连接在后面的代码中被关闭。如果有数据可读,读取数据之后,连接依然处在CON_STATE_CLOSE状态中,连接对应的fd被加入到fdevent系统中监听读事件。如果缓冲区中还有数据,那么在connection_handle_fdevent 函数中,也有上面这段代码,再次运行之。如果数据没有读完,服务器将会重复的在connection_handle_fdevent函数中运行上述代码直到数据读完。随着close_timeout_ts被设置为0,在下次joblist的调度中,状态机将会关闭连接,清理所有资源。
至此,连接正式关闭。之所以这么麻烦,就是由于连接是服务器主动关闭的,会有TIME_WAIT状态的问题。这样处理仅仅在内存上有了一些节省。但是具体效果怎样,这要看系统内核是如何处理连接缓冲区的。如果各位读者了解这方面的内容,还请不吝赐教!
另外,shutdown之后内核中的缓冲区到底会怎样笔者并不是很确定。上面的内容只是根据代码的推断。还是那句话,如果给为读者有什么高见,还行不吝赐教啊。

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd-1.html 整理编辑,转载请注明出处

wifidog认证自带http服务器Lighttpd1.4.20源码分析之状态机(3)返回response

好久没顾这个了,最近比较清闲,重新拾掇一下,有始有终。
  回到正题,前一篇介绍完了请求的处理,先面lighttpd将会把处理的结果返回给客户端。状态机进入CON_STATE_RESPONST_START。在这个状态中,服务器主要的工作在函数connection_handle_write_prepare。这个函数不算复杂,主要是根据客户端请求的method来设置response的headers,其实就是设置“Content-Length”的值。下面是函数代码,做了一些删减:

static int connection_handle_write_prepare(server * srv, connection * con)
{
   if (con->mode == DIRECT)
   {
       /*
        * static files
        */
       switch (con->request.http_method)
       {
       case HTTP_METHOD_GET:
       case HTTP_METHOD_POST:
       case HTTP_METHOD_HEAD:
       case HTTP_METHOD_PUT:
       case HTTP_METHOD_MKCOL:
       case HTTP_METHOD_DELETE:
       case HTTP_METHOD_COPY:
       case HTTP_METHOD_MOVE:
       case HTTP_METHOD_PROPFIND:
       case HTTP_METHOD_PROPPATCH:
       case HTTP_METHOD_LOCK:
       case HTTP_METHOD_UNLOCK:
           break;
       case HTTP_METHOD_OPTIONS:
           /*
            * 400 is coming from the request-parser BEFORE uri.path is set
            * 403 is from the response handler when noone else catched it
            * */
           if ((!con->http_status || con->http_status == 200)
               && con->uri.path->used && con->uri.path->ptr[0] != '*')
           {
               response_header_insert(srv, con, CONST_STR_LEN("Allow"),
                  CONST_STR_LEN("OPTIONS, GET, HEAD, POST"));

               con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
               con->parsed_response &= ~HTTP_CONTENT_LENGTH;

               con->http_status = 200;
               con->file_finished = 1;

               chunkqueue_reset(con->write_queue);
           }
           break;
       default:
           switch (con->http_status)
           {
           case 400:            /* bad request */
           case 414:            /* overload request header */
           case 505:            /* unknown protocol */
           case 207:            /* this was webdav */
               break;
           default:
               con->http_status = 501;
               break;
           }
           break;
       }
   }

   if (con->http_status == 0)
   {
       con->http_status = 403;
   }

   switch (con->http_status)
   {
   case 204:                    /* class: header only */
   case 205:
   case 304:
       /*
        * disable chunked encoding again as we have no body
        */
       con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
       con->parsed_response &= ~HTTP_CONTENT_LENGTH;
       chunkqueue_reset(con->write_queue);

       con->file_finished = 1;
       break;
   default:                    /* class: header + body */
       if (con->mode != DIRECT)
           break;

       /*
        * only custom body for 4xx and 5xx
        */
       if (con->http_status < 400 || con->http_status >= 600)
           break;

       con->file_finished = 0;
       buffer_reset(con->physical.path);

       /*
        * try to send static errorfile
        */
       if (!buffer_is_empty(con->conf.errorfile_prefix))
       {
        //设置对应的错误提示文件
       }

       if (!con->file_finished)
       {
           //没有对应的错误提示文件,设置默认错误提示。
       }
       break;
   }

   if (con->file_finished)
   {
       /*
        * we have all the content and chunked encoding is not used, set a
        * content-length
        */
       if ((!(con->parsed_response & HTTP_CONTENT_LENGTH)) &&
           (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0)
       {
           off_t qlen = chunkqueue_length(con->write_queue);
           /**
            * The Content-Length header only can be sent if we have content:
            * - HEAD doesn't have a content-body (but have a content-length)
            * - 1xx, 204 and 304 don't have a content-body (RFC 2616 Section 4.3)
            *
            * Otherwise generate a Content-Length header as chunked encoding is not
            * available
            */
           if ((con->http_status >= 100 && con->http_status < 200) ||
               con->http_status == 204 || con->http_status == 304)
           {
               data_string *ds;
               /*
                * no Content-Body, no Content-Length
                */
               if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Content-Length")))
               {
                   buffer_reset(ds->value);    /* Headers with empty values
                                                * are ignored for output */
               }
           }
           else if (qlen > 0 || con->request.http_method != HTTP_METHOD_HEAD)
           {
               /*
                * qlen = 0 is important for Redirects (301, ...) as they MAY
                * have a content. Browsers are waiting for a Content otherwise
                */
               buffer_copy_off_t(srv->tmp_buf, qlen);

               response_header_overwrite(srv, con, CONST_STR_LEN("Content-Length"), CONST_BUF_LEN(srv->tmp_buf));
           }
       }
   }
   else
   {
       /**
        * the file isn't finished yet, but we have all headers
        *
        * to get keep-alive we either need:
        * - Content-Length: ... (HTTP/1.0 and HTTP/1.0) or
        * - Transfer-Encoding: chunked (HTTP/1.1)
        */

       if (((con->parsed_response & HTTP_CONTENT_LENGTH) == 0) &&
           ((con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) == 0))
       {
           con->keep_alive = 0;
       }

       /**
        * if the backend sent a Connection: close, follow the wish
        *
        * NOTE: if the backend sent Connection: Keep-Alive, but no Content-Length, we
        * will close the connection. That's fine. We can always decide the close
        * the connection
        *
        * FIXME: to be nice we should remove the Connection: ...
        */
       if (con->parsed_response & HTTP_CONNECTION)
       {
           /*
            * a subrequest disable keep-alive although the client wanted it
            */
           if (con->keep_alive && !con->response.keep_alive)
           {
               con->keep_alive = 0;
           }
       }
   }

   if (con->request.http_method == HTTP_METHOD_HEAD)
   {
       /**
        * a HEAD request has the same as a GET
        * without the content
        */
       con->file_finished = 1;

       chunkqueue_reset(con->write_queue);
       con->response.transfer_encoding &= ~HTTP_TRANSFER_ENCODING_CHUNKED;
   }

   http_response_write_header(srv, con);

   return 0;
}

首先,该函数判断连接的模式(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理解。
下面一片文章将会分析一些错误处理过程。

本文章由 http://www.wifidog.pro/2015/04/10/wifidog%E8%AE%A4%E8%AF%81lighttpd-1.html 整理编辑,转载请注明出处