佐须之男 发布的文章

wifidog认证源码分析lighttpd1.4.20源码分析 -----工作模型

lighttpd的工作模型很简单──一个主进程加多个工作进程的多进程模型,也就是所谓的watcher-worker模型。
  整个程序的入口(main函数)在server.c文件中。在main函数的开始部分必然是处理参数和各种繁杂的初始化工作。其中有两个地方要重点看一起。第一个是下面的语句:

if (test_config) //没有进行任何测试。。。
{
     printf("Syntax OK\n");
}

这个If语句是为了判断配置文件的语法是否合法。但是,明显,没有进行任何的测试,而仅仅是输出了一句话。
  第二个是函数daemonize()。这个函数的作用是使程序进入daemon。函数的详细内容如下:

static void daemonize(void)
{
        /*
         * 忽略和终端读写有关的信号
         */
#ifdef SIGTTOU
        signal(SIGTTOU, SIG_IGN);
#endif    
#ifdef SIGTTIN
        signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
        signal(SIGTSTP, SIG_IGN);
#endif
        if (0 != fork()) /* 产生子进程,父进程退出 */
            exit(0);
        if (-1 == setsid())/* 设置子进程的设置ID */
            exit(0);
        signal(SIGHUP, SIG_IGN);/* 忽略SIGHUP信号 */
        if (0 != fork())/* 再次产生子进程,父进程退出 */
            exit(0);
        if (0 != chdir("/"))/* 更改工作目录为根目录 */
            exit(0);
}

  这里作者使用了标准的产生*nix daemon的方法。两次调用fork并退出父进程。具体的原因读者可以参阅《Unix环境高级编程》(APUE)中有关daemon的讲解部分。
  顺着main函数继续向下走,沿途的各种初始化工作尽可忽略。下面的语句是本文的重点!

* 下面程序将产生多个子进程。这些子进程成为worker,
     * 也就是用于接受处理用户的连接的进程。而当前的主进程将
     * 成为watcher,主要工作就是监视workers的工作状态,
     * 当有worker因为意外而退出时,产生新的worker。
     * 在程序退出时,watcher负责停止所有的workers并清理资源。
     */
        int child = 0; //用来标记这个进程是子进程还是父进程。
           //当子进程返回到这个while循环的开始的时候,由于标记
        //进程是子进程,流程直接跳出while循环执行后面的程序。
        //而对于父进程,则继续产生子进程。
        while (!child && !srv_shutdown && !graceful_shutdown)
        {
            if (num_childs > 0) //watcher继续产生worker
            {
                switch (fork())
                {
                case -1:    //出错
                    return -1;
                case 0:     //子进程进入这个case
                    child = 1;
                    break;
                default:    //父进程进入这个case
                    num_childs--;
                    break;
                }
            } 
            else         //watcher
            {
                /**
                 * 当产生了足够的worker时,watcher就在这个while
                 * 中不断的循环。
                 * 一但发现有worker退出(进程死亡),立即产生新的worker。
                 * 如果发生错误并接受到SIGHUP信号,向所有的进程
                 *(父进程及其子进程)包括自己发送SIGHUP信号。
                 * 并退出。
                 */
                int status;

                if (-1 != wait(&status))
                {
                    /** 
                     * one of our workers went away 
                     */
                    num_childs++;
                } 
                else
                {
                    switch (errno)
                    {
                    case EINTR:
                        /**
                         * if we receive a SIGHUP we have to close our 
                         * logs ourself as we don't 
                         * have the mainloop who can help us here
                         */
                        if (handle_sig_hup)
                        {
                            handle_sig_hup = 0;
                            log_error_cycle(srv);
                            /**
                             * forward to all procs in the process-group
                             * 向所有进程发送SIGHUP信号。(父进程及其子进程)
                             * we also send it ourself
                             */
                            if (!forwarded_sig_hup)
                            {
                                forwarded_sig_hup = 1;
                                kill(0, SIGHUP);
                            }
                        }
                        break;
                    default:
                        break;
                    }end of switch (errno)...
                }//end of if (-1 != wait(&status)) ...
            }//end of if (num_childs > 0)...
        }// end of while(!child...

在正常的运行过程中,watcher进程是不会退出上面的while循环。一旦退出了这个循环,那么也就意为着整个程序退出了。
另外,woker的数量可以在配置文件中进行配置。
子进程,也就是worker退出了上面的while循环后就开始处理连接请求等各种工作。
在子进程的一开始,还是各种初始化工作,包括fd时间处理器的初始化(fdevent_init(srv->max_fds + 1, srv->event_handler)),stat cache初始化(stat_cache_init())等。子进程工作在一个大while循环中。
while的工作流程如下:
1、判断连接是否断开。如果断开,则调用处理程序进行处理并重新开始新一轮的日志记录。
2、判断是否接受到了alarm函数发出的信号。接受到信号后,判断服务器记录的时间是否和当前时间相同。如果相同,说明时间还没有过一秒,继续处理连接请求。如果不相同,则时间已经过了一秒。那么,服务器则触发插件,清理超时连接,清理stat-cache缓存。这理里面最重要的是处理超时连接。程序中通过一个for循环查询所有的连接,比较其idle的时间和允许的最大idle时间来判断连接是否超时。如果连接超时,则让连接进入出错的状态(connection_set_state(srv, con, CON_STATE_ERROR);)。
3、判断服务器socket连接是否失效。如果失效了,则在不是服务器过载的情况下将所有连接重新加入的fdevent中。为什么服务器socket会失效呢?可以看到,在后面判断出服务器过载后,即标记了socket连接失效。srv->sockets_disabled = 1;
4、如果socket没有失效,判断服务器是否过载。如果过载了,则关闭所有连接,清理服务器并退出服务器。
5、分配文件描述符。
6、启动事件轮询。等待各种IO时间的发生。包括文件读写,socket请求等。
7、一旦有事件发生,调用相应的处理函数进行处理。
8、最后,检查joblist中是否有未处理的job并处理之。
至此,一次循环结束了。然后,从头开始继续循环直到服务器关闭。

在处理IO事件的时候,程序进入下面的循环:

do
{
        fdevent_handler handler; //事件处理函数指针
        void *context;
        handler_t r;
        //获得下一个事件的标号
        fd_ndx = fdevent_event_next_fdndx(srv->ev, fd_ndx);
        //获得这个事件的具体类型。
revents = fdevent_event_get_revent(srv->ev, fd_ndx);
        //获得事件对应的文件描述符
fd = fdevent_event_get_fd(srv->ev, fd_ndx);
        //获得事件处理函数的指针
handler = fdevent_get_handler(srv->ev, fd);
        //获得事件的环境
context = fdevent_get_context(srv->ev, fd);
        /**
         * 这里,调用请求的处理函数handler处理请求!
         * 这才是重点中的重点!!
         */
        switch (r = (*handler) (srv, context, revents))
        {
        case HANDLER_FINISHED:
        case HANDLER_GO_ON:
        case HANDLER_WAIT_FOR_EVENT:
        case HANDLER_WAIT_FOR_FD:
            break;
        case HANDLER_ERROR:
            /*
             * should never happen 
             */
            SEGFAULT();
            break;
        default:
            log_error_write(srv, __FILE__, __LINE__, "d", r);
            break;
        }
    }while (—n > 0);

这个循环是worker进程的核心部分。这里由于作者对IO系统的出色的封装。我们不须要理解这些函数的作用就可知道连接的处理流程。在程序中,作者使用回调函数轻松的解决掉了处理工种事件时判断处理函数的问题。代码优雅而高效。在lighttpd中,作者使用了大量的回调函数,这使得代码清晰易懂,效率也很高。
  有一点值得注意。程序在处理连接超时的时候是每一秒中轮询所有的连接,判断其是否超时。这必然会降低服务器的效率。在实际的运行中,确实会对lighttpd的效率造成一定的影响。
  lighttpd使用的watcher-worker模型虽然简单,但是在实际的运行过程中也非常的高效。有的时候,简单并不一定就不高效。

本文章由 http://www.wifidog.pro/2015/04/16/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%9E%8B.html 整理编辑,转载请注明出处

wifidog认证源码分析Lighttpd1.4.20源码分析之etag.c(h) -------HTTP/1.1中的Etag域

etag的全称是entity tag(标记实体值),在RFC2616中关于etag的定义如下:
The ETag response-header field provides the current value of the entity tag for the requested variant. The headers used with entity tags are described in sections 14.24, 14.26 and 14.44. The entity tag MAY be used for comparison with other entities from the same resource(see section 13.3.3).
ETag = "ETag" ":" entity-tag
Examples:
ETag: "xyzzy"
ETag: W/"xyzzy" (前面的W/表示这个是个弱Etag)
ETag: ""
巨长的RFC2616对Etag的描述就上面这么多。意思就是说Etag域提供了请求变体的一个实体标记值。这个值可以和If-Match和If-No-Match一起使用。RFC2616中对Etag的唯一要求就是它是一个双引号包围的字符串,至于怎么生成这个字符串以及怎么使用,由应用程序决定。
下面说一说在服务器程序中,一般是怎么使用Etag的(这个东西用好了还是很不错的。。。):
把Last-Modified和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。
因为服务器首先产生Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
过程如下:
1.客户端请求一个页面(A)。
2.服务器返回页面A,并在给A加上一个Last-Modified/ETag。
3.客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
4.客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
5.服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
工作原理:
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match.
请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag).状态码200。
====第二次请求===
1.客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;

流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.
另外,使用Etag比使用Last-Modified接合If-Modified-Sience更有优势。如果一些文件经常被修改的不是文件的内容,而是文件的属性,如:文件的修改时间等。那么就没有必要重新发送文件,此时,Last-Modified不能判断其内容是否修改,所以只会重新发送。而使用Etag,可以通过检验如文件的i节点号,大小等来判断是否重传。
那么,在Lighttpd中,Etag到底是个什么东东呢?且听我慢慢道来。。。
在头文件Etag.h中定义了一个枚举类型

typedef enum 
{ 
    ETAG_USE_INODE = 1,      //包含文件的i节点号。
    ETAG_USE_MTIME = 2,      //包含文件最后一次修改的时间。
    ETAG_USE_SIZE = 4        //包含文件的byte数。
} etag_flags_t;

这个枚举类型决定了Etag中所包含的东西。注意,三个枚举量被定义为1,2,4.这样可以通过或的方式来包含多个内容。如::ETAG_USE_INODE | ETAG_USE_SIZE,表示Etag中既包含文件的i节点号,也包含文件的大小。
在头文件etag.h中,只声明了三个函数:
1、int etag_is_equal(buffer * etag, const char *matches);
这个是判断etag的内容是否和matches相同。相同返回1,不同返回0.
2、int etag_create(buffer * etag, struct stat *st, etag_flags_t flags);
这个是根据flags和st生成一个etag,存放在etag中传出。st是struct stat,在文件sys/stat.h中定义,用来表示文件的状态(目录也是文件哦)。可以使用stat函数获取一个文件的文件状态。具体操作请读者自己查阅。对于flags的设置,Lighttpd通过读取配置文件信息,设置flags。
3、int etag_mutate(buffer * mut, buffer * etag);
这个函数个etag生成一个哈希值,存放在mut中,并用双引号包围。里面使用的哈希方法是上一个结果左移五位异或上一个结果右移27位异或下一个字符,得到下一个结果。在实际应用中,这个哈希方法是很可靠的,基本上不会得到两个不同的字符串得到相同的结果,也就是基本不会出现碰撞。

三个函数的具体实现如下:

Code
int etag_is_equal(buffer * etag, const char *matches)
{
    if (etag && !buffer_is_empty(etag) && 0 == strcmp(etag->ptr, matches))
    {
        return 1;
    }
    return 0;
}

int etag_create(buffer * etag, struct stat *st, etag_flags_t flags)
{
    if (0 == flags)
        return 0;
    buffer_reset(etag);
    if (flags & ETAG_USE_INODE) //i节点号(serial number)
    {
        buffer_append_off_t(etag, st->st_ino);
        buffer_append_string_len(etag, CONST_STR_LEN("-"));
    }
    if (flags & ETAG_USE_SIZE) //普通文件的byte数
    {
        buffer_append_off_t(etag, st->st_size);
        buffer_append_string_len(etag, CONST_STR_LEN("-"));
    }
    if (flags & ETAG_USE_MTIME) //文件最后一次修改的时间。
    {
        buffer_append_long(etag, st->st_mtime);
    }
    return 0;
}

int etag_mutate(buffer * mut, buffer * etag)
{
    size_t i;
    uint32_t h;
    //计算哈希值。
    for (h = 0, i = 0; i < etag->used; ++i)
    {
        h = (h << 5) ^ (h >> 27) ^ (etag->ptr[i]);
    }
    buffer_reset(mut);
    buffer_copy_string_len(mut, CONST_STR_LEN("\""));
    buffer_append_long(mut, h);
    buffer_append_string_len(mut, CONST_STR_LEN("\""));
    return 0;
}

本文章由 http://www.wifidog.pro/2015/04/16/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.html 整理编辑,转载请注明出处

wifidog 认证Lighttpd1.4.20源码分析之bitset.c(h) -------位集合的使用

使用一个比特位来表示一个事件的两种状态,即节省内存,又可以提高运行速度。在Lighttpd中,提供了一个bitset数据结构,用来管理使用一个比特位集合。
  在bitset.h中,比特位集合的数据结构定义如下:

typedef struct 
{
            size_t *bits;
            size_t nbits;
} bitset;

bits指向一个size_t类型的数组,存放bit集合。size_t类型通常被定义成一个无符号的整型(int或long),其长度和具体的机器有关,这个读者可以查阅相关的资料。nbits记录bitset中bit为的个数。其图示如下:

+-------------+
| bitset结构     |
+-------------+             +-----------------------------+
|   bits           |   -------->          |  |  |  |  |  |  |  |  |  |  | | | | | |
+-------------+             +-----------------------------+
|  nbits = 10   |
+-------------+

为了提高运行的速度,对与bitset的主要操作都有四个宏来实现:
各个宏的作用都在注释中说明。

Code
//计算一个size_t类型有占多少位。
//CHAR_BIT表示一个char类型占多少为,在/usr/include/limits.h中定义,本人机器中定义为8.
#define BITSET_BITS \
    ( CHAR_BIT * sizeof(size_t) )

/**
 * 得到一个pos位置为1,其他位置都为0的size_t类型的掩码。
 * 其中pos位置是这个位在bitset中的位置,因此要模一个BITSET_BITS才是其在size_t中的位置。
 */
#define BITSET_MASK(pos) \
    ( ((size_t)1) << ((pos) % BITSET_BITS) )
/**
 * 计算pos位在set中的bits数组中的位置。
 * 也就是,pos位在数组bits中,包含在那个size_t类型的成员中。
 */
#define BITSET_WORD(set, pos) \
    ( (set)->bits[(pos) / BITSET_BITS] )

/**
 * 由于bitset中是用size_t类型数组来存放bit位的,因此实际开的空间应该是size_t的整数倍。
 * 这个宏就是用来计算在需要nbits个位的情况下,要开多少内存空间。
 * 也就是计算nbits是BITSET_BITS的整数倍加一。
 */
#define BITSET_USED(nbits) \
    ( ((nbits) + (BITSET_BITS - 1)) /

操作函数都比较简单短小,直接贴出来了。

/**
   * 初始化一个bitset为nbits位
   */
  bitset *bitset_init(size_t nbits)
  {
      bitset *set;

      set = malloc(sizeof(*set));
      assert(set);
     //分配空间并初始化为0.
     set->bits = calloc(BITSET_USED(nbits), sizeof(*set->bits));
     set->nbits = nbits;

     assert(set->bits);

     return set;
 }

 /**
  * 将set中的所有位重置为0
  */
 void bitset_reset(bitset * set)
 {
     memset(set->bits, 0, BITSET_USED(set->nbits) * sizeof(*set->bits));
 }

 //释放set
 void bitset_free(bitset * set)
 {
     free(set->bits);
     free(set);
 }

 //将pos位设置为0.
 void bitset_clear_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }
      BITSET_WORD(set, pos) &= ~BITSET_MASK(pos);
 }
 //将pos为设置为1
 void bitset_set_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }

     BITSET_WORD(set, pos) |= BITSET_MASK(pos);
 }
 //测试pos位置是否是1
 int bitset_test_bit(bitset * set, size_t pos)
 {
     if (pos >= set->nbits)
     {
         SEGFAULT();
     }

     return (BITSET_WORD(set, pos) & BITSET_MASK(pos)) != 0;
 }

Lighttpd中的bit接合操作剪断精悍,所有的代码都已经在本文中贴出来了。当然,头文件中的函数声明没有贴出来。。。

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd%E4%BD%8D%E9%9B%86%E5%90%88%E4%BD%BF%E7%94%A8.html 整理编辑,转载请注明出处

wifidog HTTP Lighttpd1.4.20源码分析之buffer.c(h)--------字符串内存管理(2)

一些工具性的函数:
1、int LI_ltostr(char *buf, long val);
将长整型val转化成字符串,并存入buf中。

Code
int LI_ltostr(char *buf, long val) 
{
    char swap;
    char *end;
    int len = 1;
    //val为负数,加一个负号,然后转化成正数
    if (val < 0) 
    {
        len++;
        *(buf++) = '-';
        val = -val;
    }
    end = buf;
    /*
          这里val必须设置为大于9,并在循环外在做一次转换
            (*(end) = '0' + val)!
             因为如果val设置为大于0,当val为0时,将不进入循环,那么循
           环后面直接在buf中
     * 追加'\0'。这样0就被转化成了空串!!
     * 这里val转化后的字符串是逆序存放在buf中的,在后面要反转,
     * 以得到正确的顺序。
     */
    while (val > 9) 
    {
        *(end++) = '0' + (val % 10);
        val = val / 10;
    }
    *(end) = '0' + val;
    *(end + 1) = '\0';
    len += end - buf;
    //将字符串反转,
    while (buf < end) 
    {
        swap = *end;
        *end = *buf;
        *buf = swap;
        buf++;
        end--;
    }
    return len;
}

2、char hex2int(unsigned char c);
  converts hex char (0-9, A-Z, a-z) to decimal.returns 0xFF on
invalid input. 将16进制的字符转化成对应的数字,非法输入返回0xFF。忽略c的大小写。
3、char int2hex(char i);
  将i转化成对应的16进制形式
4、int light_isdigit(int c);
  c是否是数字。0-9
5、int light_isxdigit(int c);
  c是否是十六进制的数字0-9 a-f
6、int light_isalpha(int c);
  c是否是字母。
7、int light_isalnum(int c);
  c是否是字母或数字。

  以上几个函数在处理大小写的时候,都使用了c |= 32;将c转换成小写的形式,无论c原来是大写还是小写。原理在函数buffer_caseless_compare中讲解过。
8、int buffer_to_lower(buffer * b);
  将b中的数据转换成小写。
9、int buffer_to_upper(buffer * b);
  将b中的数据转换成大写。
  以上两个函数没有在buffer.c中定义。

  下面的几个宏定义一些方便的操作。

Code
#define BUFFER_APPEND_STRING_CONST(x, y) \
    buffer_append_string_len(x, y, sizeof(y) - 1)

#define BUFFER_COPY_STRING_CONST(x, y) \
    buffer_copy_string_len(x, y, sizeof(y) - 1)
//在buffer中追加一个‘/’,如果最后一个字符是‘/’,则不追加。
#define BUFFER_APPEND_SLASH(x) \
    if (x->used > 1 && x->ptr[x->used - 2] != '/') 
{ BUFFER_APPEND_STRING_CONST(x, "/"); }

#define CONST_STR_LEN(x)  x, x ? sizeof(x) - 1 : 0
#define CONST_BUF_LEN(x)  x->ptr, x->used ? x->used - 1 : 0

#define SEGFAULT() 
do { 
fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); 
} while(0)
#define

以下的函数操作涉及到编码问题。在lighttpd中,使用到的编码有六种。具体的类型定义在下面的结构体中。

Code
typedef enum 
{
    ENCODING_UNSET,
    ENCODING_REL_URI,                /* for coding a rel-uri (/withspace/and%percent) nicely as part of a href */
    ENCODING_REL_URI_PART,        /* same as ENC_REL_URL plus coding / too as %2F */
    ENCODING_HTML,                /* & becomes &amp; and so on */
    ENCODING_MINIMAL_XML,        /* minimal encoding for xml */
    ENCODING_HEX,                    /* encode string as hex */
    ENCODING_HTTP_HEADER            /* encode \n with \t\n */

于此对应的 buffer.c 源文件中给出了
const char encoded_chars_rel_uri_part[]
const char encoded_chars_rel_uri[]
const char encoded_chars_html[]
const char encoded_chars_minimal_xml[]
const char encoded_chars_hex[]
const char encoded_chars_http_header[]
六个标志数组,数组中值为 1 的元素表示对应下标值大小的字符需要被编码转换,否则不需要转换可以直接使用(即编码前和编码后是同一个值)。例如对于 encoded_chars_rel_uri数组,encoded_chars_rel_uri[32]值为1表示该对应的字符(32对应的是空格,因为空格的十进制值为 32)需要被 uri 编码(被编码为“%20”),而对于值为 0的 encoded_chars_rel_uri[48],其对应的字符就不需要编码(48 对应的是字符‘0’,而字符‘0’并不是特殊字符,因此不用编码。)。对于具体的编码方式,请查阅相关资料。
1、int buffer_append_string_encoded(buffer * b, const char *s,size_t s_len, buffer_encoding_t encoding);
  将字符串s以指定的编码方式存入b中。encoding指定编码的方式。

/**
    * 将字符串s以指定的编码方式存入b中。
    * encoding指定编码的方式。
    */
   int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding) 
   {
       unsigned char *ds, *d;
       size_t d_len, ndx;
       const char *map = NULL;

      if (!s || !b) return -1;

      //b中存放的不是亦'\0'结尾的字符串。报错。
      if (b->ptr[b->used - 1] != '\0') 
      {
          SEGFAULT();
      }

      if (s_len == 0) return 0;

      //根据编码方式,选择对应的编码数组,就是上面的那六个数组。
      switch(encoding) {
      case ENCODING_REL_URI:
          map = encoded_chars_rel_uri;
          break;
      case ENCODING_REL_URI_PART:
          map = encoded_chars_rel_uri_part;
          break;
      case ENCODING_HTML:
          map = encoded_chars_html;
          break;
      case ENCODING_MINIMAL_XML:
          map = encoded_chars_minimal_xml;
          break;
      case ENCODING_HEX:
          map = encoded_chars_hex;
          break;
      case ENCODING_HTTP_HEADER:
          map = encoded_chars_http_header;
          break;
      case ENCODING_UNSET:
          break;
      }

      assert(map != NULL);

      /* 
       * count to-be-encoded-characters 
       * 计算经过编码转换后的字符串s的长度。
       * 不同的编码方式,对与不同的字符,其转换后的字符长度不同。
       */
      for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:
              case ENCODING_REL_URI_PART:
                  d_len += 3;
                  break;
             case ENCODING_HTML:
             case ENCODING_MINIMAL_XML:
                  d_len += 6;
                  break;
              case ENCODING_HTTP_HEADER:
              case ENCODING_HEX:
                  d_len += 2;
                  break;
              case ENCODING_UNSET:
                  break;
              }
          } 
          else //字符不需要转换 
          {
              d_len ++;
          }
      }

      buffer_prepare_append(b, d_len);

      //下面这个循环就是开始做实际的编码转换工作。
     //ds指向字符串s中的字符。d指向b的数据去存放字符的位置。
      for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:             //此编码不需要转换
              case ENCODING_REL_URI_PART:     //将字符ASCII码转化成其对应的十六进制的形式,并在前面加上'%'
                  d[d_len++] = '%';
                  d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                  d[d_len++] = hex_chars[(*ds) & 0x0F];
                  break;
              case ENCODING_HTML:             //不需要转换
              case ENCODING_MINIMAL_XML:         //也是转换成ASCII编码的十六进制形式,前面要加上"&#x",尾随一个';'
                  d[d_len++] = '&';
                  d[d_len++] = '#';
                 d[d_len++] = 'x';
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 d[d_len++] = ';';
                 break;
             case ENCODING_HEX:                 //直接转换成ASCII码对应的十六进制。
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 break;
             case ENCODING_HTTP_HEADER:        //这个处理HTTP头中的换行,统一转换成'\n\t'
                 d[d_len++] = *ds;
                 d[d_len++] = '\t';
                 break;
             case ENCODING_UNSET:
                 break;
             }
        } 
         else 
         {
             d[d_len++] = *ds;
         }
     }     
     /* 
      * terminate buffer and calculate new length 
      * 在新字符串尾部加上一个'\0' 
     */
     b->ptr[b->used + d_len - 1] = '\0';     
     b->used += d_len;         //新的字符串长度。
     return 0;
 }

2、static int buffer_urldecode_internal(buffer *url, int is_query)
  将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.

/* 
   * decodes url-special-chars inplace.
   * replaces non-printable characters with '_'
   * 将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.
   * 也就是把ASCII码的16进制表示,转换成正常的ASCII码。转换后的结果直接存放在url中。
   *
   * 这个is_query参数的作用仅仅控制是否将字符串中的'+'转换成空格' '。
   */

 static int buffer_urldecode_internal(buffer *url, int is_query) 
 {
     unsigned char high, low;
     const char *src;
     char *dst;

     if (!url || !url->ptr) return -1;

     //源字符串和目的字符串是同一个串。
     src = (const char*) url->ptr;
     dst = (char*) url->ptr;

     while ((*src) != '\0') 
     {
         if (is_query && *src == '+') 
         {
             *dst = ' ';
         } 
         else if (*src == '%') 
         {
             *dst = '%';
             //将后面16进制表示的ASCII码转换成正常的ASCII码。
             high = hex2int(*(src + 1));          //高四位
             if (high != 0xFF)                   //0xFF表示转换出错。
             {
                 low = hex2int(*(src + 2));         //低四位
                 if (low != 0xFF) 
                 {
                     high = (high << 4) | low;      //合并
                     /* map control-characters out  判断是否是控制字符。*/
                    if (high < 32 || high == 127) 
                         high = '_';
                     *dst = high;
                     src += 2;     
                     //这个转换过程中,三个源字符转换成一个目的字符。
                    //虽然是一个字符串,但源字符串遍历的更快,不会冲突。
                 }
             }
         } 
         else 
         {
             *dst = *src;
         }

         dst++;
        src++;
     }

     *dst = '\0';     //新结尾。
     url->used = (dst - url->ptr) + 1;

     return 0;
 }

3、int buffer_path_simplify(buffer * dest, buffer * src);
  删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。

/* Remove "/../", "//", "/./" parts from path.
    *
    * /blah/..         gets  /
    * /blah/../foo     gets  /foo
    * /abc/./xyz       gets  /abc/xyz
    * /abc//xyz        gets  /abc/xyz
    *
    * NOTE: src and dest can point to the same buffer, in which case,
    *       the operation is performed in-place.
   *
   * 删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。
   * 对于"/../"在路径中相当与父目录,因此,实际的路径相当于删除"/../"和其前面的一个"/XX/".
   * 如: /home/test/../foo   ->   /home/foo
   * 而"//"和"/./"表示当前目录,简单的将其删去就可以了。
  * NOTE: 源缓冲src和目的缓冲可以指向同一个缓冲,在这种情况下,操作将源缓冲中的数据替换。
   */

  int buffer_path_simplify(buffer *dest, buffer *src)
  {
      int toklen;
      char c, pre1;
      char *start, *slash, *walk, *out;
      unsigned short pre;     //pre两个字节,char一个字节,pre中可以存放两个字符。

     if (src == NULL || src->ptr == NULL || dest == NULL)
          return -1;

      if (src == dest)
          buffer_prepare_append(dest, 1);
      else
          buffer_prepare_copy(dest, src->used + 1);

      walk  = src->ptr;
      start = dest->ptr;
      out   = dest->ptr;
      slash = dest->ptr;


  #if defined(__WIN32) || defined(__CYGWIN__)
      /* 
       * cygwin is treating \ and / the same, so we have to that too
       * cygwin中\和/相同。转化之。
       */

      for (walk = src->ptr; *walk; walk++) 
      {
          if (*walk == '\\') *walk = '/';
      }
      walk = src->ptr;
  #endif
      //过滤掉开始的空格。
      while (*walk == ' ') 
      {
          walk++;
      }

      pre1 = *(walk++);
      c    = *(walk++);
      pre  = pre1;
      if (pre1 != '/')  //路径不是以'/'开始,在目的路径中加上'/'
      {
          pre = ('/' << 8) | pre1; //将prel指向的字符存放在pre的高八位。
         *(out++) = '/';
      }
      *(out++) = pre1;

     if (pre1 == '\0')          //转换结束
      {
          dest->used = (out - start) + 1;
          return 0;
      }

      while (1) 
      {
          if (c == '/' || c == '\0') 
          {
              toklen = out - slash; //slash指向距离c指向的字符前面最近的一个'/'。
              if (toklen == 3 && pre == (('.' << 8) | '.')) // "/../"
             {
                 out = slash;
                 if (out > start) //删除"/../"前面的一层目录"/XX/".
                  {
                      out--;
                     while (out > start && *out != '/') 
                      {
                          out--;
                      }
                  }

                  if (c == '\0')
                      out++;
              } 
              else if (toklen == 1 || pre == (('/' << 8) | '.')) // "//" 和 "/./"
              {
                  out = slash;
                 if (c == '\0')
                      out++;
              }

             slash = out;
         }

         if (c == '\0')
             break;

         pre1 = c;
         pre  = (pre << 8) | pre1; //pre始终存放的是prel指向的字符和其前一个字符。
         c    = *walk;
         *out = pre1;

         out++;
         walk++;
     }

     *out = '\0';
     dest->used = (out - start) + 1;

     return 0;
 }

总得来说,buffer的内容比较简单,其他的函数读者可以自行查看。

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-2.html 整理编辑,转载请注明出处