10.1 概述

熟悉UNIX/Linux网络编程的读者知道,在编写网络通信程序的时候离不开这几个系统调用:如socket()、bind()、listen()、connect()、accept()、write()/read()、close()等。作为Web服务器网络应用程序,Lighttpd当然也毫不例外地要调用这些系统函数来接受客户端请求,提供资源服务。在第8章里,我们跳过了监听描述符的创建过程而直接详细分析了在监听描述符上的事件管理,本章将讲叙包括如何创建监听描述符以及监听描述符接收到客户端请求后如何处理等相关内容,使得读者对Lighttpd网络服务响应请求流程有清晰透彻的理解。

本节相关部分源码:
      base.h
      server.c
       network.c

10.2 简单网络服务通信模型

在开始对Lighttpd的服务通信分析之前,我们先来看一下简单的网络服务通信模型,如图10-1所示,该模型是单进程阻塞并发服务模型。

这个模型虽然简单,但是其已经将基于TCP的socket编程表现得很清楚。在服务器端和客户端进行实际的消息传递通信之前,必须在这两端之间建立起可信任的连接以及相互之间获取两端的相关信息,该连接通过如下步骤来建立。

首先是服务器端调用系统函数socket()<sup>73</sup>建立套接口描述符,同时指定期望的通信协议类型,比如TCP、UDP或UNIX域字节流等,利用socket()创建的描述符还需要被命名以便其他网络设备(如客户端)可以找到这个套接口,因此接下来调用bind()函数将套接口描述符与某个协议地址(一般就是本地主机地址加上本地端口号)关联起来。有名字的套接口可以很轻易地让其他远程端找到,但是如要要让该套接口能接收远程端的连接请求还需调用listen()函数将其转换为被动套接口,这是因为socket()创建的套接口被默认为主动套接口,即用来主动连接其他套接口的。被动套接口通知内核为其接收远程端发来的连接请求,此时内核为其准备两个连接队列:一个为未完成三次握手协议的连接队列,一个为已完成三次握手协议的连接队列(这两个队列总大小由listen()函数的第二个参数指定。另外,从这里可以看到三次握手协议由内核自动完成)。服务器进程接下来调用系统函数accept()接受内核准备好了的远程客户连接请求(即从已完成三次握手协议的连接队列中取出队头项连接),如果没有远程客户连接请求或者内核还没有完成连接的创建(此时已完成三次握手协议的连接队列为空)则函数accept()调用阻塞。只要函数accept()被内核准备的已建立的客户端连接唤醒,它就将为服务器进程获取当前连接的对端信息(比如协议地址等)。通过这些步骤,两端之间的可信任的连接就建立起来,而相互之间的信息也都知道,此时服务进程fork()一个和自己完全一样的子进程为该连接服务,而服务器端父进程继续等待下一个连接请求(由于监听描述符和连接描述符在子进程也会被复制,但是监听描述符对于子进程没有用,因此需要关闭,同样连接描述符对于父进程也没有用而需要关闭)。子进程可以通过系统调用read()/write()传递信息,当所有信息传递完毕之后,子进程和客户端进程双方调用close()退出进程执行。

上面描述的是服务器端情况,对于客户端情况就简单多了,客户端利用socket()创建的套接口通过系统调用connect()建立与服务器的连接,这个连接的建立过程也就是所谓的三次握手协议。对于客户端有两点值得说明:第一,客户端套接口调用connect()函数前不必调用bind()函数为该套接口命名,因为如果需要,内核会自动确定源IP地址并且选择一个临时端口作为源端口;第二,既然客户端要连接服务器端,所以必须要知道服务器的套接口名称,即IP地址和端口。IP地址一般通过域名解析可得,而端口服务器就得选择熟知端口(如HTTP的80端口)才能让更广泛的客户端连接进来。

10.3 Lighttpd网络服务通信模型

10.3.1 通信模型总图

上一节给出的简单网络服务通信模型并非是毫无理由的摆设,它是我们认识Lighttpd网络服务通信模型的基础。图10-2给出了Lighttpd网络服务通信模型的总图(以单进程且选择SELECT多路I/O复用为例)。

图 10-2 Lighttpd网络服务通信模型

从图10-2中可以看到Lighttpd网络服务通信模型和上一节给出的简单网络服务通信模型相比,在建立监听套接口描述符之前的步骤都是完全一样的,而之后由于采用了I/O复用技术却又大为不同。虽然Lighttpd网络服务通信也是单进程阻塞并发服务模型,但是其阻塞的位置不再是accept()函数处而是复用select()函数点,而且此处的select()函数并不会将进程永远阻塞,它将等待一段时间(如1秒)内的事件,如果没有事件发生则该函数超时返回,让进程可以处理一些其他事情,如管理流量控制、清除超时连接等。当有事件发生的情况下,如果是监听描述符上的事件发生则调用accept()函数接受远程连接获取信息建立连接套接口描述符并加入到I/O复用,可以认为此处的accept()函数不会阻塞,因为我们已经知道监听描述符上有发生事件。对于其他连接套接口描述符发生的事件,进程也会进行处理,如读写操作,所有这些事件处理完后,进程继续回到select()函数等待下一次事件。从整个这个过程看到,Lighttpd网络服务通信模型中所有的事件都是由主进程自己处理并没有像图10-1中的模型那样fork()一个子进程对连接套接口单独处理,这样做的好处就是节约系统资源,提高效率。

10.3.2 通信模型源码分析

在这一节我们将讲解Lighttpd中网络服务通信模型的具体实现源码,这里包括三个分步过程讲解:第一个过程为Lighttpd监听套接口描述符的创建过程(至于如何加入到I/O复用监控,第9章已经详细讲解,因此本节不再重复);第二,监听套接口发生请求连接事件后建立连接套接口描述符以及将其加入I/O复用监控的过程;第三,连接套接口描述符发生请求服务事件后的响应过程。

1.创建监听套接口描述符

        如果忽略众多细节,创建监听套接口描述符的过程非常简单,仅涉及两个源文件中的三个函数。
       首先是源文件server.c中main()函数的network_init()函数调用,这个调用出现了两次,但是刚好分别出现在if...else...语句的不同分支里,因此虽然network_init()函数被调用了两次但它却会且仅会执行一次,如清单10-1所示。
      清单10-1 主程序中调用函数network_init

//server.c
1.if(i_am_root){
2.//……省略……
3.if(0!=network_init(srv)){
4.plugins_free(srv);
5.server_free(srv);
6.return-1;
7.}
8.//……省略……
9.}else{
10.//……省略……
11.if(0!=network_init(srv)){
12.plugins_free(srv);
13.server_free(srv);
14.return-1;
15.}
16.}

network_init()函数内没有创建监听套接口描述符的实际动作,但是它主要用于获取那些用户配置的将要被创建的监听套接口信息,这包括主Web站点和基于IP/端口的虚拟主机Web站点。

清单10-2 函数network_init

//network.c
17.int network_init(server*srv){
18.buffer*b;
19.size_t i;
20.network_backend_t backend;
/*类似于之前讲过的I/O复用技术的选择,这里Lighttpd也按照所谓的优劣次序选择服务器向客户端发送数据的方式。这些方式包括有所谓的“零拷贝”方式(这是目前性能最优的网络数据传递方式,我将在后面章节试图讲解它)、散布读/聚集写方式以及一般的读写方式。*/
21.struct nb_map{
22.network_backend_t nb;
23.const char*name;
24.}network_backends[]={
25./*lowest id wins*/
26.#if defined USE_LINUX_SENDFILE
27.{NETWORK_BACKEND_LINUX_SENDFILE,"linux-sendfile"},
28.#endif
29.#if defined USE_FREEBSD_SENDFILE
30.{NETWORK_BACKEND_FREEBSD_SENDFILE,"freebsd-sendfile"},
31.#endif
32.#if defined USE_SOLARIS_SENDFILEV
33.{NETWORK_BACKEND_SOLARIS_SENDFILEV,"solaris-sendfilev"},
34.#endif
35.#if defined USE_WRITEV
36.{NETWORK_BACKEND_WRITEV,"writev"},
37.#endif
38.{NETWORK_BACKEND_WRITE,"write"},
39.{NETWORK_BACKEND_UNSET,NULL}
40.};
/*创建主Web站点的监听套接口描述符,绑定的IP地址由配置项server.bind指定,端口由配置项server.port指定。如果用户未指定配置项,server.bind则自动绑定到通配地址INADDR_ANY,而配置项server.port未指定则默认为HTTP常规端口80。
示例:
server.bind="127.0.0.1"
server.port=3000*/
41.b=buffer_init();
42.buffer_copy_string_buffer(b,srv->srvconf.bindhost);
43.buffer_append_string_len(b,CONST_STR_LEN(":"));
44.buffer_append_long(b,srv->srvconf.port);
/*调用函数network_server_init()实际创建监听套接口描述符。*/
45.if(0!=network_server_init(srv,b,srv->config_storage[0])){
46.return-1;
47.}
48.buffer_free(b);
49.#ifdef USE_OPENSSL
50.srv->network_ssl_backend_write=network_write_chunkqueue_openssl;
51.#endif
52./*get a usefull default*/
53.backend=network_backends[0].nb;/*选择系统支持的最好的数据读写方式。*/
54./*match name against known types*/
55.if(!buffer_is_empty(srv->srvconf.network_backend)){/*用户的实际选择。*/
56.for(i=0;network_backends[i].name;i++){
57./**/
58.if(buffer_is_equal_string(srv->srvconf.network_backend,
59.network_backends[i].name,strlen(network_backends[i].name))){
60.backend=network_backends[i].nb;
61.break;
62.}
63.}
64.if(NULL==network_backends[i].name){
/*用户选择了一个无效的读写方式。*/
65./*we don't know it*/
66.log_error_write(srv,__FILE__,__LINE__,"sb",
67."server.network-backend has a unknown value:",
68.srv->srvconf.network_backend);
69.return-1;
70.}
71.}
72.switch(backend){/*根据最终选择的数据读写方式,关联回调函数。*/
73.case NETWORK_BACKEND_WRITE:
74.srv->network_backend_write=network_write_chunkqueue_write;
75.break;
76.#ifdef USE_WRITEV
77.case NETWORK_BACKEND_WRITEV:
78.srv->network_backend_write=network_write_chunkqueue_writev;
79.break;
80.#endif
81.#ifdef USE_LINUX_SENDFILE
82.case NETWORK_BACKEND_LINUX_SENDFILE:
83.srv->network_backend_write=network_write_chunkqueue_linuxsendfile;
84.break;
85.#endif
86.#ifdef USE_FREEBSD_SENDFILE
87.case NETWORK_BACKEND_FREEBSD_SENDFILE:
88.srv->network_backend_write=network_write_chunkqueue_freebsdsendfile;
89.break;
90.#endif
91.#ifdef USE_SOLARIS_SENDFILEV
92.case NETWORK_BACKEND_SOLARIS_SENDFILEV:
93.srv->network_backend_write=network_write_chunkqueue_solarissendfilev;
94.break;
95.#endif
96.default:
97.return-1;
98.}
/*check for$SERVER["socket"]*/
/*基于IP/端口的虚拟主机Web站点监听套接口描述符。
配置项正确格式如下:
$SERVER["socket"]=="127.0.0.1:3001"{...}
*/
/*0下标元素保存的是基本全局配置信息,因此从索引1开始。*/
99.for(i=1;i<srv->config_context->used;i++){
100.data_config*dc=(data_config*)srv->config_context->data[i];
101.specific_config*s=srv->config_storage[i];
102.size_t j;
103./*not our stage*//*不是虚拟主机配置项。*/
104.if(COMP_SERVER_SOCKET!=dc->comp)continue;
105.if(dc->cond!=CONFIG_COND_EQ){
/*对于虚拟主机配置项只运行相等比较。*/
106.log_error_write(srv,__FILE__,__LINE__,"s",
107."only==is allowed for$SERVER[\"socket\"].");
108.return-1;
109.}
110./*check if we already know this socket,
111.*if yes,don't init it*/
/*字段srv->srv_sockets为server_socket_array结构体类型,相关结构体都定义在头文件base.h内:
//base.h
typedef struct{
server_socket**ptr;
size_t size;
size_t used;
}server_socket_array;
typedef struct{
sock_addr addr;
int fd;
int fde_ndx;/*-1表示未注册到I/O多路复用中。*/
buffer*ssl_pemfile;
buffer*ssl_ca_file;
buffer*ssl_cipher_list;
unsigned short ssl_use_sslv2;
unsigned short use_ipv6;
unsigned short is_ssl;
/*ipv4:port
[ipv6]:port*/
buffer*srv_token;
#ifdef USE_OPENSSL
SSL_CTX*ssl_ctx;
#endif
unsigned short is_proxy_ssl;
}server_socket;
typedef union{
#ifdef HAVE_IPV6
struct sockaddr_in6 ipv6;
#endif
struct sockaddr_in ipv4;
#ifdef HAVE_SYS_UN_H
struct sockaddr_un un;
#endif
struct sockaddr plain;
}sock_addr;
///usr/include/netinet/in.h
/*Structure describing an Internet socket address.*/
struct sockaddr_in
{
__SOCKADDR_COMMON(sin_);
in_port_t sin_port;/*Port number.*/
struct in_addr sin_addr;/*Internet address.*/
/*Pad to size ofstruct sockaddr'.*/
unsigned char sin_zero[sizeof(struct sockaddr)-
__SOCKADDR_COMMON_SIZE-
sizeof(in_port_t)-
sizeof(struct in_addr)];
};
/*Internet address.*/
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
/*Ditto,for IPv6.*/
struct sockaddr_in6
{
__SOCKADDR_COMMON(sin6_);
in_port_t sin6_port;/*Transport layer port#*/
uint32_t sin6_flowinfo;/*IPv6 flow information*/
struct in6_addr sin6_addr;/*IPv6 address*/
uint32_t sin6_scope_id;/*IPv6 scope-id*/
};
/*IPv6 address*/
struct in6_addr
{
union
{
uint8_t__u6_addr8[16];
#if defined__USE_MISC||defined__USE_GNU
uint16_t__u6_addr16[8];
uint32_t__u6_addr32[4];
#endif
}__in6_u;
#define s6_addr__in6_u.__u6_addr8
#if defined__USE_MISC||defined__USE_GNU
#define s6_addr16__in6_u.__u6_addr16
#define s6_addr32__in6_u.__u6_addr32
#endif
};
extern const struct in6_addr in6addr_any;/*::*/
extern const struct in6_addr in6addr_loopback;/*::1*/
#define IN6ADDR_ANY_INIT{{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}}
#define IN6ADDR_LOOPBACK_INIT{{{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}}
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
///usr/include/sys/un.h
/*Structure describing the address of an AF_LOCAL(aka AF_UNIX)socket.*/
struct sockaddr_un
{
__SOCKADDR_COMMON(sun_);
char sun_path[108];/*Path name.*/
};
///usr/include/bits/socket.h
/*Structure describing a generic socket address.*/
struct sockaddr
{
__SOCKADDR_COMMON(sa_);/*Common data:address family and length.*/
char sa_data[14];/*Address data.*/
};
///usr/include/bits/sockaddr.h
/*POSIX.1g specifies this type name for thesa_family'member.*/
typedef unsigned short int sa_family_t;
/*This macro is used to declare the initial common members
of the data types used for socket addresses,struct sockaddr',
struct sockaddr_in',struct sockaddr_un',etc.*/
#define__SOCKADDR_COMMON(sa_prefix)\
sa_family_t sa_prefix##family
#define__SOCKADDR_COMMON_SIZE(sizeof(unsigned short int))
我们在下一个network_server_init()函数的分析中可以看到,所有已经被创建了监听套接口描述符的对应server_socket信息都会被记录在该字段内,因此此处通过和该字段内保存的记录做比较可以知道该server_socket信息对应的监听套接口是否已经被创建。
*/
112.for(j=0;j<srv->srv_sockets.used;j++){
113.if(buffer_is_equal(srv->srv_sockets.ptr[j]->srv_token,dc->string)){
114.break;
115.}
116.}
117.if(j==srv->srv_sockets.used){
/*比较完所有项都未匹配则表示还没创建。*/
118.if(0!=network_server_init(srv,dc->string,s))return-1;
119.}
120.}
121.return 0;
122.}

函数network_server_init()代码量比较多,但是其执行主线还是很明朗,其按照网络TCP通信程序编程的一般函数调用顺序分别调用socket()、setsockopt()、bind()、listen()进行监听套接口的创建。清单10-3给出了该函数的大部分源码而省略了与本节讲解内容无关的SSL与WIN32部分。
清单10-3 函数network_server_init

//network.c
123.int network_server_init(server*srv,buffer*host_token,specific_config*s){
124.int val;
125.socklen_t addr_len;
126.server_socket*srv_socket;
127.char*sp;
128.unsigned int port=0;
129.const char*host;
130.buffer*b;
131.int is_unix_domain_socket=0;
132.int fd;
133.#ifdef SO_ACCEPTFILTER
134.struct accept_filter_arg afa;
135.#endif
136.#ifdef__WIN32
137.//……省略……
138.#endif
139.srv_socket=calloc(1,sizeof(*srv_socket));
140.srv_socket->fd=-1;
141.srv_socket->srv_token=buffer_init();
142.buffer_copy_string_buffer(srv_socket->srv_token,host_token);
143.b=buffer_init();
144.buffer_copy_string_buffer(b,host_token);
145./*ipv4:port
146.*[ipv6]:port
147.*/
/*函数strrchr()的原型为char*strrchr(const char*s,int c);,其功能是用来找出参数s字符串中最后一个出现的参数c,如果找到则将该字符出现的地址返回,否则返回NULL。使用该函数需要包含头文件string.h。因此,这里是用于分割IP地址和端口号。*/
148.if(NULL==(sp=strrchr(b->ptr,':'))){
149.log_error_write(srv,__FILE__,__LINE__,"sb","value of$SERVER[\"socket\"]
has to be\"ip:port\".",b);
150.return-1;
151.}
152.host=b->ptr;
153./*check for[and]*/
154.if(b->ptr[0]=='['&&*(sp-1)==']'){
/*IPV6地址的给定字符串格式是[ipv6]:port。*/
155.*(sp-1)='\0';/*去除']'。*/
156.host++;/*去除'['。*/
157.s->use_ipv6=1;
158.}
159.*(sp++)='\0';
/*字符转长整型数。*/
160.port=strtol(sp,NULL,10);
161.if(host[0]=='/'){
/*host is a unix-domain-socket*/
/*使用UNIX域协议,UNIX域协议关联一个以空字符结尾的路径名(此路径名必须是绝对路径名而不是一个相对路径名,所以字符串第一个字符应该为字符'/'),因此这里通过检测传递的地址字符串第一个字符是否为'/'字符来判断是否使用UNIX域协议。
UNIX域主要用来做同一域内的进程间通信,在处理一个进程和多个进程间通信执行类似于服务器/客户通信时,UNIX域是一种比较方便和快速的办法。它所使用的API与在不同的主机上执行服务器/客户所用的API(套接口API)完全相同,便于代码共享。UNIX域也提供了两类套接口:字节流套接口和数据报套接口。总的来说,在同一台主机上的多个进程之间通信,UNIX域有如下优点:1)利用常规的SOCKET编写的TCP套接口快;2)UNIX域套接口可以在不同进程之间传递描述字;3)UNIX域套接口较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施74。*/
162.is_unix_domain_socket=1;
163.}else if(port==0||port>65535){/*检查端口号。*/
164.log_error_write(srv,__FILE__,__LINE__,"sd","port out of range:",port);
165.return-1;
166.}
167.if(*host=='\0')host=NULL;
168.if(is_unix_domain_socket){/*创建使用UNIX域协议的套接口。*/
169.#ifdef HAVE_SYS_UN_H
170.srv_socket->addr.plain.sa_family=AF_UNIX;
/*SOCK_STREAM指定为字节流套接口,另外几种分别为:SOCK_DGRAM数据报套接口、SOCK_SEQPACKET有序分组套接口、SOCK_RAW原始套接口。第三个参数指定为0表示选择系统默认的传输协议。*/
171.if(-1==(srv_socket->fd=socket(srv_socket->addr.plain.sa_family,
SOCK_STREAM,0))){
172.log_error_write(srv,__FILE__,__LINE__,"ss","socket failed:",
strerror(errno));
173.return-1;
174.}
175.#else
176.log_error_write(srv,__FILE__,__LINE__,"s",
"ERROR:Unix Domain sockets are not supported.");
177.return-1;
178.#endif
179.
}
180.#ifdef HAVE_IPV6
181.if(s->use_ipv6){/*创建使用IPv6协议的字节流套接口,使用TCP传输协议。*/
182.srv_socket->addr.plain.sa_family=AF_INET6;
183.if(-1==(srv_socket->fd=socket(srv_socket->addr.plain.sa_family,
SOCK_STREAM,IPPROTO_TCP))){
184.log_error_write(srv,__FILE__,__LINE__,"ss","socket failed:",
strerror(errno));
185.return-1;
186.}
187.srv_socket->use_ipv6=1;
188.}
189.#endif
190.if(srv_socket->fd==-1){
/*未创建使用IPv6协议的字节流套接口或创建失败的情况下则创建使用IPv4协议的字节流套接口,使用TCP传输协议。*/
191.srv_socket->addr.plain.sa_family=AF_INET;
192.if(-1==(srv_socket->fd=socket(srv_socket->addr.plain.sa_family,
SOCK_STREAM,IPPROTO_TCP))){
193.log_error_write(srv,__FILE__,__LINE__,"ss","socket failed:",
strerror(errno));
194.return-1;
195.}
196.}
197./**/
198.srv->cur_fds=srv_socket->fd;
199.val=1;
/*SO_REUSEADDR套接口选项的作用就是允许重用本地地址,读者可以想象这么一种情况:Lighttpd服务器异常退出导致没有将绑定的本地地址释放,如果没有给Lighttpd服务器监听套接口设置SO_REUSEADDR选项,那么此时重启Lighttpd服务器则会得到本地地址已在使用中的错误而无法启动的服务。这是最常见的目的,即为了允许启动一个监听服务器绑定在一个之前就被绑定并正被使用的地址端口上,这必须保证前后的监听套接口都设置有SO_REUSEADDR选项。SO_REUSEADDR选项允许同一端口上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的,例如在有多块网卡或用IP Alias技术的机器上存在这种情况。SO_REUSEADDR选项允许单个进程绑定相同的端口到多个套接口上,只要每次捆绑的本地IP地址不同即可。SO_REUSEADDR选项还允许完全相同的地址和端口的重复绑定,这个特性仅支持UDP套接口。
函数setsockopt()用于设置套接口选项。套接口选项众多,需要关于这个函数以及选项更多细节的读者应该考虑查询man手册(命令"man tcp")或Richard Stevens编写的《UNIX网络编程》一书。*/
200.if(setsockopt(srv_socket->fd,SOL_SOCKET,SO_REUSEADDR,&val,
sizeof(val))<0){
201.log_error_write(srv,__FILE__,__LINE__,"ss","socketsockopt failed:",
strerror(errno));
202.return-1;
203.}
204.switch(srv_socket->addr.plain.sa_family){
205.#ifdef HAVE_IPV6
206.case AF_INET6:
207.memset(&srv_socket->addr,0,sizeof(struct sockaddr_in6));
208.srv_socket->addr.ipv6.sin6_family=AF_INET6;
209.if(host==NULL){/*未指定绑定地址则使用通配地址in6addr_any。*/
210.srv_socket->addr.ipv6.sin6_addr=in6addr_any;
211.}else{
/*addrinfo结构体定义在/usr/include/netdb.h内:
///usr/include/netdb.h
/*Structure to contain information about address of a service provider.*/struct addrinfo
{
int ai_flags;/*Input flags.*/
int ai_family;/*Protocol family for socket.*/
int ai_socktype;/*Socket type.*/
int ai_protocol;/*Protocol for socket.*/
socklen_t ai_addrlen;/*Length of socket address.*/
struct sockaddr*ai_addr;/*Socket address for socket.*/
char*ai_canonname;/*Canonical name for service location.*/
struct addrinfo*ai_next;/*Pointer to next in list.*/
};
*/
212.struct addrinfo hints,*res;
213.int r;
214.memset(&hints,0,sizeof(hints));/*清空0。*/
215.hints.ai_family=AF_INET6;/*填入需要获取的信息。*/
216.hints.ai_socktype=SOCK_STREAM;
217.hints.ai_protocol=IPPROTO_TCP;
/*函数getaddrinfo()是IPv6新引入的API,它是协议无关的,既可用于IPv4也可用于IPv6。该函
数用于获得一个addrinfo结构体列表(通过字段ai_next),该列表通过其第四个参数隐性传出,调用执行成功返回0,否则返回非0值。*/
218.if(0!=(r=getaddrinfo(host,NULL,&hints,&res))){
219.log_error_write(srv,__FILE__,__LINE__,
220."sssss","getaddrinfo failed:",
221.gai_strerror(r),"'",host,"'");
222.return-1;
223.
}
/*转存需要的值。*/
224.memcpy(&(srv_socket->addr),res->ai_addr,res->ai_addrlen);
225.freeaddrinfo(res);/*释放为链表res所分配的存储空间。*/
226.}
/*将主机字节顺序的无符号短整型数转换成网络字节顺序格式。我们知道主机字节顺序大端(Big Endian)和小端(Little Endian)之分(具体与主机处理器有关),为了保证数据在不同主机之间传输时能够被正确解释,网络传送数据字节统一采用大端排序方式。*/
227.srv_socket->addr.ipv6.sin6_port=htons(port);
228.addr_len=sizeof(struct sockaddr_in6);
229.break;
230.#endif
231.case AF_INET:
232.memset(&srv_socket->addr,0,sizeof(struct sockaddr_in));
233.srv_socket->addr.ipv4.sin_family=AF_INET;
234.if(host==NULL){
/*将主机字节顺序的无符号长整型数转换成网络字节顺序格式。*/
235.srv_socket->addr.ipv4.sin_addr.s_addr=htonl(INADDR_ANY);
236.}else{
/*hostent结构体定义在/usr/include/netdb.h内:
///usr/include/netdb.h
/*Description of data base entry for a single host.*/
struct hostent
{
char*h_name;/*Official name of host.*/
char**h_aliases;/*Alias list.*/
int h_addrtype;/*Host address type.*/
int h_length;/*Length of address.*/
char**h_addr_list;/*List of addresses from name server.*/
#if defined__USE_MISC||defined__USE_GNU
#define h_addr h_addr_list[0]/*Address,for backward compatibility.*/
#endif
};
*/
237.struct hostent*he;
/*函数gethostbyname()返回对应于给定主机名的包含主机名字和地址等信息的hostent结构指针。*/
238.if(NULL==(he=gethostbyname(host))){
239.log_error_write(srv,__FILE__,__LINE__,
"sds","gethostbyname failed:",
h_errno,host);
240.return-1;
241.}
242.if(he->h_addrtype!=AF_INET){
243.log_error_write(srv,__FILE__,__LINE__,"sd",
"addr-type!=AF_INET:",he->h_addrtype);
244.return-1;
245.}
246.if(he->h_length!=sizeof(struct in_addr)){
247.log_error_write(srv,__FILE__,__LINE__,"sd",
"addr-length!=sizeof(in_addr):",he->h_length);
248.return-1;
249.}
/*转存需要的值。*/
250.memcpy(&(srv_socket->addr.ipv4.sin_addr.s_addr),he->h_addr_list[0],
251.he->h_length);
252.}
253.srv_socket->addr.ipv4.sin_port=htons(port);
254.addr_len=sizeof(struct sockaddr_in);
255.break;
256.case AF_UNIX:
257.srv_socket->addr.un.sun_family=AF_UNIX;
258.strcpy(srv_socket->addr.un.sun_path,host);
259.#ifdef SUN_LEN
/*SUN_LEN宏定义在/usr/include/sys/un.h内:
///usr/include/sys/un.h
//Evaluate to actual length of thesockaddr_un'structure.
#define SUN_LEN(ptr)((size_t)(((struct sockaddr_un*)0)->sun_path)\
+strlen((ptr)->sun_path))
从定义内容可以看到,宏SUN_LEN用于计算一个sockaddr_un结构体(通过ptr指针指向)的长度大小,这个长度并不是为该结构体分配的字节空间的长度,注意其中路径名sun_path字段仅计算其中的非空格字符在内。
*/
260.addr_len=SUN_LEN(&srv_socket->addr.un);
261.#else
/*stevens says:*/
262.addr_len=strlen(host)+1+sizeof(srv_socket->addr.un.sun_family);
263.#endif
/*check if the socket exists and try to connect to it.*/
/*检测一下是否可以连接上,正常情况下这里当然是连接失败,但是如果连接上了则说明有其他进程或服务在使用本套接口,因此报错退出。函数connect()的第一个参数是由socket()函数返回的套接口描述符,第二个、第三个参数分别是一个指向套接口地址结构的指针和该结构的大小。值得注意的是,该套接口地址结构必须包含有服务器的IP地址和端口号。*/
264.if(-1!=(fd=connect(srv_socket->fd,(struct sockaddr*)&(srv_socket->addr),
265.addr_len))){
266.close(fd);
267.log_error_write(srv,__FILE__,__LINE__,"ss",
"server socket is still in use:",
host);
268.return-1;
269.}
/*connect failed*/
/*检查连接失败的原因。*/
270.switch(errno){
/*连接请求被服务器端拒绝,因此认为属于预想的正常情况,但是这里有个问题就是当监听套接口队列已满时也会设置ECONNREFUSED错误码。*/
271.case ECONNREFUSED:
272.unlink(host);/*删除先前某次运行生成的或已存在的路径名。*/
273.break;
274.case ENOENT:/*路径名不存在,属于我们想要的正常情况。*/
275.break;
276.default:/*其他错误情况都属于异常,报错返回。*/
277.log_error_write(srv,__FILE__,__LINE__,"sds",
"testing socket failed:",
host,strerror(errno));
278.return-1;
279.}
280.break;
281.default:
282.addr_len=0;
283.return-1;
284.}
/*命名接口,即进行绑定。函数bind()第一个参数是由socket()函数返回的套接口描述符,第二个参数是一个指向特定于协议的地址结构的指针,第三个参数是该结构的大小。对于TCP而言,该套接口地址结构可以只指定端口号和IP地址中的一个,也可以两者都指定还可以两者都不指定。不指定IP地址(即选择通配地址)或不指定端口(即设置为0),则由内核自动选择。*/
285.if(0!=bind(srv_socket->fd,(struct sockaddr*)&(srv_socket->addr),addr_len)){
286.switch(srv_socket->addr.plain.sa_family){/*bind()出错。*/
287.case AF_UNIX:/*UNIX域套接口命名失败。*/
288.log_error_write(srv,__FILE__,__LINE__,"sds",
"can't bind to socket:",
host,strerror(errno));
289.break;
290.default:/*IPv4和IPv6套接口命名失败。*/
291.log_error_write(srv,__FILE__,__LINE__,"ssds",
"can't bind to port:",
host,port,strerror(errno));
292.break;
293.}
294.return-1;
295.}
/*主动套接口转换为被动(监听)套接口。函数listen()第一个参数是由socket()函数返回的套接口描述符,第二个参数限定内核为该监听套接口接收客户端请求排队的最大数目。内核为给定监听套接口维护两个队列,一个为未完成连接队列,即是客户端和服务器端三次握手协议还未完成的;另一个为已完成连接队列,即存储那些已经完成三次握手协议的连接请求对应项。*/
296.if(-1==listen(srv_socket->fd,128*8)){
297.log_error_write(srv,__FILE__,__LINE__,"ss","listen failed:",strerror(errno));
298.return-1;
299.}
300.if(s->is_ssl){/*这里是对SSL的处理。*/
301.//……省略……
302.}else{
/*SO_ACCEPTFILTER是FreeBSD支持的一个选项SOL_SOCKET,称为“接收过滤器”(accept filter),其主要用来推迟函数accept()调用的返回,即只有当HTTP状态发生改变时(如一个HTTP请求到达),进程才从函数accept()阻塞中返回,因此延缓了对该连接进行处理的子进程需求,这样做的好处就是对于一定数量的子进程能处理更多的连接。另外由于accept()调用返回就表示有请求达到,所以使得子进程能更迅速地完成请求响应,减少上下文切换。FreeBSD支持的接收过滤器有两个,分别为"dataready"和"httpready"。"dataready"等待客户端传输来的第一个包(the first packet);"httpready"等待HTTP头域的结束。在Linux下和SO_ACCEPTFILTER同作用的选项为TCP_DEFER_ACCEPT,它也是推迟accept()调用返回直到有数据可读为止。
因此,这里设置SO_ACCEPTFILTER选项主要用于提高Lighttpd服务器性能。*/303.#ifdef SO_ACCEPTFILTER
/*
*FreeBSD accf_http filter
*
*/
304.memset(&afa,0,sizeof(afa));
305.strcpy(afa.af_name,"httpready");
306.if(setsockopt(srv_socket->fd,SOL_SOCKET,SO_ACCEPTFILTER,&afa,
307.sizeof(afa))<0){
308.if(errno!=ENOENT){
309.log_error_write(srv,__FILE__,__LINE__,"ss","can't set accept-filter
'httpready':",strerror(errno));
310.}
311.}
312.#endif
313.}
314.srv_socket->is_ssl=s->is_ssl;
315.srv_socket->fde_ndx=-1;
/*记录已经创建了的监听套接口描述符。*/
316.if(srv->srv_sockets.size==0){
317.srv->srv_sockets.size=4;
318.srv->srv_sockets.used=0;
319.srv->srv_sockets.ptr=malloc(srv->srv_sockets.size*sizeof(server_socket));
320.}else if(srv->srv_sockets.used==srv->srv_sockets.size){
321.srv->srv_sockets.size+=4;
322.srv->srv_sockets.ptr=realloc(srv->srv_sockets.ptr,
323.srv->srv_sockets.size*sizeof(server_socket));
324.}
325.srv->srv_sockets.ptr[srv->srv_sockets.used++]=srv_socket;
326.buffer_free(b);
327.return 0;
328.}

通过上面的分析可以知道,这三个函数主要就是完成监听套接口描述符的创建,它们的函数执行流程如图10-3所示。创建好的监听套接口描述符被加入到I/O多路复用事件处理器中对其上面发生的事件(即主要是客户端的连接请求事件)进行管理(这个过程已经在第8.3.2节有过详细的介绍),当它们发生可读事件后即调用相关代码进行事件处理,由前面的介绍可以知道处理这些事件的就是回调函数network_server_handle_fdevent(),这将是下一小节的内容。

图 10-3 监听套接口描述符的创建函数执行流程

2.创建连接套接口描述符

监听套接口描述符创建之后被加入到I/O多路复用事件处理器中等待事件发生,当有事件发生时相应的回调函数被执行。这部分代码已经在前面分析过,出现在源文件server.c内,如清单10-4所示。
       清单10-4 监听套接口描述符发生事件处理

//server.c
/*回调函数被调用,参数srv为server结构体变量,存储服务进程的大部分信息,context为server_socket结构体变量,保存该监听套接口的相关信息,revents表示发生的事件类型。回调函数handler在I/O复用监控管理事件注册函数network_register_fdevents()被赋值指向函数network_server_handle_fdevent()。*/
329.switch(r=(*handler)(srv,context,revents)){
330.case HANDLER_FINISHED:
331.case HANDLER_GO_ON:
332.case HANDLER_WAIT_FOR_EVENT:
333.case HANDLER_WAIT_FOR_FD:
334.break;
335.case HANDLER_ERROR:
336./*should never happen*/
337.SEGFAULT();
338.break;
339.default:
340.log_error_write(srv,__FILE__,__LINE__,"d",r);
341.break;
342.}
//network.c
343.handler_t network_server_handle_fdevent(void*s,void*context,int revents){
344.server*srv=(server*)s;
345.server_socket*srv_socket=(server_socket*)context;
346.connection*con;
347.int loops=0;
348.UNUSED(context);
/*首先要判断传入的event事件是否是FDEVENT_IN,也就是说,只可能在fd有可读数据的时候才触发该函数,其他的情况都是错误。*/
349.if(revents!=FDEVENT_IN){
350.log_error_write(srv,__FILE__,__LINE__,"sdd",
351."strange event for server socket",
352.srv_socket->fd,
353.revents);
354.return HANDLER_ERROR;
355.}
/*accept()s at most 100 connections directly
*
*we jump out after 100 to give the waiting connections a chance*/
/*接收客户端连接请求,每个监听套接口描述符一次接收请求数目最大为100,使得各监听套接口上的连接请求都得到及时处理。*/
356.for(loops=0;loops<100&&NULL!=(con=connection_accept(srv,srv_socket));
357.loops++){
358.handler_t r;
359.connection_state_machine(srv,con);
360.switch(r=plugins_call_handle_joblist(srv,con)){
361.case HANDLER_FINISHED:
362.case HANDLER_GO_ON:
363.break;
364.default:
365.log_error_write(srv,__FILE__,__LINE__,"d",r);
366.break;
367.}
368.}
369.return HANDLER_GO_ON;
370.}

函数connection_accept()用来完成连接套接口描述符的创建,connection_state_machine()函数plugins_call_handle_joblist()函数用于对请求连接进行处理,这里暂时不讲解它们,先来看看函数connection_accept(),该函数定义在源文件connections.c内,返回一个connection结构体变量,connection结构体定义在头文件base.h内,包含有众多的字段,如清单10-5所示。

清单10-5 函数connection_accept

//base.h
371.typedef struct{
372.connection_state_t state;
373./*timestamps*/
374.time_t read_idle_ts;
375.time_t close_timeout_ts;
376.time_t write_request_ts;
377.time_t connection_start;
378.time_t request_start;
379.struct timeval start_tv;
380.size_t request_count;/*number of requests handled in this connection*/
381.size_t loops_per_request;/*to catch endless loops in a single request
382.*
383.*used by mod_rewrite,mod_fastcgi,...and others
384.*this is self-protection
385.*/
386.int fd;/*the FD for this connection*/
387.int fde_ndx;/*index for the fdevent-handler*/
388.int ndx;/*reverse mapping to server->connection[ndx]*/
389./*fd states*/
390.int is_readable;
391.int is_writable;
392.int keep_alive;/*only request.c can enable it,all other just disable*/
393.int file_started;
394.int file_finished;
395.chunkqueue*write_queue;/*a large queue for low-level write(HTTP response)[file,mem]*/
396.chunkqueue*read_queue;/*a small queue for low-level read(HTTP request)[mem]*/
397.chunkqueue*request_content_queue;/*takes request-content into
tempfile if necessary[tempfile,mem]*/
398.int traffic_limit_reached;
399.off_t bytes_written;/*used by mod_accesslog,mod_rrd*/
400.off_t bytes_written_cur_second;/*used by mod_accesslog,mod_rrd*/
401.off_t bytes_read;/*used by mod_accesslog,mod_rrd*/
402.off_t bytes_header;
403.int http_status;
404.sock_addr dst_addr;
405.buffer*dst_addr_buf;
406./*request*/
407.buffer*parse_request;
408.unsigned int parsed_response;/*bitfield which contains the important header-fields of the parsed response header*/
409.request request;
410.request_uri uri;
411.physical physical;
412.response response;
413.size_t header_len;
414.buffer*authed_user;
415.array*environment;/*used to pass lighttpd internal stuff to the FastCGI/CGI apps,setenv does that*/
416./*response*/
417.int got_response;
418.int in_joblist;
419.connection_type mode;
420.void**plugin_ctx;/*plugin connection specific config*/
421.specific_config conf;/*global connection specific config*/
422.cond_cache_t*cond_cache;
423.buffer*server_name;
424./*error-handler*/
425.buffer*error_handler;
426.int error_handler_saved_status;
427.int in_error_handler;
428.void*srv_socket;/*reference to the server-socket(typecast to
server_socket)*/
429.#ifdef USE_OPENSSL
430.SSL*ssl;
431.buffer*ssl_error_want_reuse_buffer;
432.#endif
433./*etag handling*/
434.etag_flags_t etag_flags;
435.int conditional_is_valid[COMP_LAST_ELEMENT];
436.}connection;
437.
438.typedef struct{
439.connection**ptr;
440.size_t size;
441.size_t used;
442.}connections;
443.
//connections.c
444.connection*connection_accept(server*srv,server_socket*srv_socket){
445./*accept everything*/
446./*search an empty place*/
447.int cnt;
448.sock_addr cnt_addr;
449.socklen_t cnt_len;
450./*accept it and register the fd*/
451./**
452.*check if we can still open a new connections
453.*see#1216
454.*/
455.if(srv->conns->used>=srv->max_conns){/*检查是否超过最大连接数目限制。*/
456.return NULL;
457.}
458.cnt_len=sizeof(cnt_addr);
/*调用系统函数accept()从内核维护的已完成连接队列队头取得一个连接并创建连接套接口描述符。第二个参数用来返回已连接的客户端进程的协议地址,而第三个参数cnt_len在函数调用前设置为结构体cnt_addr的大小,而在accept()返回时获得由内核填充的结构体cnt_addr内的确切字节数。*/
459.if(-1==(cnt=accept(srv_socket->fd,(struct sockaddr*)&cnt_addr,&cnt_len))){
460.switch(errno){
/*EAGAIN和EWOULDBLOCK表示非阻塞socket描述符上当前没有连接请求接收。*/
461.case EAGAIN:
462.#if EWOULDBLOCK!=EAGAIN
463.case EWOULDBLOCK:
464.#endif
465.case EINTR:/*函数accept()调用在返回一个有效连接之前被信号所中断。*/
466./*we were stopped_before_we had a connection*/
467.case ECONNABORTED:/*this is a FreeBSD thingy*//*连接被终断。*/
468./*we were stopped_after_we had a connection*/
469.break;
470.case EMFILE:/*进程可打开的文件数目达到最大值。*/
471./*out of fds*/
472.break;
473.default:
474.log_error_write(srv,__FILE__,__LINE__,"ssd","accept failed:",
475.strerror(errno),errno);
476.}
477.return NULL;
478.}else{
479.connection*con;
480.srv->cur_fds++;/*当前打开的文件描述符数目增1。*/
481./*ok,we have the connection,register it*/
482.#if 0
483.log_error_write(srv,__FILE__,__LINE__,"sd",
484."appected()",cnt);
485.#endif
486.srv->con_opened++;/*当前连接数目增1。*/
/*从srv->conns数组中取出一个空闲的connections结构体元素并经过初始化后来使用。*/
487.con=connections_get_new_connection(srv);
488.con->fd=cnt;
489.con->fde_ndx=-1;
490.#if 0
491.gettimeofday(&(con->start_tv),NULL);
492.#endif
/*将连接套接口描述符在事件管理监控器中进行注册。*/
493.fdevent_register(srv->ev,con->fd,connection_handle_fdevent,con);
/*将连接的状态机设置为开始状态。关于连接状态机将在下一小节中讲解。*/
494.connection_set_state(srv,con,CON_STATE_REQUEST_START);
495.con->connection_start=srv->cur_ts;/*记录时间戳。*/
496.con->dst_addr=cnt_addr;/*记录对端(即客户端)地址信息。*/
/*转存客户端IP地址信息。*/
497.buffer_copy_string(con->dst_addr_buf
inet_ntop_cache_get_ip(srv,&(con->dst_addr)));
498.con->srv_socket=srv_socket;
499.if(-1==(fdevent_fcntl_set(srv->ev,con->fd))){/*设置描述符相关属性。*/
500.log_error_write(srv,__FILE__,__LINE__,"ss",
"fcntl failed:",strerror(errno));
501.return NULL;
502.}
503.#ifdef USE_OPENSSL/*这里是对SSL的处理,暂时忽略。*/
504.//……省略……
505.#endif
506.return con;
507.}
508.}
509.
510.//inet_ntop_cache.c
511.const char*inet_ntop_cache_get_ip(server*srv,sock_addr*addr){
512.#ifdef HAVE_IPV6
513.size_t ndx=0,i;
/*在缓存中查找IP记录是否已经存在。*/
514.for(i=0;i<INET_NTOP_CACHE_MAX;i++){
515.if(srv->inet_ntop_cache[i].ts!=0){/*利用函数memcmp()进行比较。*/
516.if(srv->inet_ntop_cache[i].family==AF_INET6&&0==memcmp(
517.srv->inet_ntop_cache[i].addr.ipv6.s6_addr,addr->ipv6.sin6_addr.s6_addr,16)){
518./*IPv6 found in cache*/
519.break;
520.}else if(srv->inet_ntop_cache[i].family==AF_INET&&
/*直接利用等于号比较,读者应该注意到IPv4和IPv6对于这个相似字段的不同类型设置。s6_addr为数组,s_addr为uint32_t型。
//base.h
#ifdef HAVE_IPV6
typedef struct{
int family;
union{
struct in6_addr ipv6;
struct in_addr ipv4;
}addr;
char b2[INET6_ADDRSTRLEN+1];
time_t ts;
}inet_ntop_cache_type;
#endif
*/
521.srv->inet_ntop_cache[i].addr.ipv4.s_addr==
addr->ipv4.sin_addr.s_addr){
522./*IPv4 found in cache*/
523.break;
524.}
525.}
526.}
/*记录未找到。*/
527.if(i==INET_NTOP_CACHE_MAX){
528./*not found in cache*/
529.i=ndx;
/*函数inet_ntop()原型为:const char*inet_ntop(int af,const void*src,char*dst,socklen_t cnt);函数功能为将类型为af的网络地址结构src,转换成主机序的字符串形式,存放在长度为cnt的字符串中。该函数返回指向dst的指针,如果函数调用错误则返回值为NULL。*/
530.inet_ntop(addr->plain.sa_family,
addr->plain.sa_family==AF_INET6?
(const void*)&(addr->ipv6.sin6_addr):
(const void*)&(addr->ipv4.sin_addr),
srv->inet_ntop_cache[i].b2,INET6_ADDRSTRLEN);
531.srv->inet_ntop_cache[i].ts=srv->cur_ts;
532.srv->inet_ntop_cache[i].family=addr->plain.sa_family;
533.if(srv->inet_ntop_cache[i].family==AF_INET){
534.srv->inet_ntop_cache[i].addr.ipv4.s_addr=addr->ipv4.sin_addr.s_addr;
535.}else if(srv->inet_ntop_cache[i].family==AF_INET6){
536.memcpy(srv->inet_ntop_cache[i].addr.ipv6.s6_addr,
addr->ipv6.sin6_addr.s6_addr,16);
537.}
538.}
539.return srv->inet_ntop_cache[i].b2;
540.#else
541.UNUSED(srv);
/*函数inet_ntoa()功能和inet_ntop()一致,但是它不是线程安全函数,因此一般推荐使用函数inet_ntop()。*/
542.return inet_ntoa(addr->ipv4.sin_addr);
543.#endif
544.}

函数connection_accept()主要完成根据客户端请求信息创建连接套接口描述符并将它在事件管理监控器中进行注册,但是还并未没有添加关注事件,因为目前还并不知道是否需要该连接描述符继续进行监控以及监控何种事件,而且由于可能有请求服务的信息同时达到(根据前面提到过的TCP选项SO_ACCEPTFILTER和TCP_DEFER_ACCEPT),因此需要对这个连接请求执行响应处理函数connection_state_machine(),在该执行过程中统一根据连接状态转换管理进行事件注册监控以及其他处理。

图 10-4 连接套接口描述符的创建函数执行流程

 3.网络请求服务响应过程

Lighttpd服务器程序对接受的每个客户端连接(实际上是为每个connection结构体)维护一个有限状态机,根据连接的当前状态以及发生事件对连接进行不同的处理以及对connection结构体状态字段进行转换。这样做是很合理的,因为我们知道对于不同连接状态下发生的同一个事件,其处理过程不一定相同,有了状态机对当前连接状态进行标记和转换,使得Lighttpd程序结构更直观易懂且容易实现和扩展。
     Lighttpd为每个连接定义了11种状态,每个客户端与服务器的连接只可能处理这11种状态中的一种,如表10-1所示。

针对这11个状态之间的转换,Lighttpd开发者也给出了一个粗略的有限状态机转换图,如图10-5所示(读者可直接从Lighttpd官方在线Doc文档中下载到该图也可以自己参考附录三生成得到该图,另外该图中的数字标号是后来加的,便于下面的代码分析。),在后面我们将看到一个更为详细的状态转换图。
      完成图10-5所示的状态转换的大部分执行代码都在函数connection_state_machine()内,该函数定义在源文件connections.c内,对于我们理解Lighttpd应用程序如何有条理地处理客户端请求十分重要,先来简单分析一下这个函数源码,如清单10-6所示。

//connections.c
545.int connection_state_machine(server*srv,connection*con){
546.int done=0,r;
547.#ifdef USE_OPENSSL
548.server_socket*srv_sock=con->srv_socket;
549.#endif
550.if(srv->srvconf.log_state_handling){/*日志记录。*/
551.log_error_write(srv,__FILE__,__LINE__,"sds",
"state at start",
con->fd,
connection_get_state(con->state));
552.}
553.while(done==0){
554.size_t ostate=con->state;
555.int b;
556.switch(con->state){
557.case CON_STATE_REQUEST_START:/*transient*/
558.if(srv->srvconf.log_state_handling){
559.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
560.}
561.con->request_start=srv->cur_ts;
562.con->read_idle_ts=srv->cur_ts;
563.con->request_count++;
564.con->loops_per_request=0;
/*进行转换(2),在该状态下将调用函数connection_handle_read_state()读HTTP请求头数据,该函数执行成功则进入CON_STATE_REQUEST_END状态,如果出现HTTP错误将进入CON_STATE_HANDLE_REQUEST状态。*/
565.connection_set_state(srv,con,CON_STATE_READ);
566./*patch con->conf.is_ssl if the connection is a ssl-socket already*/
567.#ifdef USE_OPENSSL
568.con->conf.is_ssl=srv_sock->is_ssl;
569.#endif
570.break;
571.case CON_STATE_REQUEST_END:/*transient*/
572.if(srv->srvconf.log_state_handling){
573.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
574.}
/*该函数将解析客户端请求,返回1值表示有后续的POST数据到达,因此进入CON_STATE_READ_POST状态读取POST数据,否则返回0值。该函数将在后面详细讲解。*/
575.if(http_request_parse(srv,con)){
576./*we have to read some data from the POST request*/
/*进行转换(10)。*/
577.connection_set_state(srv,con,CON_STATE_READ_POST);
578.break;
/*跳出,因此不会进行下面的转换(4)。*/
579.}
/*否则进行转换(4)。*/
580.connection_set_state(srv,con,CON_STATE_HANDLE_REQUEST);
581.break;
582.case CON_STATE_HANDLE_REQUEST:
583./*
584.*the request is parsed
585.*decided what to do with the request
586.*/
587.if(srv->srvconf.log_state_handling){
588.log_error_write(srv,__FILE__,__LINE__,"sds",
589."state for fd",con->fd,connection_get_state(con->state));
590.}
/*客户端请求已经解析,开始对该请求进行处理。http_response_prepare()函数将调用插件对该请求做处理,根据插件的处理结果决定下一步怎么走。*/
591.switch(r=http_response_prepare(srv,con)){
/*响应请求处理的所有准备工作已经完成,进入到状态CON_STATE_RESPONSE_START开始响应客户端。//base.h
typedef enum{DIRECT,EXTERNAL}connection_type;
con->mode用于标记该请求是否需要由Lighttpd来处理,为DIRECT则由Lighttpd自己处理(这也是默认情况),否则表示由其他比较独立的插件处理,这些插件主要有mod_cgi、mod_fastcgi、mod_magnet、mod_proxy、mod_scgi和mod_ssi。
这些插件在第一次开始对某客户端请求进行处理前会先判断con->mode是否为DIRECT,如果不为DIRECT,即表示该请求已经有其他独立插件来处理,因此直接返回,否则将con->mode设置为本插件的id(即:con->mode=p->id;);在第一次处理之后的其他情况,插件就可以通过判断con->mode是否为本插件id(即con->mode==p->id?)来获知该请求是否归属本插件处理,如果con->mode不等于p->id则表示该请求不归属本插件处理,因此也就直接返回。
另外,如果某独立插件在处理客户端请求时失败,则也可以再将con->mode重置为DIRECT,然后返回,这样将错误扫尾工作抛给Lighttpd来帮忙统一处理。本章主要讨论DIRECT情况,由其他插件处理情况留作后面章节分析。*/
592.case HANDLER_FINISHED:
593.if(con->mode==DIRECT){
/*对于HTTP协议里个状态码的定义以及含义,请读者查阅rfc2616.txt文档的第10章。附录四给出了一个简单的状态码列表以帮助读者理解Lighttpd源码。*/
594.if(con->http_status==404||/*资料未找到或禁止访问。*/
595.con->http_status==403){
596./*404 error-handler*/
/*用户设置有默认的404或403错误页面,比如"server.error-handler-404="lenky404.htm"",变量"con->conf.error_handler"内存储的全局配置值,配置值"on->error_handler"是仅针对连接当前状态的条件配置值。*/
597.if(con->in_error_handler==0&&
598.(!buffer_is_empty(con->conf.error_handler)||
599.!buffer_is_empty(con->error_handler))){
600./*call error-handler*/
/*临时存储。*/
601.con->error_handler_saved_status=con->http_status;
602.con->http_status=0;
/*请求的地址重定向到自定义错误页面。*/
603.if(buffer_is_empty(con->error_handler)){
604.buffer_copy_string_buffer(con->request.uri,
con->conf.error_handler);
605.}else{
606.buffer_copy_string_buffer(con->request.uri,
con->error_handler);
607.}
608.buffer_reset(con->physical.path);
/*处理标记,防止死循环,如当自定义的错误页面配置路径不正确时。*/
609.con->in_error_handler=1;
/*重新进行处理。*/
610.connection_set_state(srv,con,
CON_STATE_HANDLE_REQUEST);
611.done=-1;
612.break;
613.}else if(con->in_error_handler){
/*复原状态码。*/
614./*error-handler is a 404*/
615.con->http_status=con->error_handler_saved_status;
616.}
617.}else if(con->in_error_handler){
618./*error-handler is back and has generated content*/
619./*if Status:was set,take it otherwise use 200*/
620.}
621.}
622.if(con->http_status==0)con->http_status=200;
623./*we have something to send,go on*/
624.connection_set_state(srv,con,CON_STATE_RESPONSE_START);
625.break;
/*加入到fd等待队列,等待子请求。*/
626.case HANDLER_WAIT_FOR_FD:
627.srv->want_fds++;
628.fdwaitqueue_append(srv,con);
629.connection_set_state(srv,con,CON_STATE_HANDLE_REQUEST);
630.break;
/*当需要重新检查请求结构(request-structure)时,插件会返回HANDLER_COMEBACK状态码,如在插件mod_rewrite插件中用于重写URI。*/
631.case HANDLER_COMEBACK:
632.done=-1;
/*当插件没有处理完请求并需要等待fd-event或者执行FDs时,需要返回HANDLER_WAIT_FOR_EVENT或HANDLER_WAIT_FOR_FD。*/
633.case HANDLER_WAIT_FOR_EVENT:
634./*come back here*/
635.connection_set_state(srv,con,ON_STATE_HANDLE_REQUEST);
636.break;
/*只有当发生致命错误时返回HANDLER_ERROR状态码,用于终止当前连接。*/
637.case HANDLER_ERROR:
638./*something went wrong*/
639.connection_set_state(srv,con,CON_STATE_ERROR);
640.break;
641.default:
642.log_error_write(srv,__FILE__,__LINE__,"sdd","unknown ret-value:",con->fd,r);
643.break;
644.}
645.break;
646.case CON_STATE_RESPONSE_START:/*开始响应准备。*/
647./*
648.*the decision is done
649.*-create the HTTP-Response-Header
650.*/
651.if(srv->srvconf.log_state_handling){
652.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
653.}
/*connection_handle_write_prepare()函数不算复杂,用于响应前的准备工作。*/
654.if(-1==connection_handle_write_prepare(srv,con)){
655.connection_set_state(srv,con,CON_STATE_ERROR);
656.break;
657.}
658.connection_set_state(srv,con,CON_STATE_WRITE);
659.break;
/*本次响应完成。*/
660.case CON_STATE_RESPONSE_END:/*transient*/
661./*log the request*/
662.if(srv->srvconf.log_state_handling){
663.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
664.}
/*插件的回调函数调用。*/
665.plugins_call_handle_request_done(srv,con);
666.srv->con_written++;
667.if(con->keep_alive){/*需要保持连接。*/
668.connection_set_state(srv,con,CON_STATE_REQUEST_START);
669.#if 0
670.con->request_start=srv->cur_ts;
671.con->read_idle_ts=srv->cur_ts;
672.#endif
673.}else{/*否则表示关闭连接,因此插件的回调函数调用。*/
674.switch(r=plugins_call_handle_connection_close(srv,con)){
675.case HANDLER_GO_ON:
676.case HANDLER_FINISHED:
677.break;
678.default:
679.log_error_write(srv,__FILE__,__LINE__,"sd",
"unhandling return value",r);
680.break;
681.}
682.#ifdef USE_OPENSSL
683.//……省略……
684.#endif
685.connection_close(srv,con);/*连接关闭。*/
686.srv->con_closed++;
687.}
688.connection_reset(srv,con);/*连接重置。*/
689.break;
690.case CON_STATE_CONNECT:
691.if(srv->srvconf.log_state_handling){
692.log_error_write(srv,__FILE__,__LINE__,"sds",
693."state for fd",con->fd,connection_get_state(con->state));
694.}
/*前面3.6节对chunk数据结构已经有过详细的分析,Lighttpd接收客户端请求的数据以及对客户端发送响应的数据就是通过chunk结构体来组织的。如果connection结构体处在CON_STATE_CONNECT状态则表示该Connection已经关闭,因此重置其数据存储chunkqueue以节省内存,等待下一个客户端连接到来。*/
695.chunkqueue_reset(con->read_queue);
696.con->request_count=0;
697.break;
698.case CON_STATE_CLOSE:
699.if(srv->srvconf.log_state_handling){
700.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
701.}
702.if(con->keep_alive){
/*FIONREAD用来确定套接口描述符con->fd内可以读取的数据量,该值通过整型参数b返回。*/
703.if(ioctl(con->fd,FIONREAD,&b)){
704.log_error_write(srv,__FILE__,__LINE__,"ss",
"ioctl()failed",strerror(errno));
705.}
706.if(b>0){/*有数据读取。*/
707.char buf[1024];
708.log_error_write(srv,__FILE__,__LINE__,"sdd",
"CLOSE-read()",con->fd,b);
/*读取数据,但是Lighttpd却并没有对读取的数据做进一步处理,因此这里的函数read()调用主要是为了清空套接口描述符con->fd内的缓存空间。*/
709.read(con->fd,buf,sizeof(buf));
710.}else{
711./*nothing to read*/
712.con->close_timeout_ts=0;
713.}
714.}else{
715.con->close_timeout_ts=0;
716.}
717.if(srv->cur_ts-con->close_timeout_ts>1){/*需要关闭连接?*/
718.connection_close(srv,con);
719.if(srv->srvconf.log_state_handling){
720.log_error_write(srv,__FILE__,__LINE__,"sd",
"connection closed for fd",con->fd);
721.}
722.}
723.break;
724.case CON_STATE_READ_POST:
725.case CON_STATE_READ:
726.if(srv->srvconf.log_state_handling){
727.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
728.}
729.connection_handle_read_state(srv,con);/*读取请求数据。*/
730.break;
731.case CON_STATE_WRITE:/*响应请求。*/
732.if(srv->srvconf.log_state_handling){
733.log_error_write(srv,__FILE__,__LINE__,"sds",
"state for fd",con->fd,connection_get_state(con->state));
734.}
735./*only try to write if we have something in the queue*/
736.if(!chunkqueue_is_empty(con->write_queue)){
737.#if 0
738.log_error_write(srv,__FILE__,__LINE__,"dsd",
739.con->fd,
740."packets to write:",
741.con->write_queue->used);
742.#endif
743.}
744.if(!chunkqueue_is_empty(con->write_queue)&&con->is_writable){
745.if(-1==connection_handle_write(srv,con)){
746.log_error_write(srv,__FILE__,__LINE__,"ds",
con->fd,
"handle write failed.");
747.connection_set_state(srv,con,CON_STATE_ERROR);
748.}else if(con->state==CON_STATE_WRITE){
749.con->write_request_ts=srv->cur_ts;
750.}
751.}
752.break;
753.case CON_STATE_ERROR:/*transient*/
754./*even if the connection was drop we still have to write it to the access log*/
if(con->http_status){/*插件链的回调函数调用。*/
755.plugins_call_handle_request_done(srv,con);
756.}
757.#ifdef USE_OPENSSL
758.//……省略……
759.#endif
760.switch(con->mode){
761.case DIRECT:
762.#if 0
763.log_error_write(srv,__FILE__,__LINE__,"sd",
764."emergency exit:direct",
765.con->fd);
766.#endif
767.break;
768.default:
/*插件链的回调函数调用。*/
769.switch(r=plugins_call_handle_connection_close(srv,con)){
770.case HANDLER_GO_ON:
771.case HANDLER_FINISHED:
772.break;
773.default:
774.log_error_write(srv,__FILE__,__LINE__,"");
775.break;
776.}
777.break;
778.}
779.connection_reset(srv,con);
780./*close the connection*/
/*关闭连接。函数shutdown()同函数close()类似,用来终止网络连接。但是函数shutdown()可以避免函数close()的两个限制:第一,函数close()将描述字的访问计数减1,仅在此计数为0时才关闭套接口,而函数shutdown()可直接激发TCP的正常连接终止序列,而不管访问计数;第二,函数close()终止了数据传送的读和写两个方向,而函数shutdown()终止的数据传送的方向可以自己选择,比如关闭读连接(SHUT_RD)、关闭写连接(SHUT_WR)、关闭读和写连接(SHUT_RDWR)。函数shutdown()执行成功则返回0,失败则返回-1,错误代号存于errno中。这里是关闭服务段写,但客户端如果发有数据过来仍能读取,从前面的代码(在CON_STATE_CLOSE状态下的执行代码)也可以看到Lighttpd读取了客户端发送过来的数据,但是将读到的数据直接丢弃了。*/
781.if((con->keep_alive==1)&&
782.(0==shutdown(con->fd,SHUT_WR))){
783.con->close_timeout_ts=srv->cur_ts;
784.connection_set_state(srv,con,CON_STATE_CLOSE);
785.if(srv->srvconf.log_state_handling){
786.log_error_write(srv,__FILE__,__LINE__,"sd",
"shutdown for fd",con->fd);
787.}
788.}else{
789.connection_close(srv,con);
790.}
791.con->keep_alive=0;
792.srv->con_closed++;
793.break;
794.default:
795.log_error_write(srv,__FILE__,__LINE__,"sdd",
"unknown state:",con->fd,con->state);
796.break;
797.}
798.if(done==-1){/*需要继续处理。*/
799.done=0;
800.}else if(ostate==con->state){/*状态未变,不需要继续处理。*/
801.done=1;
802.}
803.}
804.if(srv->srvconf.log_state_handling){
805.log_error_write(srv,__FILE__,__LINE__,"sds",
"state at exit:",
con->fd,
connection_get_state(con->state));
806.}
807.switch(con->state){
/*在这三种状态下,有可能会收到客户端的请求数据,因此在socket描述符上加上可读事件监控。*/
808.case CON_STATE_READ_POST:
809.case CON_STATE_READ:
810.case CON_STATE_CLOSE:
811.fdevent_event_add(srv->ev,&(con->fde_ndx),con->fd,FDEVENT_IN);
812.break;
813.case CON_STATE_WRITE:
814./*request write-fdevent only if we really need it
815.*-if we have data to write
816.*-if the socket is not writable yet
817.*/
/*有数据要发送、当前不可写并且未达到传输速度限制上限,此时需要监控socket描述符何时可写,即发生可写事件时立即获得通知,便可以将待发数据发送出去。*/
818.if(!chunkqueue_is_empty(con->write_queue)&&
819.(con->is_writable==0)&&
820.(con->traffic_limit_reached==0)){
821.fdevent_event_add(srv->ev,&(con->fde_ndx),con->fd,DEVENT_OUT);
822.}else{
/*无数据发送或socket描述符当前本来就是可写状态或者达到传输速度限制上限,此时删除监控socket描述符上可写事件。*/
823.fdevent_event_del(srv->ev,&(con->fde_ndx),con->fd);
824.}
825.break;
/*其他情况需要删除socket描述符上监控事件。*/
826.default:
827.fdevent_event_del(srv->ev,&(con->fde_ndx),con->fd);
828.break;
829.}
830.return 0;
831.}

图 10-5 网络连接11种状态转换图
      清单10-6并没有包括Lighttpd有限状态机转换的所有代码,从图10-5中也可以清楚地看到还涉及另外几个函数,因此在这里读者理解起来仍然会有困难。下面我将结合图10-5所示的详细有限状态转换情况并按照两种常见的客户端访问Web服务器连接请求(GET和POST)流程对Lighttpd函数源码进行分析,这样根据实例分析源码,便于读者理解而不至于显得空洞。

图 10-6 网络连接11种状态转换函数调用流程

在做实例分析前,我们需要先做一些准备工作,首先在系统(这里使用的是Ubuntu.8.10)下有编译运行Lighttpd的环境便于我们修改编译运行Lighttpd应用程序;其次,安装抓包工具,这里我将使用前面章节提过的Wireshark(Version 1.0.3),这样我们可以清楚地看到服务器和客户端之间交互了什么数据。

下面的实例分析中将以前面7.2.1节的Lighttpd配置信息范例为Lighttpd实际配置信息来运行Lighttpd服务器。由于Lighttpd主目录绑定的HTTP端口为3000,因此利用Wireshark抓HTTP信息包前还需做下设置:运行Wireshark(as root),从菜单Edit->Preferences...(Shift+Ctrl+P)打开参数设置选项页,再选项页左边选择Protocls->HTTP,对右边显示的TCP Ports进行设置,即将端口号3000加入。这样利用Wireshark就可以抓取到并显示我们访问Lighttpd应用程序的HTTP交互数据包,否则从Wireshark显示里无法看到这些信息,当然如果读者设置的是80、8080等常用Web端口,由于Wireshark已经默认加入了,这步设置就不需要做了。图10-7是设置HTTP端口的Wireshark界面。

图 10-7 Wireshark抓包端口设置

GET请求的Web服务响应过程
在站点主目录"/home/lenky/source/lighttpd-1.4.20/lenky/"下有静态页面index.htm,该页面内容如清单10-7所示。

//index.htm
832.<html >
833.<head>
834.<meta http-equiv="Content-Type"content="text/html;charset=gb2312"/>
835.<title>Lighttpd GET/POST Test</title>
836.</head>
837.<body>
838.Hello world!
839.<hr color="blue">
840.<form name="form1"method="post"action="">
841.Name:
842.<input name="name"type="text"id="name">
843.<br>
844.<input type="submit"name="Submit"value="post">
845.</form>
846.</body>
847.</html>

运行Lighttpd和Wireshark,通过浏览器Firefox访问"http://127.0.0.1:3000/index.htm"页面,获得GET请求和响应数据,如图10-8、图10-9所示。

图 10-8 GET请求头信息

图 10-9 GET响应信息

当我们在浏览器Firefox地址栏输入"http://127.0.0.1:3000/index.htm"之后回车,Firefox就代理我们向Lighttpd服务器发起一个GET请求连接,Lighttpd应用程序接收(代码356行)这个GET请求并新建一个connection结构体变量con来管理这个连接,此时的con状态为CON_STATE_REQUEST_START(代码494行)且有数据可读(即con->is_readable=1,这由代码487行的函数链connections_get_new_connection()->connection_reset()设置这个值,这两个函数源码未列出)。

Lighttpd接收客户端请求后开始对这个连接进行处理(代码359行),由于初始为状态CON_STATE_REQUEST_START,因此在函数connection_state_machine()内首先执行的case为该状态下的处理(代码557行),做简单记录处理(包括访问请求时间、读空闲时间以及该连接的请求次数)后转换为CON_STATE_READ状态(代码565行),紧接着进入下一次while循环(状态CON_STATE_REQUEST_START的处理不会修改while判断条件,因此while循环继续执行)。CON_STATE_READ状态的处理看似很简单,就只有一个调用函数connection_handle_read_state(),可以猜到该函数肯定要读取客户端发送过来的请求信息(如GET请求的URI地址等,如图10-8所示)。我们先来解析这个以及相关函数的源码(清单10-8):

清单10-8 读取请求消息头

//connections.c
/**
*handle all header and content read
*we get called by the state-engine and by the fdevent-handler
*/
848.int connection_handle_read_state(server*srv,connection*con){
849.connection_state_t ostate=con->state;
850.chunk*c,*last_chunk;
851.off_t last_offset;
852.chunkqueue*cq=con->read_queue;
853.chunkqueue*dst_cq=con->request_content_queue;
854.int is_closed=0;/*the connection got closed,if we don't have a complete header,->error*/
/*有数据可读?调用函数connection_handle_read()读取客户端请求数据。*/
855.if(con->is_readable){
856.con->read_idle_ts=srv->cur_ts;
857.switch(connection_handle_read(srv,con)){
858.case-1:
859.return-1;
860.case-2:
861.is_closed=1;
862.break;
863.default:
864.break;
865.}
866.}
867.
/*the last chunk might be empty*/
/*阅读函数connection_handle_read()源码知道,该函数会向con->read_queue链末尾新添加一个chunk块(代码1059行),然后从socket套接口描述符内读取数据存储到该新增chunk块内,但是也有可能没有读到数据而导致该新增chunk块为空闲块,因此这里作整理操作,将没有被使用(即c->mem->used==0)的chunk块都归入到unused chunk链中。*/
868.for(c=cq->first;c;){
869.if(cq->first==c&&c->mem->used==0){
870./*the first node is empty*/
871./*...and it is empty,move it to unused*/
/*第一块为空,则移到未使用链。*/
872.cq->first=c->next;
873.if(cq->first==NULL)cq->last=NULL;
874.c->next=cq->unused;
875.cq->unused=c;
876.cq->unused_chunks++;
877.c=cq->first;
878.}else if(c->next&&c->next->mem->used==0){
/*下一块为空,移动到未使用链。*/
879.chunk*fc;
880./*next node is the last one*/
881./*...and it is empty,move it to unused*/
882.fc=c->next;
883.c->next=fc->next;
884.fc->next=cq->unused;
885.cq->unused=fc;
886.cq->unused_chunks++;
887./*the last node was empty*/
888.if(c->next==NULL){
889.cq->last=c;
890.}
891.c=c->next;
892.}else{
893.c=c->next;
894.}
895.}
/*we might have got several packets at once
*/
896.switch(ostate){
897.case CON_STATE_READ:
/*if there is a\r\n\r\n in the chunkqueue
*scan the chunk-queue twice
*1.to find the\r\n\r\n
*2.to copy the header-packet
*/
/*在开始下面的源码分析之前,我们先来了解一下HTTP的消息机制。HTTP消息包括从客户到服务器的请求和从服务器到客户的响应两种消息组成(RFC 2616第4节)。请求消息和响应消息都利用RFC 822定义的常用消息的格式来传输实体(消息的负载)。这种常用消息格式就是由开始行(start-line)、零个或多个头域(经常被称作“头”)、一个指示头域结束的空行(一个仅包含CRLF的“空”行)以及一个可有可无的消息主体(message-body)(与此相对,下面的讲解中,我们将开始行、头域和结束空行称为请求消息头)组成。其BNF表示为:
generic-message=start-line
*(message-header CRLF)
CRLF
[message-body]
start-line=Request-Line|Status-Line
message-header=field-name":"[field-value]
field-name=token
field-value=*(field-content|LWS)
field-content=<the OCTETs making up the field-value
and consisting of either*TEXT or combinations
of token,separators,and quoted-string>
OCTET=<any 8-bit sequence of data>
CHAR=<any US-ASCII character(octets 0-127)>
UPALPHA=<any US-ASCII uppercase letter"A".."Z">
LOALPHA=<any US-ASCII lowercase letter"a".."z">
ALPHA=UPALPHA|LOALPHA
DIGIT=<any US-ASCII digit"0".."9">
CTL=<any US-ASCII control character
(octets 0-31)and DEL(127)>
CR=<US-ASCII CR,carriage return(13)>
LF=<US-ASCII LF,linefeed(10)>
SP=<US-ASCII SP,space(32)>
HT=<US-ASCII HT,horizontal-tab(9)>
<">=<US-ASCII double-quote mark(34)>
CRLF=CR LF
LWS=[CRLF]1*(SP|HT)
TEXT=<any OCTET except CTLs,
but including LWS>
HEX="A"|"B"|"C"|"D"|"E"|"F"
|"a"|"b"|"c"|"d"|"e"|"f"|DIGIT
token=1*<any CHAR except CTLs or separators>
separators="("|")"|"<"|">"|"@"
|","|";"|":"|"\"|<">
|"/"|"["|"]"|"?"|"="
|"{"|"}"|SP|HT
comment="("*(ctext|quoted-pair|comment)")"
ctext=<any TEXT excluding"("and")">
quoted-string=(<">*(qdtext|quoted-pair)<">)
qdtext=<any TEXT except<">>
quoted-pair="\"CHAR
与消息主体(message-body)概念容易混淆的另外一个概念为实体主体(entity-body)(后面经常提到实体主体),实体主体可以看成为消息主体的一种特例,即消息主体仅仅当传输编码应用于传输译码头域上时才和实体主体不同,其他情况下消息主体和实体主体都完全相同(RFC 2616第4.3节)。
BNF:
message-body=entity-body
|<entity-body encoded as per Transfer-Encoding>
HTTP头域包括常用头(RFC 2616第4.5节),请求头(RFC 2616第5.3节)、响应头(RFC 2616第6.2节)和实体头(RFC 2616第7.1节)域。这些头域都遵循RFC 822第3.1节中给出的同一个常规的格式,即每一个头域由一个名字(域名)跟随一个":"和域值构成,域名是大小写不敏感的,域值前面可能有任意数量的LWS(线性空白)的,但SP(空格)是首选的。另外,头域能被延升多行,通过在这些行前面加一些SP或HT(水平制表符),但是为了保证和所有应用程序平稳交换,一般不建议这么做。HTTP消息的消息主体用来承载请求和响应的实体主体。
对于请求消息的具体BNF表示为:
Request=Request-Line;Section 5.1
*((general-header;Section 4.5
|request-header;Section 5.3
|entity-header)CRLF);Section 7.1
CRLF
[message-body];Section 4.3
Request-Line=Method SP Request-URI SP HTTP-Version CRLF
Method="OPTIONS";Section 9.2
|"GET";Section 9.3
|"HEAD";Section 9.4
|"POST";Section 9.5
|"PUT";Section 9.6
|"DELETE";Section 9.7
|"TRACE";Section 9.8
|"CONNECT";Section 9.9
|extension-method
extension-method=token
Request-URI="*"|absoluteURI|abs_path|authority

对照图10-8中Firefox向Lighttpd服务器发送的GET请求数据,可以看到其开始行的Method为"GET",Request-URI采用abs_path为"/index.htm"(对于Request-URI请读者参考RFC 2616第5.1.2节,关于abs_path、absoluteURI和authority请读者参考RFC 2396第3节),HTTP-Version为"HTTP/1.1"。开始行之后是头域,接着一个CRLF空行表示头域结束,没有消息主体。

下面代码用于查找请求头域结束,即查找"\r\n\r\n"字符串。
*/
898.last_chunk=NULL;
899.last_offset=0;
/*有可能客户端的请求头信息数据分多个包(packet)到达Lighttpd,此时状态CON_STATE_READ状态将持续多次(图10-5内的状态切换箭头b所示情况),每次读取的数据存在con->read_queue链的末尾chunk块内,因此此处循环处理每个chunk块。*/
900.for(c=cq->first;!last_chunk&&c;c=c->next){
901.buffer b;
902.size_t i;
903.b.ptr=c->mem->ptr+c->offset;
904.b.used=c->mem->used-c->offset;
905.for(i=0;!last_chunk&&i<b.used;i++){
/*对每个接收到的请求数据字符逐个查找。*/
906.char ch=b.ptr[i];
907.size_t have_chars=0;
908.switch(ch){
909.case'\r':
/*we have to do a 4 char lookup*/
/*查找到字符'\r'并且该chunk块内待查找字符多余4个,则直接利用字符串比较函数strncmp()判断是否已经找到请求头域结束字符串"\r\n\r\n",函数strncmp()的第三个参数为需要比较字符的个数。*/
910.have_chars=b.used-i-1;
911.if(have_chars>=4){
912./*all chars are in this buffer*/
913.if(0==strncmp(b.ptr+i,"\r\n\r\n",4)){
914./*found*/
/*找到请求头域结束字符串"\r\n\r\n",记录最后一个chunk块和对应的"\r\n\r\n"最后一个字符所在下标。*/
915.last_chunk=c;
916.last_offset=i+4;
917.break;
918.}
/*查找到字符'\r'但是该chunk块内待查找字符不足4个,这种情况下,我们待找的请求头域结束字符串"\r\n\r\n"有可能被分割存在了两个chunk块内,即前一个chunk块内保存字符串"\r\n\r\n"的前面几个字符,接着的后一个chunk块保存字符串"\r\n\r\n"的后面几个字符,因此需要进行分别比较。*/
919.}else{
/*后一个chunk块。*/
920.chunk*lookahead_chunk=c->next;
/*可能保存后一个chunk块中的字符串"\r\n\r\n"内字符个数。*/
921.size_t missing_chars;
/*looks like the following chars are not in the same chunk*/
922.missing_chars=4-have_chars;
923.if(lookahead_chunk&&lookahead_chunk->type==
MEM_CHUNK){
/*后一个chunk块存在并且类型正确的情况下执行。*/
/*is the chunk long enough to contain the other chars?*/
924.if(lookahead_chunk->mem->used>missing_chars){
/*将字符串"\r\n\r\n"拆开,分别与前一个chunk块内剩余的字符和后一个chunk块内前面几个字符进行比较。如果比较成功则表示找到请求头域结束字符串,记录最后一个chunk块以及字符下标。对于第二个函数strncmp()比较调用的第二个参数,读者可以这样看,将"\r\n\r\n"看成一个指向字符'\r'的字符指针,再加上have_chars表示将该字符指针后移到第二次比较的正确位置,这样才符合我们的真实意图。这样的用法很巧妙,读者可以看看实际运行语句"printf("%s\n","123456"+3);"得到的结果是什么?(得到的打印结果为“456”)。*/
925.if(0==strncmp(b.ptr+i,"\r\n\r\n",have_chars)&&
0==strncmp(lookahead_chunk->mem->ptr,
"\r\n\r\n"+have_chars,missing_chars)){
926.last_chunk=lookahead_chunk;
927.last_offset=missing_chars;
928.break;
929.}
930.}else{
931./*a splited\r\n*/
932.break;
933.}
934.}
935.}
936.break;
937.}
938.}
939.}
/*found*/
/*只有当找到了请求头域结束字符串"\r\n\r\n"时,变量last_chunk才会由初始值NULL赋值转变为指向某个chunk块。这里将HTTP请求头数据(注意:不包括消息主体)从con->read_queue复制到con->request.request(buffer类型变量,参考清单10-9)。*/
940.if(last_chunk){
941.buffer_reset(con->request.request);
942.for(c=cq->first;c;c=c->next){
943.buffer b;
944.b.ptr=c->mem->ptr+c->offset;/*确定复制数据的区域。*/
945.b.used=c->mem->used-c->offset;
/*如果是最后一个chunk块,确保只复制到请求头域结束字符串"\r\n\r\n"数据为止。*/
946.if(c==last_chunk){
947.b.used=last_offset+1;
948.}/*执行拷贝。*/
949.buffer_append_string_buffer(con->request.request,&b);
950.if(c==last_chunk){
951.c->offset+=last_offset;
952.break;/*复制请求头域数据结束,跳出。*/
953.}else{
954./*the whole packet was copied*/
955.c->offset=c->mem->used-1;
956.}
957.}/*状态切换。*/
958.connection_set_state(srv,con,CON_STATE_REQUEST_END);
959.}else if(chunkqueue_length(cq)>64*1024){
/*未找到情况:
1)如果没有查找到"\r\n\r\n"并且缓冲区数据长度大于64*1024,也就是64KB,那么就返回414错误,也就是说,对于lighttpd而言,一般的HTTP请求不能超过64KB。根据RFC 2616第10.4.15节,414状态码表示请求URI太长(Request-URI Too Long),服务器拒绝为此请求服务,因为此请求URI太长了以至于服务器不能理解。这种情况一般是很少发生的,只有当客户端把POST请求不合适地转换为带有大量查询信息的GET请求时可能导致该错误状态码。切换连接状态为CON_STATE_HANDLE_REQUEST。
2)如果没有查找到"\r\n\r\n"并且缓冲区数据长度又没有超过64*1024,那么此时状态依旧是CON_STATE_READ(图1 0-5内的状态切换箭头b所示情况),以便下一次继续读取请求数据到con->read_queue链中再进行处理,这与代码900行上的注释相照应。*/
960.log_error_write(srv,__FILE__,__LINE__,"s",
"oversized request-header->sending Status 414");
961.con->http_status=414;/*Request-URI too large*/
962.con->keep_alive=0;
963.connection_set_state(srv,con,CON_STATE_HANDLE_REQUEST);
964.}
965.break;
966.case CON_STATE_READ_POST:
967.for(c=cq->first;c&&(dst_cq->bytes_in!=
(off_t)con->request.content_length);c=c->next){
968.off_t weWant,weHave,toRead;
/*数据读取函数connection_handle_read()读取的数据存放在con->read_queue内,con->request.content_length指定整个实体主体数据的大小(通过分析Content-Length头域得知,代码1520行),也就是我们一共需要读取的数据长度,该for循环做的事情是将保存在con->read_queue链各chunk块内的数据逐个转存到con->request_content_queue链内(即dst_cq),字段dst_cq->bytes_in内存储当前已经被转移的字节数。
我们需要读取的剩余数据字节数。*/
969.weWant=con->request.content_length-dst_cq->bytes_in;
970.assert(c->mem->used);
/*con->read_queue链中当前被处理chunk块内包含的数据量。*/
971.weHave=c->mem->used-c->offset-1;
/*从该块内可以实际可以读取的数据量,两种情况:1)该块内已有数据少于我们需要的数据(weHave<weWant)时则读完整个块(weHave),这很好理解;2)该块内已有数据大于我们需要的数据(weHave>weWant)时则只读取我们需要的数据(weWant),因为多余的数据极有可能是下次请求的请求头数据(如在一连续客户端请求情况下,这在我们访问网页时很频繁,因为打开网页除了要请求该网页本身文件外,还有该网页上的图片、JS、CSS、FLASH等文件,这么多文件的连续请求信息将前后陆续发送到Lighttpd服务器端);*/
972.toRead=weHave>weWant?weWant:weHave;
/*the new way,copy everything into a chunkqueue whcih might use tempfiles*/
/*大量的POST数据时则需要读取到临时文件。*/
973.if(con->request.content_length>64*1024){
974.chunk*dst_c=NULL;
/*copy everything to max 1Mb sized tempfiles*/
/*
*if the last chunk is
*-smaller than 1Mb(size<1Mb)
*-not read yet(offset==0)
*->append to it
*otherwise
*->create a new chunk
*
**/
/*直接利用最后一个块,如果其满足这些条件。*/
975.if(dst_cq->last&&
976.dst_cq->last->type==FILE_CHUNK&&
977.dst_cq->last->file.is_temp&&
978.dst_cq->last->offset==0){
979./*ok,take the last chunk for our job*/
/*最后这块对应的文件大小未超过1MB,则可以继续添加数据。*/
980.if(dst_cq->last->file.length<1*1024*1024){
981.dst_c=dst_cq->last;
982.if(dst_c->file.fd==-1){
/*this should not happen as we cache the fd,but you never know*/
983.dst_c->file.fd=open(dst_c->file.name->ptr,
O_WRONLY|O_APPEND);
984.}
/*最后这块对应的文件大小已经超过1MB,则重新建立一个文件类型的chunk块。*/
985.}else{
986./*the chunk is too large now,close it*/
987.dst_c=dst_cq->last;
988.if(dst_c->file.fd!=-1){
989.close(dst_c->file.fd);
990.dst_c->file.fd=-1;
991.}
992.dst_c=chunkqueue_get_append_tempfile(dst_cq);
993.}
*直接获取一个新的文件类型chunk块。*/
994.}else{
995.dst_c=chunkqueue_get_append_tempfile(dst_cq);
996.}
997./*we have a chunk,let's write to it*/
998.if(dst_c->file.fd==-1){/*文件出错。*/
/*we don't have file to write to,
*EACCES might be one reason.
*Instead of sending 500 we send 413 and say the request
is too large
**/
999.log_error_write(srv,__FILE__,__LINE__,"sbs",
"denying upload as opening to temp-file for upload
failed:",dst_c->file.name,strerror(errno));
1000.con->http_status=413;/*Request-Entity too large*/
1001.con->keep_alive=0;
1002.connection_set_state(srv,con,
CON_STATE_HANDLE_REQUEST);
1003.break;
1004.}
1005.if(toRead!=write(dst_c->file.fd,c->mem->ptr+c->offset,
toRead)){
/*因为一些原因导致文件读写错。*/
1006./*write failed for some reason...disk full?*/
1007.log_error_write(srv,__FILE__,__LINE__,"sbs",
"denying upload as writing to file failed:",
dst_c->file.name,strerror(errno));
1008.con->http_status=413;/*Request-Entity too large*/
1009.con->keep_alive=0;
1010.connection_set_state(srv,con,
CON_STATE_HANDLE_REQUEST);
1011.close(dst_c->file.fd);
1012.dst_c->file.fd=-1;
1013.break;
1014.}
1015.dst_c->file.length+=toRead;
1016.if(dst_cq->bytes_in+toRead==
(off_t)con->request.content_length){
1017./*we read everything,close the chunk*/
/*读完所有需要的数据。*/
1018.close(dst_c->file.fd);
1019.dst_c->file.fd=-1;
1020.}
1021.}else{/*少量数据直接保存到mem块中。*/
1022.buffer*b;
1023.b=chunkqueue_get_append_buffer(dst_cq);
1024.buffer_copy_string_len(b,c->mem->ptr+c->offset,toRead);
1025.}
1026.c->offset+=toRead;/*更新记录。*/
1027.dst_cq->bytes_in+=toRead;
1028.}
/*Content is ready*/
/*POST数据已经全部读完,切换到CON_STATE_HANDLE_REQUEST状态(图10-5内的状态切换箭头l所示情况)开始请求处理,否则状态继续保持在CON_STATE_READ_POST(图10-5内的状态切换箭头m所示情况)。*/
1029.if(dst_cq->bytes_in==(off_t)con->request.content_length){
1030.connection_set_state(srv,con,CON_STATE_HANDLE_REQUEST);
1031.}
1032.break;
1033.default:break;
1034.}
/*the connection got closed and we didn't got enough data to leave one of
the READ states
*the only way is to leave here*/
/*连接被对方关闭并且连接状态未切换,则将状态换为CON_STATE_ERROR。*/
1035.if(is_closed&&ostate==con->state){
1036.connection_set_state(srv,con,CON_STATE_ERROR);
1037.}
/*从链表头开始清理已经使用完毕的chunk,释放内存。*/
1038.chunkqueue_remove_finished_chunks(cq);
1039.return 0;
1040.}
/*函数connection_handle_read()从连接con对应的socket套接口描述符内读取数据到con->read_queue链中,正常执行返回0,返回-2表示客户端关闭连接,其他出错返回-1。*/
1041.static int connection_handle_read(server*srv,connection*con){
1042.int len;
1043.buffer*b;
1044.int toread;
1045.if(con->conf.is_ssl){/*SSL情况调用特定SSL读取函数。*/
1046.return connection_handle_read_ssl(srv,con);
1047.}
1048.#if defined(__WIN32)
1049.b=chunkqueue_get_append_buffer(con->read_queue);
1050.buffer_prepare_copy(b,4*1024);
1051.len=recv(con->fd,b->ptr,b->size-1,0);
1052.#else
/*利用FIONREAD获取当前socket套接口描述符内可读的字节数目。*/
1053.if(ioctl(con->fd,FIONREAD,&toread)){
1054.log_error_write(srv,__FILE__,__LINE__,"sd",
1055."unexpected end-of-file:",
1056.con->fd);
1057.return-1;
1058.}
/*获取一个mem类型的chunk并添加到con->read_queue尾部,返回用于保存数据的buffer结构体字段。*/
1059.b=chunkqueue_get_append_buffer(con->read_queue);
/*为该buffer结构体字段分配数据存储空间,大小为可读数据字节长度+1,接着调用系统函数read()读取数据。*/
1060.buffer_prepare_copy(b,toread+1);
1061.len=read(con->fd,b->ptr,b->size-1);
1062.#endif
1063.if(len<0){/*读取出错。*/
1064.con->is_readable=0;/*将读取状态设置为不可读。*/
1065.if(errno==EAGAIN)return 0;
1066.if(errno==EINTR){
/*read()被信号中断,则说明应该是有数据可读,因此将读取状态仍设置为可读。*/
1067./*we have been interrupted before we could read*/
1068.con->is_readable=1;
1069.return 0;
1070.}
/*这种情况下的ECONNRESET表示与客户端的连接被出乎意料地断开,如客户端浏览器在发送请求数据时异常崩溃、网络中断等导致服务器与客户端之间的连接被强行断开。如果不是ECONNRESET则表示read()出错,记录日志。*/
1071.if(errno!=ECONNRESET){
1072./*expected for keep-alive*/
1073.log_error_write(srv,__FILE__,__LINE__,"ssd","connection
closed-read failed:",strerror(errno),errno);
1074.}
1075.connection_set_state(srv,con,CON_STATE_ERROR);
1076.return-1;
1077.}else if(len==0){
1078.con->is_readable=0;
/*客户端关闭连接。*/
1079./*the other end close the connection->KEEP-ALIVE*/
1080./*pipelining*/
1081.return-2;
1082.}else if((size_t)len<b->size-1){
1083./*we got less then expected,wait for the next fd-event*/
/*读得数据量比预期少,等待下一次fd事件。*/
1084.con->is_readable=0;
1085.}
1086.b->used=len;
1087.b->ptr[b->used++]='\0';
1088.con->bytes_read+=len;
1089.#if 0
1090.dump_packet(b->ptr,len);
1091.#endif
1092.return 0;
1093.}

request和response数据结构定义如清单10-9所示。
清单10-9 request、response数据结构定义

//base.h
1094.typedef struct{
1095./**HEADER*/
1096./*the request-line*/
1097.buffer*request;
1098.buffer*uri;
1099.buffer*orig_uri;
1100.http_method_t http_method;
1101.http_version_t http_version;
1102.buffer*request_line;
1103./*strings to the header*/
1104.buffer*http_host;/*not alloced*/
1105.const char*http_range;
1106.const char*http_content_type;
1107.const char*http_if_modified_since;
1108.const char*http_if_none_match;
1109.array*headers;
1110./*CONTENT*/
1111.size_t content_length;/*returned by strtoul()*/
1112./*internal representation*/
1113.int accept_encoding;
1114./*internal*/
1115.buffer*pathinfo;
1116.}request;
1117.
1118.typedef struct{
1119.off_t content_length;
1120.int keep_alive;/*used by the subrequests in proxy,cgi and
fcgi to say the subrequest was keep-alive or not*/
1121.array*headers;
1122.enum{
1123.HTTP_TRANSFER_ENCODING_IDENTITY,
HTTP_TRANSFER_ENCODING_CHUNKED
1124.}transfer_encoding;
1125.}response;

从清单10-8的分析可以看到在CON_STATE_READ状态下执行的函数connection_handle_read_state()用于读取客户端发送过来的请求信息到con->read_queue的chunk链内,并且在读取完请求头信息后(通过查找请求头域结束字符串"\r\n\r\n"来判断是否已经读取完,可能一次不能读取完请求头信息,则需要多次读取,即状态依旧处在CON_STATE_READ,也有可能还读到一些消息主体信息,但是Lighttpd在这里并不关心是否读到消息主体信息)就将请求头信息复制到request.request内,同时将状态切换到CON_STATE_REQUEST_END (图10-5内的状态切换箭头c所示情况),当然也可能出现异常情况,如图10-10所示。我们这里先沿着主线走,对于异常情况的处理,在讲完整个主线后读者自然能够自行理解。
回到函数connection_state_machine(),进入到状态CON_STATE_REQUEST_END的处理(代码571行),调用函数http_request_parse()解析客户端请求信息并根据解析结果做更进一步的处理。先来看看函数http_request_parse()源码,

如清单10-10所示。

图 10-10 读取存放请求消息头的数据结构
清单10-10 函数http_request_parse

//request.c
/*
RFC1945
RFC2616
解析请求
返回值:
1,表示有后续的POST数据到达
0,表示其他
*/
1126.int http_request_parse(server*srv,connection*con){
1127.char*uri=NULL,*proto=NULL,*method=NULL,con_length_set;
/*由前面的讲解知道,头域是由关键字(field-name)、冒号和可选值(field-value)组成的(即message-header=field-name":"[field-value]),这里设置变量is_key用于标记区分当前处理的字符属于哪部分,为1表示属于关键字部分,为0表示属于field-value部分。*/
1128.int is_key=1,key_len=0,is_ws_after_key=0,in_folding;
1129.char*value=NULL,*key=NULL;
1130.enum{HTTP_CONNECTION_UNSET,HTTP_CONNECTION_KEEPALIVE,
HTTP_CONNECTION_CLOSE}keep_alive_set=HTTP_CONNECTION_UNSET;
1131.int line=0;
1132.int request_line_stage=0;
1133.size_t i,first;
1134.int done=0;
/*正则式形式的请求消息格式,三行分别表示为Request-Line、header(包括general-header、request-header、entity-header)和CRLF的对应正则式。这里对正则式不做讲解,读者可以参考有关正则式方面知识的专门书籍。*/
/*
*Request:"^(GET|POST|HEAD)([^]+(\\?[^]+|))(HTTP/1\\.[01])$"
*Option:"^([-a-zA-Z]+):(.+)$"
*End:"^$"
*/
1135.if(con->conf.log_request_header){/*日志记录。*/
1136.log_error_write(srv,__FILE__,__LINE__,"sdsdSb",
1137."fd:",con->fd,
1138."request-len:",con->request.request->used,
1139."\n",con->request.request);
1140.}
1141.if(con->request_count>1&&
1142.con->request.request->ptr[0]=='\r'&&
1143.con->request.request->ptr[1]=='\n'){
1144./*we are in keep-alive and might get\r\n after a previous POST request.*/
/*根据RFC 2616第4.1节,一般来说,健壮性足够好的服务器应该忽略任意请求行(Request-Line)前面的空行。也就是说,在服务器开始读消息流的时候发现了一个CRLF则应该忽略这个CRLF。而由于某些有问题的HTTP/1.0客户端会在POST请求消息之后产生额外的CRLF,如果是Keep-Alive连接,则这个额外的CRLF会保留到下一次请求处理中,因此Lighttpd服务器应该忽略它们。判断"con->request_count>1"表示前面处理过连接,即之前客户端POST过请求消息并且连接是Keep-Alive。复制到con->parse_request,接下来对其进行解析。*/
1145.buffer_copy_string_len(con->parse_request,con->request.request->ptr+2,con->request.request->used-1-2);
1146.}else{
1147./*fill the local request buffer*/
1148.buffer_copy_string_buffer(con->parse_request,con->request.request);
1149.}
1150.keep_alive_set=0;
1151.con_length_set=0;
1152./*parse the first line of the request
1153.*should be:
1154.*<method><uri><protocol>\r\n
1155.**/
/*按照请求行Request-Line的格式进行逐个字符解析处理。*/
1156.for(i=0,first=0;i<con->parse_request->used&&line==0;i++){
1157.char*cur=con->parse_request->ptr+i;
1158.switch(*cur){
/*正常的请求行应该是以\r\n结束,读者第一次看这个函数时应该先看下面的"case''"情况(代码1275行)。*/
1159.case'\r':
1160.if(con->parse_request->ptr[i+1]=='\n'){
1161.http_method_t r;
1162.char*nuri=NULL;
1163.size_t j;
1164./*\r\n->\0\0*/
/*截取请求行:利用函数buffer_copy_string_len()将con->parse_request的前i个字符(即请求行)复制到con->request.request_line内。*/
1165.con->parse_request->ptr[i]='\0';
1166.con->parse_request->ptr[i+1]='\0';
1167.buffer_copy_string_len(con->request.request_line,
con->parse_request->ptr,i);
/*变量request_line_stage用来记录已经遇到的空格数,正常的请求行逐个字符解析完后,request_line_stage应该等于2,即"<method><uri><protocol>\r\n"格式的请求行只包含两个空格字符,否则就是错误的请求,因此设置状态码为400(Bad Request)。*/
1168.if(request_line_stage!=2){
1169.con->http_status=400;
1170.con->response.keep_alive=0;
1171.con->keep_alive=0;
1172.if(srv->srvconf.log_request_header_on_error){
1173.log_error_write(srv,__FILE__,__LINE__,"s",
"incomplete request line->400");
1174.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1175.}
1176.return 0;
1177.}
/*截取请求行内的method、uri、protocol。*/
1178.proto=con->parse_request->ptr+first;
1179.*(uri-1)='\0';/*在method和uri之间截断。*/
1180.*(proto-1)='\0';/*在uri和protocol之间截断。*/
1181./*we got the first one:)*/
/*获取request方法。函数get_http_method_key()功能为获取给定字符串对应的枚举值。Lighttpd作者定义了method枚举值与method字符串之间的对应关系,如HTTP_METHOD_GET对应"GET",get_http_method_key()函数就是通过其字符串参数"GET"返回枚举值HTTP_METHOD_GET,如果给定字符串参数没有对应的枚举值则返回-1,此处则表示客户端请求method错误(这一部分有关源码已经在前面的第3.7节给出。),这包括有可能客户端发生的请求method本身就非法,也可能是Lighttpd还不支持该请求method,于是设置状态码501(Not Implemented)表示未实现该请求method。*/
1182.if(-1==(r=get_http_method_key(method))){
1183.con->http_status=501;
1184.con->response.keep_alive=0;
1185.con->keep_alive=0;
1186.if(srv->srvconf.log_request_header_on_error){
1187.log_error_write(srv,__FILE__,__LINE__,"s",
"unknown http-method->501");
1188.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1189.}
1190.return 0;
1191.}
1192.con->request.http_method=r;/*
*RFC2616 says:
*HTTP-Version="HTTP""/"1*DIGIT"."1*DIGIT**/
/*根据RFC 2616第3.1节,HTTP-Version的格式如上Lighttpd作者给出的BNF所示。HTTP协议使用"<major>.<minor>"的数字模式来指明协议的版本号,即包括主版本号和副版本号,它们之间用点(.)分割开。下面代码首先获取对应的主版本号和副版本号,再进行进一步判断处理。*/
1193.if(0==strncmp(proto,"HTTP/",sizeof("HTTP/")-1)){
1194.char*major=proto+sizeof("HTTP/")-1;/*主版本号。*/
1195.char*minor=strchr(major,'.');/*副版本号。*/
1196.char*err=NULL;
1197.int major_num=0,minor_num=0;
1198.int invalid_version=0;
/*没找到点或没有主版本号或没有副版本号都是错误的HTTP协议的版本号。*/
1199.if(NULL==minor||/*no dot*/
1200.minor==major||/*no major*/
1201.*(minor+1)=='\0'/*no minor*/){
1202.invalid_version=1;
1203.}else{
/*在major和minor之间截断,便于接下来的strtol()函数调用转换主版本号。*/
1204.*minor='\0';
1205.major_num=strtol(major,&err,10);/*转换。*/
1206.if(*err!='\0')invalid_version=1;/*检错。*/
1207.*minor++='.';/*还原。*/
1208.minor_num=strtol(minor,&err,10);/*转换。*/
1209.if(*err!='\0')invalid_version=1;/*检错。*/
1210.}
1211.if(invalid_version){/*错误的HTTP协议的版本号。*/
1212.con->http_status=400;
1213.con->keep_alive=0;
1214.if(srv->srvconf.log_request_header_on_error){
1215.log_error_write(srv,__FILE__,__LINE__,"s",
"unknown protocol->400");
1216.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1217.}
1218.return 0;
1219.}
/*记录HTTP协议的版本号。HTTP_VERSION_1_1和HTTP_VERSION_1_0为枚举值,定义在keyvalue.h头文件内:
//keyvalue.h
typedef enum{HTTP_VERSION_UNSET=-1,
HTTP_VERSION_1_0,HTTP_VERSION_1_1}http_version_t;
*/
1220.if(major_num==1&&minor_num==1){
1221.con->request.http_version=con->conf.allow_http11?
HTTP_VERSION_1_1:HTTP_VERSION_1_0;
1222.}else if(major_num==1&&minor_num==0){
1223.con->request.http_version=HTTP_VERSION_1_0;
1224.}else{
1225.con->http_status=505;
1226.if(srv->srvconf.log_request_header_on_error){
1227.log_error_write(srv,__FILE__,__LINE__,"s",
"unknown HTTP version->505");
1228.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1229.}
1230.return 0;
1231.}
1232.}else{/*不是HTTP协议。*/
1233.con->http_status=400;
1234.con->keep_alive=0;
1235.if(srv->srvconf.log_request_header_on_error){
1236.log_error_write(srv,__FILE__,__LINE__,"s",
"unknown protocol->400");
1237.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1238.}
1239.return 0;
1240.}
1241.if(0==strncmp(uri,"http://",7)&&
1242.NULL!=(nuri=strchr(uri+7,'/'))){
/*ignore the host-part*/
/*忽略掉主机部分,即当请求行内的uri为absoluteRUI时,比如请求行为"GET http://www.example.cn/www/index.htm HTTP/1.1",此时uri内的主机部分"http://www.example.cn"将被去掉。*/
1243.buffer_copy_string_len(con->request.uri,nuri,proto-nuri-1);
1244.}else{
/*everything looks good so far*/
1245.buffer_copy_string_len(con->request.uri,uri,proto-uri-1);
1246.}
/*check uri for invalid characters*/
/*下面代码用于检查uri字符的合法性,这儿的uri是经过客户端编码了的,也就是说,当客户请求Lighttpd站点内某一文件,如为"http://127.0.0.1:3000/1234.html"(文件名内带有空格),该请求被传到Lighttpd服务器时就变成了"GET/1234%20.htm HTTP/1.1",即空格被编码为“%20”(空格也有可能被编码为加号),这种编码转换是由客户端自动完成的。前面的第3.3.3节对这种编码有介绍,读者可以翻回去看看。*/
1247.for(j=0;j<con->request.uri->used-1;j++){
/*函数request_uri_is_valid_char()定义在request.c源文件内,用于检查参数c是否为uri合法字符,合法返回1,否则返回0值。
//request.c
int request_uri_is_valid_char(unsigned char c){
if(c<=32)return 0;
if(c==127)return 0;
if(c==255)return 0;
return 1;
}
*/
1248.if(!request_uri_is_valid_char(con->request.uri->ptr[j])){
/*捕获非法字符。*/
1249.unsigned char buf[2];
1250.con->http_status=400;
1251.con->keep_alive=0;
1252.if(srv->srvconf.log_request_header_on_error){
1253.buf[0]=con->request.uri->ptr[j];
1254.buf[1]='\0';
1255.if(con->request.uri->ptr[j]>32&&
1256.con->request.uri->ptr[j]!=127){
1257./*the character is printable->print it*/
1258.log_error_write(srv,__FILE__,__LINE__,"ss",
"invalid character in URI->400",buf);
1259.}else{
1260./*a control-character,print ascii-code*/
1261.log_error_write(srv,__FILE__,__LINE__,
"sd",
"invalid character in URI->400",
con->request.uri->ptr[j]);
1262.}
1263.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1264.}
1265.return 0;
1266.}
1267.}/*转存请求uri。*/
1268.buffer_copy_string_buffer(con->request.orig_uri,con->request.uri);
1269.con->http_status=0;
1270.i++;
1271.line++;/*请求行已经处理完毕,设置变量跳出for循环。*/
1272.first=i+1;
1273.}
1274.break;
1275.case'':
/*正常的客户端请求行格式为:<method><uri><protocol>\r\n
即分为三个阶段,request_line_stage用于记录当前分析处于哪个阶段。*/
1276.switch(request_line_stage){
1277.case 0:
1278./*GET|POST|...*/
/*第一个阶段为<method>,利用变量method记录。*/
1279.method=con->parse_request->ptr+first;
1280.first=i+1;
1281.break;
1282.case 1:
1283./*/foobar/...*/
/*第一个阶段为<uri>,利用变量uri记录。*/
1284.uri=con->parse_request->ptr+first;
1285.first=i+1;
1286.break;
1287.default:
1288./*ERROR,one space to much*/
/*request_line_stage大于等于2时就表示客户端请求行内的空格字符多余两个,即为错误的请求。*/
1289.con->http_status=400;
1290.con->response.keep_alive=0;
1291.con->keep_alive=0;
1292.if(srv->srvconf.log_request_header_on_error){
1293.log_error_write(srv,__FILE__,__LINE__,"s",
"overlong request line->400");
1294.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1295.}
1296.return 0;
1297.}
/*遇到一个空格,request_line_stage增1。*/
1298.request_line_stage++;
1299.break;
1300.}
1301.}
/*我们已经知道HTTP/1.1将"\r\n"字符串定义为除了实体主体(entity-body)外的其他任何协议元素的行尾标志(实体主体的行尾标志是由它的关联媒体类型定义,RFC 2616第3.7节)。但是,HTTP/1.1的消息头域值可以折叠成多行,其紧接着的折叠行由空格(SP)或水平制表符(HT)折叠标记开始。(RFC 2616第2.2节)。变量in_folding就是用于标记折叠行。*/
1302.in_folding=0;
/*检查是否指定了请求的uri地址。与1比较而不是与0比较是因为如果buffer结构体保存的数据是字符串,则其used字段的计算将是包括最后的'\0'字符。*/
1303.if(con->request.uri->used==1){
1304.con->http_status=400;
1305.con->response.keep_alive=0;
1306.con->keep_alive=0;
1307.log_error_write(srv,__FILE__,__LINE__,"s","no uri specified->400");
1308.if(srv->srvconf.log_request_header_on_error){
1309.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1310.}
1311.return 0;
1312.}
1313.for(;i<con->parse_request->used&&!done;i++){
/*解析请求头,is_key用于标记区分当前处理的字符属于哪部分,为1表示属于field-name部分,为0表示属于field-value部分,这两部分之间通过冒号分割。*/
1314.char*cur=con->parse_request->ptr+i;
1315.if(is_key){/*is_key初始为1,field-name部分解析处理。*/
1316.size_t j;
1317.int got_colon=0;/*标记遇到冒号则进入field-value处理部分。*/
1318./**
1319.*1*<any CHAR except CTLs or separators>
1320.*CTLs==0-31+127
1321.*/
1322.switch(*cur){
1323.case':':
1324.is_key=0;/*遇到冒号进入field-value处理部分。*/
1325.value=cur+1;/*field-value开始字符位置。*/
1326.if(is_ws_after_key==0){/*field-name字符串实际长度。*/
1327.key_len=i-first;
1328.}
1329.is_ws_after_key=0;
1330.break;
1331.case'(':case')':case'<':
1332.case'>':case'@':case',':
1333.case';':case'\\':case'\"':
1334.case'/':case'[':case']':
1335.case'?':case'=':case'{':
1336.case'}':
/*遇到上面这些字符,即表示field-name字符串内会包含这些字符则都是错误情况。前面曾经给出过field-name以及token的BNF(RFC 2616第2.2节)为:
field-name=token
token=1*<any CHAR except CTLs or separators>
separators="("|")"|"<"|">"|"@"|","|";"|":"|"\"|<">
|"/"|"["|"]"|"?"|"="|"{"|"}"|SP|HT
即field-name不能包含控制字符(CTL,为字符0~31以及字符127)和分隔符(separator)。这里判断出field-name包含有分隔符,因此设置状态码为400(Bad Request)。*/
1337.con->http_status=400;
1338.con->keep_alive=0;
1339.con->response.keep_alive=0;
1340.log_error_write(srv,__FILE__,__LINE__,"sbsds",
1341."invalid character in key",con->request.request,cur,
*cur,"->400");
1342.return 0;
1343.case'':
1344.case'\t':
/*在解析完一个消息头域后(即遇到"\r\n"),is_key被设置为1表示开始解析field-name,而first被设置为指向下一个消息头域的开始字符,在消息头域的开始字符为空格(SP)或水平制表符(HT)则表示为消息头域值(field-value)折叠成多行,因此此时复原变量is_key和标记in_folding,然后break跳出,继续进行field-value的解析。*/
1345.if(i==first){
1346.is_key=0;
1347.in_folding=1;
1348.value=cur;
1349.break;
1350.}
/*不是field-value折叠情况,则有另外两种可能,一是field-name内包含有空格或水平制表符,根据前面的注释可以知道这种情况是非法头域;二是在field-name和冒号之间有空格或水平制表符。下面这个for循环对这两种情况进行处理。*/
1351.key_len=i-first;
1352./*skip every thing up to the:*/
1353.for(j=1;!got_colon;j++){
1354.switch(con->parse_request->ptr[j+i]){
1355.case'':
1356.case'\t':/*第二种情况。*/
1357./*skip WS*/
1358.continue;
1359.case':':
1360./*ok,done*/
1361.i+=j-1;
1362.got_colon=1;
1363.break;
1364.default:/*第一种情况,出错。*/
1365./*error*/
1366.if(srv->srvconf.log_request_header_on_error){
1367.log_error_write(srv,__FILE__,__LINE__,"s",
"WS character in key->400");
1368.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1369.}
1370.con->http_status=400;
1371.con->response.keep_alive=0;
1372.con->keep_alive=0;
1373.return 0;
1374.}
1375.}
1376.break;
1377.case'\r':
1378.if(con->parse_request->ptr[i+1]=='\n'&&i==first){
/*遇到仅包含CRLF的“空”行,头域解析结束。*/
1379./*End of Header*/
1380.con->parse_request->ptr[i]='\0';
1381.con->parse_request->ptr[i+1]='\0';
1382.i++;
1383.done=1;
1384.break;
1385.}else{/*丢失换行符号。*/
1386.if(srv->srvconf.log_request_header_on_error){
1387.log_error_write(srv,__FILE__,__LINE__,"s",
"CR without LF->400");
1388.log_error_write(srv,__FILE__,__LINE__,"Sb",
1389."request-header:\n",
1390.con->request.request);
1391.}
1392.con->http_status=400;
1393.con->keep_alive=0;
1394.con->response.keep_alive=0;
1395.return 0;
1396.}
1397./*fall thru*/
/*发现有控制字符,报错。*/
1398.case 0:/*illegal characters(faster than a if():)*/
1399.case 1:case 2:case 3:
1400.case 4:case 5:case 6:
1401.case 7:case 8:case 10:
1402.case 11:case 12:case 14:
1403.case 15:case 16:case 17:
1404.case 18:case 19:case 20:
1405.case 21:case 22:case 23:
1406.case 24:case 25:case 26:
1407.case 27:case 28:case 29:
1408.case 30:case 31:case 127:
1409.con->http_status=400;
1410.con->keep_alive=0;
1411.con->response.keep_alive=0;
1412.if(srv->srvconf.log_request_header_on_error){
1413.log_error_write(srv,__FILE__,__LINE__,"sbsds",
"CTL character in key",con->request.request,cur,*cur,"->400");
1414.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1415.}
1416.return 0;
1417.default:
1418./*ok*/
1419.break;
1420.}
1421.}else{/*field-value部分解析处理。*/
1422.switch(*cur){
1423.case'\r':
1424.if(con->parse_request->ptr[i+1]=='\n'){
/*为了排版清晰,从下行开始的代码全部向前缩进了5个tab符。*/
1425.data_string*ds=NULL;
/*一行消息的解析工作结束,该行可能是一个新的消息头域,也可能是上一个消息头域值(field-value)的折叠。*/
1426./*End of Headerline*/
1427.con->parse_request->ptr[i]='\0';
1428.con->parse_request->ptr[i+1]='\0';
1429.if(in_folding){/*该行信息为上一个消息头域值(field-value)的折叠。*/
1430.buffer*key_b;
1431./**
1432.*we use a evil hack to handle the line-folding
1433.*As array_insert_unique()deletes'ds'in the case of a duplicate
1434.*ds points somewhere and we get a evil crash.As a solution we keep the old
1435.*"key"and get the current value from the hash and append us
1436.**/
1437.if(!key||!key_len){/*头域里没有任何内容,除了空白字符。*/
1438./*400*/
1439.if(srv->srvconf.log_request_header_on_error){
1440.log_error_write(srv,__FILE__,__LINE__,"s",
1441."WS at the start of first line->400");
1442.log_error_write(srv,__FILE__,__LINE__,"Sb",
1443."request-header:\n",con->request.request);
1444.}
1445.con->http_status=400;
1446.con->keep_alive=0;
1447.con->response.keep_alive=0;
1448.return 0;
1449.}
/*找到保存上一个消息头域的ds(通过field-name来查找),然后将本行信息作为field-value的折叠值添加到后面。
con->request.headers为array结构体类型,用于存储所有的消息头域。消息头域的field-name和field-value分别作为key和value存储在一个data_string结构体内,然后将这个data_string结构体作为array结构体(con->request.headers)的数据存储在其data字段内。*/
1450.key_b=buffer_init();
1451.buffer_copy_string_len(key_b,key,key_len);
1452.if(NULL!=(ds=(data_string*)array_get_element(con->request.headers,
key_b->ptr))){
1453.buffer_append_string(ds->value,value);
1454.}
1455.buffer_free(key_b);/*释放临时变量。*/
1456.}else{/*该行信息为一个新的消息头域。*/
1457.int s_len;
1458.key=con->parse_request->ptr+first;/*key指向field-name的第一个字符。*/
1459.s_len=cur-value;
1460./*strip trailing white-spaces*//*剥去末尾空白字符。*/
1461.for(;s_len>0&&
1462.(value[s_len-1]==''||
1463.value[s_len-1]=='\t');s_len--);
1464.value[s_len]='\0';
1465.if(s_len>0){/*如果field-value有值存在,则保存该头域。*/
1466.int cmp=0;/*获得一个未使用的data。*/
1467.if(NULL==(ds=(data_string*)array_get_unused_element(
con->request.headers,TYPE_STRING))){
1468.ds=data_string_init();
1469.}
1470.buffer_copy_string_len(ds->key,key,key_len);/*转存头域field-name。*/
1471.buffer_copy_string_len(ds->value,value,s_len);/*转存头域field-value。*/
1472./*retreive values
1473.*the list of options is sorted to simplify the search
1474.*/
/*下面几个if判断都是根据解析出来的每个头域值field-value来设置相应的变量。*/
1475.if(0==(cmp=buffer_caseless_compare(CONST_BUF_LEN(ds->key),
CONST_STR_LEN("Connection")))){
/*常用头域Connection允许发送者指定某一特定连接中的选项,该选项一般取值包括有Keep-Alive(声明持久连接,RFC 2068第19.7.1节)和Close(声明非持久连接,RFC 2616第14.10节)。
BNF:
Connection="Connection"":"1#(connection-token)
connection-token=token
例子:
Connection:close
*/
1476.array*vals;
1477.size_t vi;
1478./*split on,*/
1479.vals=srv->split_vals;
1480.array_reset(vals);
/*函数http_request_split_value()将保存在ds->value内的类似于"val1,val2,val3,val4"形式的字符串按","分割(同时会去除每个val的前后空格)后创建初始化对应data_string结构并保存到vals中,在后面我们会看到对这个函数的代码解析。*/
1481.http_request_split_value(vals,ds->value);
1482.for(vi=0;vi<vals->used;vi++){/*对每个值进行比较判断。*/
1483.data_string*dsv=(data_string*)vals->data[vi];
1484.if(0==buffer_caseless_compare(CONST_BUF_LEN(dsv->value),
CONST_STR_LEN("keep-alive"))){
1485.keep_alive_set=HTTP_CONNECTION_KEEPALIVE;
1486.break;
1487.}else if(0==buffer_caseless_compare(CONST_BUF_LEN(
dsv->value),CONST_STR_LEN("close"))){
1488.keep_alive_set=HTTP_CONNECTION_CLOSE;
1489.break;
1490.}
1491.}
1492.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("Content-Length")))){
/*实体头域Content-Length用来按十进制或八位字节数指明了发给给接收端的实体主体大小,或是在使用HEAD方法的情况下指明若请求为GET方法时响应应该发送的实体主体大小。任何大于或等于0的Content-Length均为有效值,其BNF表示如下所示。(RFC 2616第14.13节)。
BNF:
Content-Length="Content-Length"":"1*DIGIT
例子:
Content-Length:3495
另外,注意上面的if语句中用cmp>0作为短路运算&&的前部分,是因为上一个if判断中的key与"Connection"比较不等于0,那么如果要此处的if判断为真(即key与"Content-Length"相等),则上一个buffer_caseless_compare()执行比较(也就是"Content-Length"与"Connection"相比较)的结果cmp必大于0,如果不大于0,那么此处的i f判断必定为假,按&&短路运算规则,后面的buffer_caseless_compare()比较函数直接不执行了。Lighttpd作者巧妙地选择了头域值比较的先后顺序,这在一定程度上提高程序的执行效率,后面的几个if判断也是如此。*/
1493.char*err;
1494.unsigned long int r;
1495.size_t j;
1496.if(con_length_set){/*请求头重复,错误请求。*/
1497.con->http_status=400;
1498.con->keep_alive=0;
1499.if(srv->srvconf.log_request_header_on_error){
1500.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate Content-Length-header->400");
1501.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1502.}
/*该值被插入,因为已经存在,所以这里的插入是将该值添加到之前存在的值后面。*/
1503.array_insert_unique(con->request.headers,(data_unset*)ds);
1504.return 0;
1505.}
1506.if(ds->value->used==0)SEGFAULT();/*保证有值。*/
1507.for(j=0;j<ds->value->used-1;j++){
/*根据上面的BNF检查错误。*/
1508.char c=ds->value->ptr[j];
1509.if(!isdigit((unsigned char)c)){
1510.log_error_write(srv,__FILE__,__LINE__,"sbs",
"content-length broken:",ds->value,"->400");
1511.con->http_status=400;
1512.con->keep_alive=0;
1513.array_insert_unique(con->request.headers,(data_unset*)ds);
1514.return 0;
1515.}
1516.}/*转换成十进制无符号整型数。*/
1517.r=strtoul(ds->value->ptr,&err,10);
1518.if(*err=='\0'){/*成功转换,合法值,设置相应变量。*/
1519.con_length_set=1;
1520.con->request.content_length=r;
1521.}else{
1522.log_error_write(srv,__FILE__,__LINE__,"sbs",
"content-length broken:",ds->value,"->400");
1523.con->http_status=400;
1524.con->keep_alive=0;
1525.array_insert_unique(con->request.headers,(data_unset*)ds);
1526.return 0;
1527.}
1528.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("Content-Type")))){
/*实体头域Content-Type用来指明发给接收端的实体主体的媒体类型,或在HEAD方法中指明若请求为GET时将发送的媒体类型,其BNF表示如下所示(RFC 2616第14.17节以及第3.7节)。
BNF:
Content-Type="Content-Type"":"media-type
media-type=type"/"subtype*(";"parameter)
type=token
subtype=token
例子:
Content-Type:text/html;charset=ISO-8859-4
*/
1529./*if dup,only the first one will survive*/
1530.if(!con->request.http_content_type){
1531.con->request.http_content_type=ds->value->ptr;
1532.}else{
1533.con->http_status=400;
1534.con->keep_alive=0;
1535.if(srv->srvconf.log_request_header_on_error){
1536.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate Content-Type-header->400");
1537.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1538.}
1539.array_insert_unique(con->request.headers,(data_unset*)ds);
1540.return 0;
1541.}
1542.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("Expect")))){
/*HTTP 2616 8.2.3
*Expect:100-continue
*->(10.1.1)100(read content,process request,send final status-code)
*->(10.4.18)417(close)
*(not handled at all yet,we always send 417 here)
*What has to be added?
*1.handling of chunked request body
*2.out-of-order sending from the HTTP/1.1 100 Continue
*header
*/
/*请求头域Expect用于指明客户端需要的特定服务器行为。Lighttpd目前无法满足任何的Expection,因此直接以417(期望失败)状态码响应(RFC 2616第14.20节)。
BNF:
Expect="Expect"":"1#expectation
expectation="100-continue"|expectation-extension
expectation-extension=token["="(token|quoted-string)
*expect-params]
expect-params=";"token["="(token|quoted-string)]
*/
1543.con->http_status=417;
1544.con->keep_alive=0;
1545.array_insert_unique(con->request.headers,(data_unset*)ds);
1546.return 0;
1547.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("Host")))){
/*请求头域Host用于指明正在请求资源的网络主机和端口号。如果"host"没有跟随端口号,那么就采用请求服务的的默认端口,比如对一个HTTP服务来说就是80端口。遵循HTTP/1.1协议的客户端或代理在发出的请求中必须包含一个合适的Host头域(如果请求URI没有包含请求服务的网络主机名,那么Host头域就给一个空值),否则访问的同是基于HTTP/1.1协议的服务器会(也必须)响应一个400(坏请求)状态码(RFC 2616第14.23节)。
BNF:
Host="Host"":"host[":"port];Section 3.2.2
例子:
GET/pub/WWW/HTTP/1.1
Host:www.w3.org
*/
1548.if(!con->request.http_host){
1549.con->request.http_host=ds->value;
1550.}else{
1551.con->http_status=400;
1552.con->keep_alive=0;
1553.if(srv->srvconf.log_request_header_on_error){
1554.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate Host-header->400");
1555.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1556.}
1557.array_insert_unique(con->request.headers,(data_unset*)ds);
1558.return 0;
1559.}
1560.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("If-Modified-Since")))){
/*请求头域If-Modified-Since在前面的6.2.3节提到过,其主要用来让请求方法成为条件方法,即如果想Web服务器请求的资源自从由该头域里指定的时间之后都没有发生改变,那么Web服务器不会返回实体,而是以304(没有改变)状态码进行响应,同时返回消息也没有消息主体(message-body)(RFC 2616第14.25节)。
BNF:
If-Modified-Since="If-Modified-Since"":"HTTP-date
例子:
If-Modified-Since:Sat,29 Oct 1994 19:43:31 GMT
*/
1561./*Proxies sometimes send dup headers
1562.*if they are the same we ignore the second
1563.*if not,we raise an error*/
/*代理服务器有时会发送重复的请求头,如果它们相同则忽略第二个,否则抛出一个错误。*/
1564.if(!con->request.http_if_modified_since){
1565.con->request.http_if_modified_since=ds->value->ptr;
1566.}else if(0==strcasecmp(con->request.http_if_modified_since,
ds->value->ptr)){
1567./*ignore it if they are the same*/
1568.ds->free((data_unset*)ds);
1569.ds=NULL;
1570.}else{
1571.con->http_status=400;
1572.con->keep_alive=0;
1573.if(srv->srvconf.log_request_header_on_error){
1574.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate If-Modified-Since header->400");
1575.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1576.}
1577.array_insert_unique(con->request.headers,(data_unset*)ds);
1578.return 0;
1579.}
1580.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
_BUF_LEN(ds->key),CONST_STR_LEN("If-None-Match")))){
/*请求头域If-None-Match和If-Modified-Since类似,也是用来让请求方法成为条件方法,它通过比较请求实体的标签来验证已获取的实体中是否有不存在于服务器当前实体中的实体,这个特性允许通过以一个最小事务开销来更新客户端缓存信息,前面的6.2.2节提到过这个具体流程(RFC 2616第14.26节)。
BNF:
If-None-Match="If-None-Match"":"("*"|1#entity-tag)
例子:
If-None-Match:"xyzzy"
If-None-Match:W/"xyzzy"
If-None-Match:"xyzzy","r2d2xxxx","c3piozzzz"
If-None-Match:W/"xyzzy",W/"r2d2xxxx",W/"c3piozzzz"
If-None-Match:*/*头域值“*”匹配资源的任何当前实体。*/
*/
1581./*if dup,only the first one will survive*/
1582.if(!con->request.http_if_none_match){
1583.con->request.http_if_none_match=ds->value->ptr;
1584.}else{
1585.con->http_status=400;
1586.con->keep_alive=0;
1587.if(srv->srvconf.log_request_header_on_error){
1588.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate If-None-Match-header->400");
1589.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1590.}
1591.array_insert_unique(con->request.headers,(data_unset*)ds);
1592.return 0;
1593.}
1594.}else if(cmp>0&&0==(cmp=buffer_caseless_compare(
CONST_BUF_LEN(ds->key),CONST_STR_LEN("Range")))){
/*HTTP/1.1允许一个客户请求响应实体的一部分(RFC 2616第3.12节)。利用请求头域Range可以请求一个或多个实体主体的某一范围内字节,而不是整个实体主体(RFC 2616第14.35.2节)。
BNF:
range-unit=bytes-unit|other-range-unit
bytes-unit="bytes"
other-range-unit=token
Range="Range"":"ranges-specifier
ranges-specifier=byte-ranges-specifier
byte-ranges-specifier=bytes-unit"="byte-range-set
byte-range-set=1#(byte-range-spec|suffix-byte-range-spec)
byte-range-spec=first-byte-pos"-"[last-byte-pos]
first-byte-pos=1*DIGIT
last-byte-pos=1*DIGIT
suffix-byte-range-spec="-"suffix-length
suffix-length=1*DIGIT
假设实体主体的总长度为10000,则利用Range请求部分数据的例子:
Range:bytes=0-499/*第一个500字节,字节偏移是以0开始。*/
Range:bytes=500-999/*第二个500字节。*/
Range:bytes==-500/*最后500字节。*/
Range:bytes=9500-/*最后500字节的另一种表示方法。*/
Range:bytes=0-0,-1/*仅仅第一个和最后一个字节。*/
*/
1595.if(!con->request.http_range){
1596./*bytes=.*-.**/
1597.if(0==strncasecmp(ds->value->ptr,"bytes=",6)&&
1598.NULL!=strchr(ds->value->ptr+6,'-')){
/*if dup,only the first one will survive*/
1599.con->request.http_range=ds->value->ptr+6;
1600.}
1601.}else{
1602.con->http_status=400;
1603.con->keep_alive=0;
1604.if(srv->srvconf.log_request_header_on_error){
1605.log_error_write(srv,__FILE__,__LINE__,"s",
"duplicate Range-header->400");
1606.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",
con->request.request);
1607.}
1608.array_insert_unique(con->request.headers,(data_unset*)ds);
1609.return 0;
1610.}
1611.}/*将解析到的头域信息存储到con->request.headers内。*/
1612.if(ds)array_insert_unique(con->request.headers,(data_unset*)ds);
1613.}else{
1614./*empty header-fields are not allowed by HTTP-RFC,we just ignore them*/
/*空值的头域则直接忽略。*/
1615.}
1616.}
1617.i++;/*变量复原,重新开始对下一个头域field-name的解析处理。*/
1618.first=i+1;
1619.is_key=1;
1620.value=0;
1621.#if 0
1622./**
1623.*for Bug 1230 keep the key_len a live
1624.*/
1625.key_len=0;
1626.#endif
1627.in_folding=0;
/*为了排版清晰,向前缩进5个tab符到此截止。*/
1628.}else{/*丢失换行符号。*/
1629.if(srv->srvconf.log_request_header_on_error){
1630.log_error_write(srv,__FILE__,__LINE__,"sbs",
"CR without LF",con->request.request,"->400");
1631.}
1632.con->http_status=400;
1633.con->keep_alive=0;
1634.con->response.keep_alive=0;
1635.return 0;
1636.}
1637.break;
1638.case'':
1639.case'\t':
1640./*strip leading WS*/
/*去掉field-value中的前导空白字符。*/
1641.if(value==cur)value=cur+1;
1642.default:
1643.if(*cur>=0&&*cur<32){/*控制字符非法。*/
1644.if(srv->srvconf.log_request_header_on_error){
1645.log_error_write(srv,__FILE__,__LINE__,"sds",
"invalid char in header",(int)*cur,"->400");
1646.}
1647.con->http_status=400;
1648.con->keep_alive=0;
1649.return 0;
1650.}
1651.break;
1652.}
1653.}
1654.}
1655.con->header_len=i;
1656./*do some post-processing*/
1657.if(con->request.http_version==HTTP_VERSION_1_1){
1658.if(keep_alive_set!=HTTP_CONNECTION_CLOSE){
1659./*no Connection-Header sent*/
1660./*HTTP/1.1->keep-alive default TRUE*/
/*1.1版本的HTTP协议默认情况下是保持持久连接(Keep-Alive)的(RFC 2068第8.1.2节)。*/
1661.con->keep_alive=1;
1662.}else{
1663.con->keep_alive=0;
1664.}
1665./*RFC 2616,14.23*/
/*根据前面的注释已经知道基于HTTP/1.1协议的服务器对于没有包含Host头域的请求应该要响应一个400(坏请求)状态码(RFC 2616第14.23节)。*/
1666.if(con->request.http_host==NULL||
1667.buffer_is_empty(con->request.http_host)){
1668.con->http_status=400;
1669.con->response.keep_alive=0;
1670.con->keep_alive=0;
1671.if(srv->srvconf.log_request_header_on_error){
1672.log_error_write(srv,__FILE__,__LINE__,"s",
"HTTP/1.1 but Host missing->400");
1673.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1674.}
1675.return 0;
1676.}
1677.}else{
1678.if(keep_alive_set==HTTP_CONNECTION_KEEPALIVE){
1679./*no Connection-Header sent*/
1680./*HTTP/1.0->keep-alive default FALSE*/
1681.con->keep_alive=1;
1682.}else{
1683.con->keep_alive=0;
1684.}
1685.}
1686.
1687./*check hostname field if it is set*/
1688.if(NULL!=con->request.http_host&&
1689.0!=request_check_hostname(srv,con,con->request.http_host)){
/*函数request_check_hostname()用来检测host格式是否正确,正确返回0否则返回-1。*/
1690.if(srv->srvconf.log_request_header_on_error){
1691.log_error_write(srv,__FILE__,__LINE__,"s",
"Invalid Hostname->400");
1692.log_error_write(srv,__FILE__,__LINE__,"Sb",
"request-header:\n",con->request.request);
1693.}
1694.con->http_status=400;
1695.con->response.keep_alive=0;
1696.con->keep_alive=0;
1697.return 0;
1698.}
1699.
1700.switch(con->request.http_method){
1701.case HTTP_METHOD_GET:
1702.case HTTP_METHOD_HEAD:
1703./*content-length is forbidden for those*/
/*对于GET和HEAD的请求方法,禁止发送Content-Length头域。一般来说,GET请求方式是没有提交内容的(其能通过URL向服务器传送少量数据),所以Content-length在GET模式下是无效的。曾经的Microsoft IIS/5在处理Content-length时就存在拒绝服务漏洞问题,感兴趣的读者可以了解一下。*/
1704.if(con_length_set&&con->request.content_length!=0){
1705./*content-length is missing*/
1706.log_error_write(srv,__FILE__,__LINE__,"s",
1707."GET/HEAD with content-length->400");
1708.con->keep_alive=0;
1709.con->http_status=400;
1710.return 0;
1711.}
1712.break;
1713.case HTTP_METHOD_POST:
/*对于POST的请求方法,必须提供Content-Length头域。*/
1714./*content-length is required for them*/
1715.if(!con_length_set){
1716./*content-length is missing*/
1717.log_error_write(srv,__FILE__,__LINE__,"s",
"POST-request,but content-length missing->411");
1718.con->keep_alive=0;
1719.con->http_status=411;
1720.return 0;
1721.}
1722.break;
1723.default:
1724./*the may have a content-length*/
1725.break;
1726.}
1727./*check if we have read post data*/
1728.if(con_length_set){
1729./*don't handle more the SSIZE_MAX bytes in content-length*/
1730.if(con->request.content_length>SSIZE_MAX){/*数据太大。*/
1731.con->http_status=413;
1732.con->keep_alive=0;
1733.log_error_write(srv,__FILE__,__LINE__,"sds",
"request-size too long:",con->request.content_length,"->413");
1734.return 0;
1735.}
1736./*divide by 1024 as srvconf.max_request_size is in kBytes*/
1737.if(srv->srvconf.max_request_size!=0&&
1738.(con->request.content_length>>10)>srv->srvconf.max_request_size){
1739./*the request body itself is larger then
1740.*our our max_request_size
1741.*//*数据超过限制。*/
1742.con->http_status=413;
1743.con->keep_alive=0;
1744.log_error_write(srv,__FILE__,__LINE__,"sds",
"request-size too long:",con->request.content_length,"->413");
1745.return 0;
1746.}
1747./*we have content*/
1748.if(con->request.content_length!=0){
1749.return 1;/*有POST数据需要读取。*/
1750.}
1751.}
1752.return 0;
}

图 10-11 解析请求消息头流程
清单10-11 解析请求消息头辅助函数

//request.c
/*把b内保存的类似于"val1,val2,val3,val4"的字符串按","分割并保存到vals数组内,并且会去除每个val的前后空格。*/
1753.int http_request_split_value(array*vals,buffer*b){
1754.char*s;
1755.size_t i;
1756.int state=0;
1757./*
1758.*parse
1759.*val1,val2,val3,val4
1760.*into a array(more or less a explode()incl.striping of whitespaces
1761.*/
1762.if(b->used==0)return 0;
1763.s=b->ptr;
1764.for(i=0;i<b->used-1;){
1765.char*start=NULL,*end=NULL;
1766.data_string*ds;
1767.switch(state){
1768.case 0:/*ws*/
1769./*skip ws*//*跳过开头的空白字符。*/
1770.for(;(*s==''||*s=='\t')&&i<b->used-1;i++,s++);
1771.state=1;
1772.break;
1773.case 1:/*value*/
1774.start=s;
1775.for(;*s!=','&&i<b->used-1;i++,s++);
1776.end=s-1;/*删掉末尾的空白字符。*/
1777.for(;(*end==''||*end=='\t')&&end>start;end--);
1778.if(NULL==(ds=(data_string*)array_get_unused_element(vals,
TYPE_STRING))){
1779.ds=data_string_init();
1780.}
1781.buffer_copy_string_len(ds->value,start,end-start+1);
1782.array_insert_unique(vals,(data_unset*)ds);
1783.if(*s==','){
1784.state=0;
1785.i++;
1786.s++;
1787.}else{
1788./*end of string*/
1789.state=2;
1790.}
1791.break;
1792.default:
1793.i++;
1794.break;
1795.}
1796.}
1797.return 0;
1798.}
/*该函数用于判断host格式是否正确,正确则返回0,否则返回-1。*/
1799.static int request_check_hostname(server*srv,connection*con,buffer*host){
1800.enum{DOMAINLABEL,TOPLABEL}stage=TOPLABEL;
1801.size_t i;
1802.int label_len=0;
1803.size_t host_len;
1804.char*colon;
1805.int is_ip=-1;/*-1 don't know yet,0 no,1 yes*/
1806.int level=0;
1807.UNUSED(srv);
1808.UNUSED(con);
1809./*
1810.*hostport=host[":"port]
1811.*host=hostname|IPv4address|IPv6address
1812.*hostname=*(domainlabel".")toplabel["."]
1813.*domainlabel=alphanum|alphanum*(alphanum|"-")alphanum
1814.*toplabel=alpha|alpha*(alphanum|"-")alphanum
1815.*IPv4address=1*digit"."1*digit"."1*digit"."1*digit
1816.*IPv6address="["..."]"
1817.*port=*digit
1818.*/
/*上面给出Host的BNF表示来自RFC 1738第5节以及RFC 2396第3.2.2节,读者可以参考该文档里的具体详细内容。*/
1819./*no Host:*//*空或长度为0也认为符合host格式而返回0。*/
1820.if(!host||host->used==0)return 0;
1821.host_len=host->used-1;/*字符总长度(除掉末尾的'\0'字符)。*/
1822./*IPv6 adress*/
/*对于文本IPv6地址在URL上的格式,读者可以参考RFC 2732第2节以及RFC 3986第3.2.2节,下面简单说明,便于读者理解下面代码。在URL中使用IPv6地址,该地址都以符号“[”和“]”来括起来,举几个URL例子:
http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html
http://[1080:0:0:0:8:800:200C:417A]/index.html
http://[1080::8:800:200C:417A]/foo
http://[::FFFF:129.144.52.38]:80/index.html
*/
1823.if(host->ptr[0]=='['){/*IPv6地址的处理。*/
1824.char*c=host->ptr+1;
1825.int colon_cnt=0;
1826./*check portnumber*/
1827.for(;*c&&*c!=']';c++){
1828.if(*c==':'){
1829.if(++colon_cnt>7){/*最多只有7个冒号。*/
1830.return-1;
1831.}
1832.}else if(!light_isxdigit(*c)){/*十六进制数字。*/
1833.return-1;
1834.}
1835.}
1836./*missing]*/
1837.if(!*c){/*丢失右中括“]”符号。*/
1838.return-1;
1839.}
1840./*check port*/
1841.if(*(c+1)==':'){/*检查端口号。*/
1842.for(c+=2;*c;c++){
1843.if(!light_isdigit(*c)){
1844.return-1;
1845.}
1846.}
1847.}
1848.return 0;
1849.}
/*IPv4地址的处理。函数memchr()在某一内存范围(host->ptr所指内存内容的前host_len个字节)中查找一特定字节(':'),找到则返回该指定字节的指针,否则返回NULL。*/
1850.if(NULL!=(colon=memchr(host->ptr,':',host_len))){
1851.char*c=colon+1;
1852./*check portnumber*/
1853.for(;*c;c++){
1854.if(!light_isdigit(*c))return-1;
1855.}/*求得IPv4的host实际长度。*/
1856./*remove the port from the host-len*/
1857.host_len=colon-host->ptr;
1858.}
1859./*Host is empty*/
1860.if(host_len==0)return-1;
/*IPv4的host有两种形式,一种为直接的IPv4地址形式,一种为域名形式。域名形式的host又分为两种,一种为绝对域名,即以根域名(英文点“.”)结尾的域名,如"poneria.ISI.EDU.";另外一种为相对域名,即不是以根域名(英文点“.”)结尾的域名。关于这部分的详细知识介绍,读者可以参考RFC 1034第3.1节以及RFC 1035第5.1节。*/
/*如果最后一个字符为点(“.”)则去掉,比如"www.baidu.com."这样的域名是有效的,统一处理则去掉最后一个点。*/
1861./*if the hostname ends in a"."strip it*/
1862.if(host->ptr[host_len-1]=='.')host_len-=1;
1863./*scan from the right and skip the\0*/
/*从右往左扫描,并跳过字符'\0',因为i为size_t类型,前面3.2.2节曾经讲过size_t是为了方便系统之间的移植而定义的无符号整型数据,所以这里在判断循环结束条件的时候必须是i+1>0这样来判断,直接用i>=0就将隐含及其严重的BUG(即由于无符号整型数据0减去1将得到异常大的正整数)。*/
1864.for(i=host_len-1;i+1>0;i--){
1865.const char c=host->ptr[i];
1866.switch(stage){
1867.case TOPLABEL:
/*从右往左,首先是toplabel。根据toplabel的BNF进行逐步处理:
toplabel=alpha|alpha*[alphadigit|"-"]alphadigit
*/
1868.if(c=='.'){
1869./*only switch stage,if this is not the last character*/
/*toplabel长度为0,出错返回。label_len记录域名每一部分(两个点之间字符串)或IPv4地址每一节(两个点之间数字串)的长度,如果label_len为0表示出错。不管是域名字符串还是IPv4地址字符串,下面我们统一将两个点之间的字符串称为一节。*/
1870.if(i!=host_len-1){
1871.if(label_len==0){
1872.return-1;
1873.}
1874./*check the first character at right of the dot*/
/*不是数字IPv4地址形式,则根据toplabel的BNF知道点右边字符必定是字母字符(即每节域名以字母字符开头)才正确。*/
1875.if(is_ip==0){
1876.if(!light_isalpha(host->ptr[i+1])){
1877.return-1;
1878.}
1879.}else if(!light_isdigit(host->ptr[i+1])){
1880.is_ip=0;
/*下面这个判断永远也不会被执行到,因为如果上面这个if判断(代码1879行)的函数light_isdigit()返回真则表示字符host->ptr[i+1]为数字则肯定不会与字符'-'相等,如果返回假则又不会执行到下个if判断(代码1881行),所以两种情况下的代码1882行都不会被执行到。*/
1881.}else if('-'==host->ptr[i+1]){
1882.return-1;
1883.}else{
1884./*just digits*/
1885.is_ip=1;
1886.}
/*根据hostname的BNF知道处理完toplabel后要开始进行domainlabel的处理。*/
1887.stage=DOMAINLABEL;
1888.label_len=0;
1889.level++;
1890.}else if(i==0){
1891./*just a dot and nothing else is evil*/
1892.return-1;
1893.}
1894.}else if(i==0){
1895./*the first character of the hostname*/
/*不是数字IPv4地址形式(因为只有一节,即toplabel,而数字IPv4地址形式有4节),因此第一个字符必定是字母字符才符合toplabel的BNF。*/
1896.if(!light_isalpha(c)){
1897.return-1;
1898.}
1899.label_len++;
1900.}else{
/*toplabel的BNF式里只包含有三种字符:字母(alpha)、横线(-)以及数字(digit)。*/
1901.if(c!='-'&&!light_isalnum(c)){
1902.return-1;
1903.}
1904.if(is_ip==-1){/*未知情况下进行初步判断。*/
1905.if(!light_isdigit(c))is_ip=0;
1906.}
1907.label_len++;
1908.}
1909.break;
1910.case DOMAINLABEL:
1911.if(is_ip==1){
1912.if(c=='.'){
/*label_len为0表示该节IPv4地址丢失,出错返回。*/
1913.if(label_len==0){
1914.return-1;
1915.}
1916.label_len=0;
1917.level++;
1918.}else if(!light_isdigit(c)){/*IPv4地址出错:不是数字。*/
1919.return-1;
1920.}else{
1921.label_len++;
1922.}
1923.}else{
1924.if(c=='.'){
/*label_len为0表示该部分域名丢失,出错返回。*/
1925.if(label_len==0){
1926.return-1;
1927.}
1928./*c is either-or alphanum here*/
/*domainlabel的BNF为:
domainlabel=alphanum|alphanum*(alphanum|"-")alphanum可以看到domainlabel不能以'-'字符开头。*/
1929.if('-'==host->ptr[i+1]){
1930.return-1;
1931.}
1932.label_len=0;
1933.level++;
1934.}else if(i==0){/*第一个字符必须是字母或数字字符。*/
1935.if(!light_isalnum(c)){
1936.return-1;
1937.}
1938.label_len++;
1939.}else{
/*domainlabel的BNF式里只包含有三种字符:字母(alpha)、横线(-)以及数字(digit)。*/
1940.if(c!='-'&&!light_isalnum(c)){
1941.return-1;
1942.}
1943.label_len++;
1944.}
1945.}
1946.break;
1947.}
1948.}
/*数字IPv4地址形式包含有4节。*/
1949./*a IP has to consist of 4 parts*/
1950.if(is_ip==1&&level!=3){
1951.return-1;
1952.}
/*根据host的BNF可以知道以点开头的形式不对。*/
1953.if(label_len==0){
1954.return-1;
1955.}
1956.return 0;
1957.}

通过上面对http_request_parse()函数源码的分析,我们可以知道Lighttpd服务器管理的该连接状态将根据解析的客户端请求信息的不同结果做不同的切换,如果判断出客户端将有后继数据传送过来(这通过实体头域Content-Length来指定后继数据长度)则连接状态被切换到CON_STATE_READ_POST状态(代码577行,图10-5内的状态切换箭头g所示情况)继续读取数据,否则切换到状态CON_STATE_HANDLE_REQUEST(代码580行,图10-5内的状态切换箭头f所示情况)开始对该服务请求进行处理。

GET服务请求没有后继数据传送,因此连接状态切换到CON_STATE_HANDLE_REQUEST开始进行响应请求服务的内部处理,完成此处理任务的是函数http_response_prepare()(代码591行),该函数主要调用加载的各个插件进行响应请求处理准备工作,如清单10-12所示。
清单10-12 函数http_response_prepare

//response.c
1958.handler_t http_response_prepare(server*srv,connection*con){
1959.handler_t r;
1960./*looks like someone has already done a decision*/
/*该请求已经被处理则直接返回。*/
1961.if(con->mode==DIRECT&&
1962.(con->http_status!=0&&con->http_status!=200)){
1963./*remove a packets in the queue*/
1964.if(con->file_finished==0){
1965.chunkqueue_reset(con->write_queue);
1966.}
1967.return HANDLER_FINISHED;
1968.}
1969./*no decision yet,build conf->filename*/
/*该请求未被处理并且请求资料的物理路径还未组织。*/
1970.if(con->mode==DIRECT&&con->physical.path->used==0){
1971.char*qstr;
1972./*we only come here when we have the parse the full request again
1973.*a HANDLER_COMEBACK from mod_rewrite and mod_fastcgi might be a
1974.*problem here as mod_setenv might get called multiple times
1975.*fastcgi-auth might lead to a COMEBACK too
1976.*fastcgi again dead server too
1977.*mod_compress might add headers twice too
1978.**/
1979.config_cond_cache_reset(srv,con);/*重置连接配置值。*/
/*获取连接的固定全局配置值。*/
1980.config_setup_connection(srv,con);
1981./*Perhaps this could be removed at other places.*/
1982.
1983.if(con->conf.log_condition_handling){
1984.log_error_write(srv,__FILE__,__LINE__,"s","run condition");
1985.}/*获取连接的SOCKET条件配置值。*/
1986.config_patch_connection(srv,con,COMP_SERVER_SOCKET);
1987./*SERVERsocket*/
1988./**
1989.*prepare strings
1990.*-uri.path_raw
1991.*-uri.path(secure)
1992.*-uri.query
1993.*/
1994./**
1995.*Name according to RFC 2396
1996.*-scheme
1997.*-authority
1998.*-path
1999.*-query
2000.*(scheme)://(authority)(path)?(query)#fragment
2001.*/
/*这部分BNF来自RFC 2396第3节。*/
2002.if(con->conf.is_ssl){/*协议类型。*/
2003.buffer_copy_string_len(con->uri.scheme,CONST_STR_LEN("https"));
2004.}else{
2005.buffer_copy_string_len(con->uri.scheme,CONST_STR_LEN("http"));
2006.}/*转存主机名或者IP地址以及:port。*/
2007.buffer_copy_string_buffer(con->uri.authority,con->request.http_host);
2008.buffer_to_lower(con->uri.authority);/*转换为小写。*/
/*获取连接的各类条件配置值。*/
2009.config_patch_connection(srv,con,COMP_HTTP_SCHEME);
2010./*Scheme:*/
2011.config_patch_connection(srv,con,COMP_HTTP_HOST);
2012./*Host:*/
2013.config_patch_connection(srv,con,COMP_HTTP_REMOTE_IP);
2014./*Client-IP*/
2015.config_patch_connection(srv,con,COMP_HTTP_REFERER);
2016./*Referer:*/
2017.config_patch_connection(srv,con,COMP_HTTP_USER_AGENT);
2018./*User-Agent:*/
2019.config_patch_connection(srv,con,COMP_HTTP_COOKIE);
2020./*Cookie:*/
2021.config_patch_connection(srv,con,COMP_HTTP_REQUEST_METHOD);
2022./*REQUEST_METHOD*/
2023.
2024./**their might be a fragment which has to be cut away*/
/*大部分读者都应该知道URI字符串中的#标志符用来界定片段(fragment)或者锚点(anchor),这样它使得不仅能让URI指向特定的页面资源(当然,URI也可以指向其他资源),还能指向特定页面内的特定段落,因此#标志符能当作“精准链接”的便利工具。但是#标志符对于我们Lighttpd服务器定位页面资源的物理路径是没有任何帮助作用的,因此这里要去掉。*/
2025.if(NULL!=(qstr=strchr(con->request.uri->ptr,'#'))){
2026.con->request.uri->used=qstr-con->request.uri->ptr;
2027.con->request.uri->ptr[con->request.uri->used++]='\0';
2028.}
2029./**extract query string from request.uri*/
/*'?'标志字符用来界定查询数据(query),这样它使得客户端GET请求方法也能向服务器提交少量的数据,至于少量数据是多少则由URI的最大长度来决定,但是HTTP协议并没有对URI的长度作事先的限制(RFC 2616第3.2.1节),由各实现服务器自己决定能够处理的资源URI长度,一般情况下是应该能够处理无限长度的URI。对于Lighttpd服务器,通过前面的分析(代码959行)可以知道最多是64KB(当然是不可能达到64KB,因为请求头还包括其他头域等)。
另外,通过这几行的分析可以知道,Lighttpd服务器下的资源命名中不能包含有'#','?'(UNIX/Linux下文件名可以带'?'的,Windows下不可以)字符,否则会被截断而找不到对应的资源。比如请求"http://127.0.0.1:3000/1?23#4.htm"找到的对应文件为webroot下的“1”而不是"1?23#4.htm",因为在这几行代码执行过程中被截断。*/
2030.if(NULL!=(qstr=strchr(con->request.uri->ptr,'?'))){
2031.buffer_copy_string(con->uri.query,qstr+1);
2032.buffer_copy_string_len(con->uri.path_raw,con->request.uri->ptr,
qstr-con->request.uri->ptr);
2033.}else{
2034.buffer_reset(con->uri.query);
2035.buffer_copy_string_buffer(con->uri.path_raw,con->request.uri);
2036.}
2037.if(con->conf.log_request_handling){
2038.//……日志代码,省略……
2039.}
2040./*disable keep-alive if requested*//*超过了可保持的最大连接数。*/
2041.if(con->request_count>con->conf.max_keep_alive_requests){
2042.con->keep_alive=0;
2043.}
2044./*build filename
2045.*-decode url-encodings(e.g.%20->'')
2046.*-remove path-modifiers(e.g./../)
2047.*/
/*解码URL。*/
2048.if(con->request.http_method==HTTP_METHOD_OPTIONS&&
2049.con->uri.path_raw->ptr[0]=='*'&&con->uri.path_raw->ptr[1]=='\0'){
2050./*OPTIONS*...*/
/*当客户端请求方法为OPTIONS时,请求URI可以为星号“*”,表示OPTIONS请求将会应用于服务器的所有资源而不是特定资源(RFC 2616第9.2节)。*/
2051.buffer_copy_string_buffer(con->uri.path,con->uri.path_raw);
2052.}else{
2053.buffer_copy_string_buffer(srv->tmp_buf,con->uri.path_raw);
2054.buffer_urldecode_path(srv->tmp_buf);/*URL解码。*/
/*con->uri.path_raw内保存的是客户端请求的原始URI,而con->uri.path内存储已经被解码并经过等同简化了的URI。*/
2055.buffer_path_simplify(con->uri.path,srv->tmp_buf);
2056.}
2057.if(con->conf.log_request_handling){
2058.//……日志代码,省略……
2059.}
2060./**
2061.*call plugins
2062.*-based on the raw URL
2063.*/
/*插件处理函数调用。*/
2064.switch(r=plugins_call_handle_uri_raw(srv,con)){
2065.case HANDLER_GO_ON:
2066.break;
2067.case HANDLER_FINISHED:
2068.case HANDLER_COMEBACK:
2069.case HANDLER_WAIT_FOR_EVENT:
2070.case HANDLER_ERROR:
2071.return r;
2072.default:
2073.log_error_write(srv,__FILE__,__LINE__,"sd",
"handle_uri_raw:unknown return value",r);
2074.break;
2075.}
2076./**
2077.*call plugins
2078.*-based on the clean URL
2079.*//*获取连接的HTTPurl和HTTPqs条件配置值。*/
2080.config_patch_connection(srv,con,COMP_HTTP_URL);/*HTTPurl*/
2081.config_patch_connection(srv,con,COMP_HTTP_QUERY_STRING);/*HTTPqs*/
2082./*do we have to downgrade to 1.0?*/
2083.if(!con->conf.allow_http11){/*支持http1.0版本。*/
2084.con->request.http_version=HTTP_VERSION_1_0;
2085.}
/*插件处理函数调用。*/
2086.switch(r=plugins_call_handle_uri_clean(srv,con)){
2087.case HANDLER_GO_ON:
2088.break;
2089.case HANDLER_FINISHED:
2090.case HANDLER_COMEBACK:
2091.case HANDLER_WAIT_FOR_EVENT:
2092.case HANDLER_ERROR:
2093.return r;
2094.default:
2095.log_error_write(srv,__FILE__,__LINE__,"");
2096.break;
2097.}
/*OPTIONS请求应用于服务器的所有资源时直接处理并返回。*/
2098.if(con->request.http_method==HTTP_METHOD_OPTIONS&&
2099.con->uri.path->ptr[0]=='*'&&con->uri.path_raw->ptr[1]=='\0'){
2100./*option requests are handled directly without checking of the path*/
/*插入的Allow响应头,该函数源码后面解析。*/
2101.response_header_insert(srv,con,CONST_STR_LEN("Allow"),
CONST_STR_LEN("OPTIONS,GET,HEAD,POST"));
2102.con->http_status=200;
2103.con->file_finished=1;
2104.return HANDLER_FINISHED;
2105.}
2106./***
2107.*border
2108.*logical filename(URI)becomes a physical filename here
2109.*/
2110./*1.stat()
2111.*...ISREG()->ok,go on
2112.*...ISDIR()->index-file->redirect
2113.*2.pathinfo()
2114.*...ISREG()
2115.*3.->404
2116.*/
2117./*
2118.*SEARCH DOCUMENT ROOT
2119.*/
2120./*set a default*/
2121.buffer_copy_string_buffer(con->physical.doc_root,
con->conf.document_root);
2122.buffer_copy_string_buffer(con->physical.rel_path,con->uri.path);
2123.#if defined(__WIN32)||defined(__CYGWIN__)
2124./*strip dots from the end and spaces
2125.*windows/dos handle those filenames as the same file
2126.*foo==foo.==foo.....=="foo..."=="foo.../"
2127.*This will affect in some cases PATHINFO
2128.*on native windows we could prepend the filename with\\?\to circumvent
2129.*this behaviour.I have no idea how to push this through cygwin
2130.**/
/*去掉末尾的点和空格,在Windows/DOS下,"foo"和"foo."和"foo....."以及"foo.../"等都是表示同一个文件。*/
2131.if(con->physical.rel_path->used>1){
2132.buffer*b=con->physical.rel_path;
2133.size_t i;
2134.if(b->used>2&&
2135.b->ptr[b->used-2]=='/'&&
2136.(b->ptr[b->used-3]==''||
2137.b->ptr[b->used-3]=='.')){
2138.b->ptr[b->used--]='\0';
2139.}
2140.for(i=b->used-2;b->used>1;i--){
2141.if(b->ptr[i]==''||
2142.b->ptr[i]=='.'){
2143.b->ptr[b->used--]='\0';
2144.}else{
2145.break;
2146.}
2147.}
2148.}
2149.#endif
2150.if(con->conf.log_request_handling){
2151.//……日志代码,省略……
2152.}
2153./*the docroot plugin should set the doc_root and might also set the physical.path
2154.*for us(all vhost-plugins are supposed to set the doc_root)
2155.**/
/*插件处理函数调用。*/
2156.switch(r=plugins_call_handle_docroot(srv,con)){
2157.case HANDLER_GO_ON:
2158.break;
2159.case HANDLER_FINISHED:
2160.case HANDLER_COMEBACK:
2161.case HANDLER_WAIT_FOR_EVENT:
2162.case HANDLER_ERROR:
2163.return r;
2164.default:
2165.log_error_write(srv,__FILE__,__LINE__,"");
2166.break;
2167.}
2168./*MacOS X and Windows can't distiguish between upper and lower-case
2169.*convert to lower-case
2170.*/
2171.if(con->conf.force_lowercase_filenames){/*需要强制转换为小写。*/
2172.buffer_to_lower(con->physical.rel_path);
2173.}
2174./*the docroot plugins might set the servername,if they don't we take http-host*/
2175.if(buffer_is_empty(con->server_name)){
2176.buffer_copy_string_buffer(con->server_name,con->uri.authority);
2177.}
2178./**
2179.*create physical filename
2180.*->physical.path=docroot+rel_path
2181.*/
/*组合docroot+rel_path获得physical.path。*/
2182.buffer_copy_string_buffer(con->physical.path,con->physical.doc_root);
2183.BUFFER_APPEND_SLASH(con->physical.path);
2184.buffer_copy_string_buffer(con->physical.basedir,con->physical.path);
2185.if(con->physical.rel_path->used&&
2186.con->physical.rel_path->ptr[0]=='/'){
2187.buffer_append_string_len(con->physical.path,
con->physical.rel_path->ptr+1,con->physical.rel_path->used-2);
2188.}else{
2189.buffer_append_string_buffer(con->physical.path,
con->physical.rel_path);
2190.}
2191.if(con->conf.log_request_handling){
2192.//……日志代码,省略……
2193.}
/*插件处理函数调用。*/
2194.switch(r=plugins_call_handle_physical(srv,con)){
2195.case HANDLER_GO_ON:
2196.break;
2197.case HANDLER_FINISHED:
2198.case HANDLER_COMEBACK:
2199.case HANDLER_WAIT_FOR_EVENT:
2200.case HANDLER_ERROR:
2201.return r;
2202.default:
2203.log_error_write(srv,__FILE__,__LINE__,"");
2204.break;
2205.}
2206.if(con->conf.log_request_handling){
2207.//……日志代码,省略……
2208.}
2209.}
2210./*
2211.*Noone catched away the file from normal path of execution yet(like
2212.*mod_access)
2213.*Go on and check of the file exists at all
2214.*/
/*在前面的分析构建请求文件路径的过程中,该客户端请求就有可能被处理掉,如OPTIONS请求(代码2048行)、请求的URI被禁止访问(被插件mod_access处理)等;如果程序执行到这则表示在当前请求还没有被处理掉,因此接下来进行检查请求资源文件是否存在的进一步处理。*/
2215.if(con->mode==DIRECT){
2216.char*slash=NULL;
2217.char*pathinfo=NULL;
2218.int found=0;
2219.stat_cache_entry*sce=NULL;
2220.if(con->conf.log_request_handling){
2221.//……日志代码,省略……
2222.}
/*前面6.3.3节详细分析过函数stat_cache_get_entry(),该函数用来返回对应文件在状态缓存器中的缓存值。*/
2223.if(HANDLER_ERROR!=stat_cache_get_entry(srv,con,con->physical.path,&sce)){
2224./*file exists*/
2225.if(con->conf.log_request_handling){
2226.//……日志代码,省略……
2227.}
2228.#ifdef HAVE_LSTAT
2229.if((sce->is_symlink!=0)&&!con->conf.follow_symlink){
/*当前请求资源文件路径中包含符号链接(可能是该请求资源文件也可能是该资源文件所在的父目录、祖父目录等),而用户配置不允许访问符号链接资源(即server.follow-symlink="disable"),因此设置响应状态码为403(禁止访问)。*/
2230.con->http_status=403;
2231.if(con->conf.log_request_handling){
2232.//……日志代码,省略……
2233.}
2234.buffer_reset(con->physical.path);
2235.return HANDLER_FINISHED;
2236.};
2237.#endif
2238.if(S_ISDIR(sce->st.st_mode)){
/*当前请求资源文件为一个目录文件,而请求URI的末尾字符又不是表示目录的'/',此时调用http_response_redirect_to_directory()函数来设置响应状态码为301(永久性重定向)。两点说明,第一,i f判断里是倒数第二个字符与'/'比较,因为倒数第一个为字符'\0';第二,函数http_response_redirect_to_directory()重新组建重定向的目录字符串,然后设置301响应状态码以及请求处理完成标志(con->file_finished=1;)。*/
2239.if(con->physical.path->ptr[con->physical.path->used-2]!='/'){
2240./*redirect to.../*/
2241.http_response_redirect_to_directory(srv,con);
2242.return HANDLER_FINISHED;/*请求处理完成返回。*/
2243.}
2244.#ifdef HAVE_LSTAT
2245.}else if(!S_ISREG(sce->st.st_mode)&&!sce->is_symlink){
2246.#else
2247.}else if(!S_ISREG(sce->st.st_mode)){
2248.#endif
2249./*any special handling of non-reg files?*/
2250.}
2251.}else{/*stat_cache_get_entry()函数调用出错处理。*/
2252.switch(errno){
2253.case EACCES:/*存取权限有误。*/
2254.con->http_status=403;
2255.if(con->conf.log_request_handling){
2256.//……日志代码,省略……
2257.}
2258.buffer_reset(con->physical.path);
2259.return HANDLER_FINISHED;
2260.case ENOENT:/*路径名的部分组成不存在,或路径名是空字串。*/
2261.con->http_status=404;
2262.if(con->conf.log_request_handling){
2263.//……日志代码,省略……
2264.}
2265.buffer_reset(con->physical.path);
2266.return HANDLER_FINISHED;
2267.case ENOTDIR:/*路径名的部分组件不是目录。*/
2268./*PATH_INFO!:)*/
2269.break;
2270.default:/*其他错误。*/
2271./*we have no idea what happend.let's tell the user so.*/
2272.con->http_status=500;
2273.buffer_reset(con->physical.path);
2274.log_error_write(srv,__FILE__,__LINE__,"ssbsb",
"file not found...or so:",strerror(errno),con->uri.path,"->",con->physical.path);
2275.return HANDLER_FINISHED;
2276.}
2277./*not found,perhaps PATHINFO*/
/*很多Web应用开发框架(比如ThinkPHP、FleaPHP、QuickPHP)支持多种URL模式,如普通模式、PATHINFO模式、兼容模式和REWRITE模式。PATHINFO模式和URL重写技术类似(RFC 3875第4.1.5节),但是要比URL重写简单得多,它可以将动态页面映射到伪静态页面(比如将"http://127.0.0.1:3000/www/index.cgi?name=lenky&submit=post"映射到"http://127.0.0.1:3000/www/index.cgi/name/lenky/submit/post"),便于搜索引擎收录索引。*/
2278.buffer_copy_string_buffer(srv->tmp_buf,con->physical.path);
2279.do{
/*逐步向前查找实际页面资源路径,如对于客户端请求解析出来的物理路径"/home/lenky/source/lighttpd-1.4.20/lenky/www/index.cgi/name/lenky/submit/post"需要找到实际路径"/home/lenky/source/lighttpd-1.4.20/lenky/www/index.cgi",而"/name/lenky/submit/post"事实上为请求查询参数,并不是真正的资源路径。*/
2280.if(slash){/*之后逐步去掉末尾的查询参数。*/
2281.buffer_copy_string_len(con->physical.path,
srv->tmp_buf->ptr,slash-srv->tmp_buf->ptr);
2282.}else{/*首次为全部字符串。*/
2283.buffer_copy_string_buffer(con->physical.path,srv->tmp_buf);
2284.}
/*如果当前路径串对应的文件存在(即不会返回HANDLER_ERROR),此时表示所有的查询参数字符串都被去掉,已经找到实际请求资源路径。*/
2285.if(HANDLER_ERROR!=stat_cache_get_entry(srv,con,
con->physical.path,&sce)){
/*S_ISREG宏返回非0,如果该文件为普通文件。*/
2286.found=S_ISREG(sce->st.st_mode);
2287.break;
2288.}
/*首先暂时截断后面的查询参数字符串,便于利用函数strrchr()继续向前查找(通过定位最末尾字符'/')实际请求资源路径,然后再复原。*/
2289.if(pathinfo!=NULL){
2290.*pathinfo='\0';
2291.}
2292.slash=strrchr(srv->tmp_buf->ptr,'/');
2293.if(pathinfo!=NULL){
2294./*restore'/'*/
2295.*pathinfo='/';/*复原。*/
2296.}
2297.if(slash)pathinfo=slash;/*保存此次定位位置。*/
/*继续逐步查找处理,found初始值为0并且在进入循环前一直未被改变,而在此循环内当其被设置时必定就会跳出循环(代码2287行),因此可以说判断(found==0)不是必要的;判断(slash!=NULL)保证有进一步向前查找字符'/'的意义,第三个判断的用意很明显,确保不是在根目录字符串内进行无用操作。*/
2298.}while((found==0)&&(slash!=NULL)&&((size_t)(slash-srv->tmp_buf->ptr)>(con->physical.basedir->used-2)));
/*found==0表示有两种可能:1)请求路径出错,该路径对应资源文件不存在;2)请求路径对应资源文件不是普通文件。这两种情况都设置响应状态码为404(资源未找到)。*/
2299.if(found==0){
2300./*no it really doesn't exists*/
2301.con->http_status=404;
2302.if(con->conf.log_file_not_found){
2303.log_error_write(srv,__FILE__,__LINE__,"sbsb",
"file not found:",con->uri.path,"->",con->physical.path);
2304.}
2305.buffer_reset(con->physical.path);
2306.return HANDLER_FINISHED;
2307.}
2308.#ifdef HAVE_LSTAT
/*当前请求资源文件路径中包含符号链接(可能是该请求资源文件也可能是该资源文件所在的父目录、祖父目录等),而用户配置不允许访问符号链接资源(即server.follow-symlink="disable"),因此设置响应状态码为403(禁止访问)。*/
2309.if((sce->is_symlink!=0)&&!con->conf.follow_symlink){
2310.con->http_status=403;
2311.if(con->conf.log_request_handling){
2312.log_error_write(srv,__FILE__,__LINE__,"s",
"--access denied due symlink restriction");
2313.log_error_write(srv,__FILE__,__LINE__,"sb",
"Path:",con->physical.path);
2314.}
2315.buffer_reset(con->physical.path);
2316.return HANDLER_FINISHED;
2317.};
2318.#endif
2319./*we have a PATHINFO*/
/*将PATHINFO转存。*/
2320.if(pathinfo){
2321.buffer_copy_string(con->request.pathinfo,pathinfo);
2322./*
2323.*shorten uri.path
2324.*/
2325.con->uri.path->used-=strlen(pathinfo);
2326.con->uri.path->ptr[con->uri.path->used-1]='\0';
2327.}
2328.if(con->conf.log_request_handling){
2329.//……日志代码,省略……
2330.}
2331.}
2332.if(con->conf.log_request_handling){
2333.//……日志代码,省略……
2334.}
2335./*call the handlers*//*插件处理函数调用。*/
2336.switch(r=plugins_call_handle_subrequest_start(srv,con)){
2337.case HANDLER_GO_ON:
2338./*request was not handled*/
2339.break;
2340.case HANDLER_FINISHED:
2341.default:
2342.if(con->conf.log_request_handling){
2343.log_error_write(srv,__FILE__,__LINE__,"s",
"--subrequest finished");
2344.}
2345./*something strange happend*/
2346.return r;
2347.}
2348./*if we are still here,no one wanted the file,status 403 is ok I think*/
/*没有任何插件来处理这个情况,设置响应状态码为403(禁止访问)。*/
2349.if(con->mode==DIRECT&&con->http_status==0){
2350.switch(con->request.http_method){
2351.case HTTP_METHOD_OPTIONS:
2352.con->http_status=200;
2353.break;
2354.default:
2355.con->http_status=403;
2356.}
2357.return HANDLER_FINISHED;
2358.}
2359.}
/*插件处理函数调用。*/
2360.switch(r=plugins_call_handle_subrequest(srv,con)){
2361.case HANDLER_GO_ON:
2362./*request was not handled,looks like we are done*/
2363.return HANDLER_FINISHED;
2364.case HANDLER_FINISHED:
2365./*request is finished*/
2366.default:
2367./*something strange happend*/
2368.return r;
2369.}
2370./*can't happen*/
2371.return HANDLER_COMEBACK;
2372.}
//http-header-glue.c
2373.int response_header_insert(server*srv,connection*con,const char*key,
size_t keylen,
const char*value,size_t vallen){
2374.data_string*ds;
2375.UNUSED(srv);/*获取一个未使用的元素。*/
2376.if(NULL==(ds=(data_string*)array_get_unused_element(
con->response.headers,TYPE_STRING))){
2377.ds=data_response_init();
2378.}
2379.buffer_copy_string_len(ds->key,key,keylen);
2380.buffer_copy_string_len(ds->value,value,vallen);
2381.array_insert_unique(con->response.headers,(data_unset*)ds);
2382.return 0;
2383.}
//data_string.c
2384.data_string*data_response_init(void){
2385.data_string*ds;
2386.ds=data_string_init();
2387.ds->insert_dup=data_response_insert_dup;/*重新设置回调函数,重点。*/
2388.return ds;
2389.}
2390.static int data_response_insert_dup(data_unset*dst,data_unset*src){
2391.data_string*ds_dst=(data_string*)dst;
2392.data_string*ds_src=(data_string*)src;
2393.if(ds_dst->value->used){
2394.buffer_append_string_len(ds_dst->value,CONST_STR_LEN("\r\n"));
2395.buffer_append_string_buffer(ds_dst->value,ds_dst->key);
2396.buffer_append_string_len(ds_dst->value,CONST_STR_LEN(":"));
2397.buffer_append_string_buffer(ds_dst->value,ds_src->value);
2398.}else{
2399.buffer_copy_string_buffer(ds_dst->value,ds_src->value);
2400.}
2401.src->free(src);
2402.return 0;
2403.}

函数http_response_prepare()执行完毕后,将根据其返回值进行对应的连接状态切换。正常情况下,即返回值为HANDLER_FINISHED,并且响应状态码既不是404(未找到)也不是403(禁止),则将连接状态切换到CON_STATE_RESPONSE_START(代码624行,图10-5内的状态切换箭头i所示情况)开始响应请求处理的准备工作;但是如果响应状态码为404(未找到)或403(禁止)之一,则需要根据用户是否设置有默认的404或403错误页面进行处理,如果设置有默认的404或403错误页面,则将对该次请求的地址重定向到这些自定义的错误页面,然后将连接状态再切换到CON_STATE_HANDLE_REQUEST重新进行处理,如果这些自定义的错误页面配置路径正确,那么此次客户端的请求响应状态码也不再是404或403,而是200(成功,表示发送自定义的错误页面成功)。这部分代码的详细注释请读者参看代码591~645行。

当我们已经完成了响应请求处理的准备工作(即已经确定了以何种状态码来响应此次客户端请求),在大多数情况下都将以状态码为200(成功)来进行响应,但也会有以其他状态码如404(未找到)来响应,但是不管怎么样,此时服务器都要开始准备向客户端发送响应数据,如状态码为200下的资源文件数据或者状态码为4 0 4下的4 0 4自组织页面数据等,即连接状态处在CON_STATE_RESPONSE_START下,该状态下的主要执行功能函数为connection_handle_write_prepare(),下面来分析这个函数源码,如清单10-13所示。

清单10-13 函数connection_handle_write_prepare

//connections.c
2404.static int connection_handle_write_prepare(server*srv,connection*con){
2405.if(con->mode==DIRECT){
2406./*static files*/
2407.switch(con->request.http_method){
2408.case HTTP_METHOD_GET:
2409.case HTTP_METHOD_POST:
2410.case HTTP_METHOD_HEAD:
2411.case HTTP_METHOD_PUT:
2412.case HTTP_METHOD_MKCOL:
2413.case HTTP_METHOD_DELETE:
2414.case HTTP_METHOD_COPY:
2415.case HTTP_METHOD_MOVE:
2416.case HTTP_METHOD_PROPFIND:
2417.case HTTP_METHOD_PROPPATCH:
2418.case HTTP_METHOD_LOCK:
2419.case HTTP_METHOD_UNLOCK:
2420.break;
2421.case HTTP_METHOD_OPTIONS:
2422./*
2423.*400 is coming from the request-parser BEFORE uri.path is set
2424.*403 is from the response handler when noone else catched it
2425.**/
/*对于OPTIONS请求应用于服务器的某个资源时也设置Allow响应头为"OPTIONS,GET,HEAD,POST",对于OPTIONS请求应用于服务器的所有资源情况,在上一个被分析函数里处理(代码2048行)。
我们利用GET命令可以查看到这个响应:

*/
2426.if((!con->http_status||con->http_status==200)&&
con->uri.path->used&&con->uri.path->ptr[0]!='*'){
/*插入的Allow响应头。*/
2427.response_header_insert(srv,con,CONST_STR_LEN("Allow"),
CONST_STR_LEN("OPTIONS,GET,HEAD,POST"));
/*在HTTP 1.1协议文档里,消息主体(message body)的编码转换由常用头域传输编码(Transfer-Encoding)来指定,它是为了实现在接收端和发送端之间的安全数据传输,属于消息的属性(RFC 2616第14.41节)。一种常见的编码转换方式为块传输编码(Chunked Transfer Coding)(RFC 2616第3.6.1节),块编码使得消息主体成块地发送,每一个块都有它自己的大小指示器并会紧接着一个可选的包含实体头域的尾部(trailer)。其BNF表示为:
Chunked-Body=*chunk
last-chunk
trailer
CRLF
chunk=chunk-size[chunk-extension]CRLF chunk-data CRLF
chunk-size=1*HEX
last-chunk=1*("0")[chunk-extension]CRLF
chunk-extension=*(";"chunk-ext-name["="chunk-ext-val])
chunk-ext-name=token
chunk-ext-val=token|quoted-string
chunk-data=chunk-size(OCTET)
trailer=*(entity-header CRLF)
另外一种编码转换方式为恒等传输编码(Identity Transfer Coding),也是默认编码,其表示不进行任何编码转换(也可以说是恒等转换)。
这两种编码转换方式对应值定义在头文件base.h内,
//base.h
enum{
HTTP_TRANSFER_ENCODING_IDENTITY,
HTTP_TRANSFER_ENCODING_CHUNKED
}transfer_encoding;
OPTIONS请求没有消息主体,禁止块编码(从后面代码分析可以看到即是不发送响应头域"Transfer-Encoding:chunked",也就是说请求响应消息里不会带上这个常用头域)。*/
2428.con->response.transfer_encoding&=
~HTTP_TRANSFER_ENCODING_CHUNKED;
2429.con->parsed_response&=~HTTP_CONTENT_LENGTH;
2430.con->http_status=200;
2431.con->file_finished=1;
2432.chunkqueue_reset(con->write_queue);
2433.}
2434.break;
2435.default:
2436.switch(con->http_status){
2437.case 400:/*bad request*/
2438.case 414:/*overload request header*/
2439.case 505:/*unknown protocol*/
/*Web分布式创作和版本管理(Web-based Distributed Authoring and Versioning,WebDAV)是一种基于HTTP 1.1协议的通信协议。它扩展了HTTP 1.1,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可直接对Web Server直接读写,并支持写文件锁定(Locking)、解锁(Unlock)以及资源管理、版本控制等。关于状态码207(Multi-Status,多重状态)和WebDAV的更多信息读者可以参考RFC 2518第8.2.1节)。*/
2440.case 207:/*this was webdav*/
2441.break;
2442.default:/*其他情况统一设置为状态码501(未实现)。*/
2443.con->http_status=501;
2444.break;
2445.}
2446.break;
2447.}
2448.}
2449.if(con->http_status==0){/*未被处理则设置为状态码403(禁止)。*/
2450.con->http_status=403;
2451.}
2452.switch(con->http_status){
2453.case 204:/*class:header only*/
2454.case 205:
2455.case 304:
2456./*disable chunked encoding again as we have no body*/
/*对于状态码为204、205、304的响应都是只有响应头域而没有消息主体的,因此禁止块编码。*/
2457.con->response.transfer_encoding&=
~HTTP_TRANSFER_ENCODING_CHUNKED;
2458.con->parsed_response&=~HTTP_CONTENT_LENGTH;
2459.chunkqueue_reset(con->write_queue);
2460.con->file_finished=1;
2461.break;
2462.default:/*class:header+body*/
/*其他状态码的响应只有响应头域也有消息主体。*/
2463.if(con->mode!=DIRECT)break;/*被其他模块(如cgi、scgi)处理*/
2464./*only custom body for 4xx and 5xx*/
/*仅对状态码为4xx和5xx的响应进行自组建响应状态码内容。*/
2465.if(con->http_status<400||con->http_status>=600)break;
2466.con->file_finished=0;
2467.buffer_reset(con->physical.path);
2468./*try to send static errorfile*//*尝试发送静态错误页面。*/
2469.if(!buffer_is_empty(con->conf.errorfile_prefix)){/*自定义错误页面。*/
2470.stat_cache_entry*sce=NULL;
2471.buffer_copy_string_buffer(con->physical.path,
con->conf.errorfile_prefix);
2472.buffer_append_long(con->physical.path,con->http_status);
2473.buffer_append_string_len(con->physical.path,
CONST_STR_LEN(".html"));
2474.if(HANDLER_ERROR!=stat_cache_get_entry(srv,con,
con->physical.path,&sce)){
/*自定义错误页面存在。*/
2475.con->file_finished=1;
2476.http_chunk_append_file(srv,con,con->physical.path,0,
sce->st.st_size);/*自定义错误页面加入发送链。*/
/*重写响应头域。*/
2477.response_header_overwrite(srv,con,
CONST_STR_LEN("Content-Type"),
CONST_BUF_LEN(sce->content_type));
2478.}
2479.}
/*自组织错误页面内容。*/
2480.if(!con->file_finished){
2481.buffer*b;
2482.buffer_reset(con->physical.path);
2483.con->file_finished=1;
2484.b=chunkqueue_get_append_buffer(con->write_queue);
2485./*build default error-page*/
/*创建错误页面。在ANSI C语言中,相邻的字符串常量将被自动组合为一个字符串,用以替换旧风格的在行末加斜杠“\”换行的做法。一连串相邻的字符串常量会在编译时自动合并,除了最后一个字符串外,其余每个字符串末尾的字符'\0'会被自动删除。这在《C专家编程》第34页有介绍。
下面这一部分代码为按HTML标记语言格式来组建错误页面。*/
2486.buffer_copy_string_len(b,CONST_STR_LEN(
"<?xml version=\"1.0\"encoding=\"iso-8859-1\"?>\n"
"<!DOCTYPE html PUBLIC\"-//W3C//DTD XHTML 1.0
Transitional//EN\"\n"
"\"http://www.w3.org/TR/xhtml1/DTD
/xhtml1-transitional.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\"
xml:lang=\"en\"lang=\"en\">\n"
"<head>\n"
"<title>"));
2487.buffer_append_long(b,con->http_status);
2488.buffer_append_string_len(b,CONST_STR_LEN("-"));
2489.buffer_append_string(b,get_http_status_name(con->http_status));
2490.
2491.buffer_append_string_len(b,CONST_STR_LEN(
"</title>\n"
"</head>\n"
"<body>\n"
"<h1>"));
2492.buffer_append_long(b,con->http_status);
2493.buffer_append_string_len(b,CONST_STR_LEN("-"));
2494.buffer_append_string(b,get_http_status_name(con->http_status));
2495.
2496.
buffer_append_string_len(b,CONST_STR_LEN("</h1>\n"
"</body>\n"
"</html>\n"
));
/*重写响应头域。*/
2497.response_header_overwrite(srv,con,
CONST_STR_LEN("Content-Type"),
CONST_STR_LEN("text/html"));
2498.}
2499.break;
2500.}
2501.if(con->file_finished){
2502./*we have all the content and chunked encoding is not used,set a
content-length*/
/*发送消息有消息主体并且消息主体未进行块传输编码。*/
2503.if((!(con->parsed_response&HTTP_CONTENT_LENGTH))&&
2504.(con->response.transfer_encoding&
HTTP_TRANSFER_ENCODING_CHUNKED)==0){
2505.off_t qlen=chunkqueue_length(con->write_queue);
2506./**
2507.*The Content-Length header only can be sent if we have content:
2508.*-HEAD doesn't have a content-body(but have a content-length)
2509.*-1xx,204 and 304 don't have a content-body(RFC 2616 Section 4.3)
2510.*Otherwise generate a Content-Length header as chunked encoding is not
2511.*available
2512.*/
/*根据RFC 2616第4.4节可以知道,某些响应消息是绝对不会包含消息主体的,如状态码为1xx、204和304的响应消息以及响应任何HEAD请求的消息,它们总是被头域后的第一个空行(即CRLF)终止而不管消息里是否有实体头域(当然也就包括实体头域Content-Length)。
因此在这几种情况下,实体头域Content-Length的存在是没有意义的。*/
2513.if((con->http_status>=100&&con->http_status<200)||
2514.con->http_status==204||
2515.con->http_status==304){
2516.data_string*ds;
2517./*no Content-Body,no Content-Length*/
2518.if(NULL!=(ds=(data_string*)array_get_element(
con->response.headers,"Content-Length"))){
/*没有内容的头域在实际发送数据时会自动忽略。*/
2519.buffer_reset(ds->value);/*Headers with empty values are
ignored for output*/
2520.}
2521.}else if(qlen>0||con->request.http_method!=
HTTP_METHOD_HEAD){
/*存在消息主体且不是HEAD请求,添加Content-Lengt h头域。*/
2522./*qlen=0 is important for Redirects(301,...)as they MAY have
2523.*a content.Browsers are waiting for a Content otherwise
2524.*/
2525.buffer_copy_off_t(srv->tmp_buf,qlen);
2526.response_header_overwrite(srv,con,
CONST_STR_LEN("Content-Length"),
CONST_BUF_LEN(srv->tmp_buf));
2527.}
2528.}
2529.}else{
2530./**
2531.*the file isn't finished yet,but we have all headers
2532.*to get keep-alive we either need:
2533.*-Content-Length:...(HTTP/1.0 and HTTP/1.0)or
2534.*-Transfer-Encoding:chunked(HTTP/1.1)
2535.*/
2536.if(((con->parsed_response&HTTP_CONTENT_LENGTH)==0)&&
((con->response.transfer_encoding&
HTTP_TRANSFER_ENCODING_CHUNKED)==0)){
2537.con->keep_alive=0;
2538.}
2539./**
2540.*if the backend sent a Connection:close,follow the wish
2541.*NOTE:if the backend sent Connection:Keep-Alive,but no
Content-Length,we
2542.*will close the connection.That's fine.We can always decide the close
2543.*the connection
2544.*FIXME:to be nice we should remove the Connection:...
2545.*/
2546.if(con->parsed_response&HTTP_CONNECTION){
2547./*a subrequest disable keep-alive although the client wanted it*/
2548.if(con->keep_alive&&!con->response.keep_alive){
2549.con->keep_alive=0;
2550.}
2551.}
2552.}
2553.if(con->request.http_method==HTTP_METHOD_HEAD){
2554./**
2555.*a HEAD request has the same as a GET
2556.
*without the content
2557.*/
/*根据RFC 2616第9.4节,HEAD请求和GET请求的响应一致,除了HEAD请求不返回信息主体。*/
2558.con->file_finished=1;
2559.chunkqueue_reset(con->write_queue);
2560.con->response.transfer_encoding&=
~HTTP_TRANSFER_ENCODING_CHUNKED;
2561.}
/*组织所有响应头字符串并保存到con->write_queue的第一个chunk元素内。*/
2562.http_response_write_header(srv,con);
2563.return 0;
2564.}
//response.c
2565.int http_response_write_header(server*srv,connection*con){
2566.buffer*b;
2567.size_t i;
2568.int have_date=0;
2569.int have_server=0;
/*获取一个mem类型的chunk块并添加到con->write_queue头部作为第一个chunk元素,这样在向客户端发送响应数据时,该chunk块内保存的头域字符串将被首先发送出去。*/
2570.b=chunkqueue_get_prepend_buffer(con->write_queue);
/*前面章节在讲述请求消息时提到过响应消息格式,响应消息(RFC 2616,第6节)的BNF如下所示:
Response=Status-Line;Section 6.1
*((general-header;Section 4.5
|response-header;Section 6.2
|entity-header)CRLF);Section 7.1
CRLF
[message-body];Section 7.2
Status-Line=HTTP-Version SP Status-Code SP Reason-Phrase CRLF
响应消息的第一行是状态行(stauts-Line),从状态行的BNF可以看到它由协议版本、字状态码以及该状态对应的相关文本短语组成,各部分之间利用空格符隔开,除了最末尾的回车换行外,中间不允许有回车换行符号。
代码2571~2578行用于组建响应消息的状态行字符串。*/
2571.if(con->request.http_version==HTTP_VERSION_1_1){
2572.buffer_copy_string_len(b,CONST_STR_LEN("HTTP/1.1"));
2573.}else{
2574.buffer_copy_string_len(b,CONST_STR_LEN("HTTP/1.0"));
2575.}
2576.buffer_append_long(b,con->http_status);
2577.buffer_append_string_len(b,CONST_STR_LEN(""));
2578.buffer_append_string(b,get_http_status_name(con->http_status));
/*默认情况下,HTTP1.1是保持连接的,而HTTP1.0模式是不保持连接的。因此,HTTP1.1协议并且keep_alive为1时,可以不加Connection响应头域,其他情况都需要添加上Connection响应头域。*/
2579.if(con->request.http_version!=HTTP_VERSION_1_1||con->keep_alive==0){
2580.buffer_append_string_len(b,CONST_STR_LEN("\r\nConnection:"));
2581.if(con->keep_alive){
2582.buffer_append_string_len(b,CONST_STR_LEN("keep-alive"));
2583.}else{
2584.buffer_append_string_len(b,CONST_STR_LEN("close"));
2585.}
2586.}
/*根据判断决定请求响应消息里是否带上常用头域Transfer-Encoding,和代码2428行对应。*/
2587.if(con->response.transfer_encoding&
HTTP_TRANSFER_ENCODING_CHUNKED){
2588.buffer_append_string_len(b,CONST_STR_LEN("\r\nTransfer-Encoding:
chunked"));
2589.}
/*添加所有既定头域。*/
2590./*add all headers*/
2591.for(i=0;i<con->response.headers->used;i++){
2592.data_string*ds;
2593.ds=(data_string*)con->response.headers->data[i];
/*自动忽略空值的响应头,和代码2519行对应。"X-LIGHTTPD-"和"X-Sendfile"属于插件fastcgi的头域,即Lighttpd内部执行处理所需要而并不发送给客户端。*/
2594.if(ds->value->used&&ds->key->used&&
2595.0!=strncasecmp(ds->key->ptr,CONST_STR_LEN("X-LIGHTTPD-"))
&&0!=strcasecmp(ds->key->ptr,"X-Sendfile")){
2596.if(0==strcasecmp(ds->key->ptr,"Date"))have_date=1;
2597.if(0==strcasecmp(ds->key->ptr,"Server"))have_server=1;
2598.if(0==strcasecmp(ds->key->ptr,"Content-Encoding")&&
304==con->http_status)continue;
/*上一个头域结束。*/
2599.buffer_append_string_len(b,CONST_STR_LEN("\r\n"));
2600.buffer_append_string_buffer(b,ds->key);/*头域key。*/
2601.buffer_append_string_len(b,CONST_STR_LEN(":"));/*头域值。*/
2602.#if 0
2603./**
2604.*the value might contain newlines,encode them with at least one
2605.white-space
2606.*/
2607.buffer_append_string_encoded(b,
CONST_BUF_LEN(ds->value),ENCODING_HTTP_HEADER);
2608.#else
2609.buffer_append_string_buffer(b,ds->value);/*组合。*/
2610.#endif
2611.}
2612.}
2613.
2614.if(!have_date){/*如果还没有添加Date头域,则添加一个。*/
2615./*HTTP/1.1 requires a Date:header*/
/*支持HTTP1.1版本的源服务器必须在所有的响应中都包括一个日期头域,除了下面这些情况以外:1)如果响应的状态码为100(继续)或者101(转换协议),那么响应可以根据服务器的选择需要包含一个Date头域。2)如果响应状态码表达了一个服务器的错误,比如500(内部服务器错误)或者503(服务难以获得),那么源服务器就不便于或不可能去产生一个有效的日期。3)如果服务器本身没有时钟,不能提供合理的当前时间的近似值,那么这个响应就完全没必要去包括一个Date头域,同时也就不能给响应指定过期(Expires)或者最后修改日期(Last-Modified)头域值。关于这方面更详细的内容,请读者参考文档RFC 2616第14.18节。*/
2616.buffer_append_string_len(b,CONST_STR_LEN("\r\nDate:"));
2617./*cache the generated timestamp*/
2618.if(srv->cur_ts!=srv->last_generated_date_ts){
2619.buffer_prepare_copy(srv->ts_date_str,255);
2620.strftime(srv->ts_date_str->ptr,srv->ts_date_str->size-1,
2621."%a,%d%b%Y%H:%M:%S GMT",gmtime(&(srv->cur_ts)));
2622.srv->ts_date_str->used=strlen(srv->ts_date_str->ptr)+1;
2623.srv->last_generated_date_ts=srv->cur_ts;
2624.}
2625.buffer_append_string_buffer(b,srv->ts_date_str);
2626.}
2627.if(!have_server){/*添加服务器标记头域。*/
2628.if(buffer_is_empty(con->conf.server_tag)){
2629.buffer_append_string_len(b,CONST_STR_LEN("\r\nServer:"
PACKAGE_NAME"/"PACKAGE_VERSION));
2630.}else if(con->conf.server_tag->used>1){
2631.buffer_append_string_len(b,CONST_STR_LEN("\r\nServer:"));
2632.buffer_append_string_encoded(b,
CONST_BUF_LEN(con->conf.server_tag),
ENCODING_HTTP_HEADER);
2633.}
2634.}
/*添加头域结束行。*/
2635.buffer_append_string_len(b,CONST_STR_LEN("\r\n\r\n"));
2636.con->bytes_header=b->used-1;
2637.if(con->conf.log_response_header){
2638.log_error_write(srv,__FILE__,__LINE__,"sSb","Response-Header:","\n",b);
2639.}
2640.return 0;
2641.}

响应请求处理准备工作执行函数connection_handle_write_prepare()并不会返回-1值(至少从版本1.4.20里的代码来看),因此毫无例外,连接状态将切换到CON_STATE_WRITE(代码658行,图10-5内的状态切换箭头k所示的情况)开始响应请求处理的数据发送工作,完成这个工作的是函数connection_handle_write()(代码745行),如清单10-14所示。

清单10-14 函数connection_handle_write

//connections.c
2642.static int connection_handle_write(server*srv,connection*con){
/*Lighttpd服务器发送响应数据的实际执行函数为network_write_chunkqueue()。*/
2643.switch(network_write_chunkqueue(srv,con,con->write_queue)){
2644.case 0:/*执行成功。*/
2645.if(con->file_finished){/*响应数据全部发送完毕。*/
2646.connection_set_state(srv,con,CON_STATE_RESPONSE_END);
2647.joblist_append(srv,con);
2648.}
2649.break;
2650.case-1:/*error on our side*//*执行失败,服务器端出错。*/
2651.log_error_write(srv,__FILE__,__LINE__,"sd",
"connection closed:write failed on fd",con->fd);
2652.connection_set_state(srv,con,CON_STATE_ERROR);
2653.joblist_append(srv,con);
2654.break;
2655.case-2:/*remote close*//*执行失败,客户端关闭连接。*/
2656.connection_set_state(srv,con,CON_STATE_ERROR);
2657.joblist_append(srv,con);
2658.break;
2659.case 1:
/*执行成功,但由于已经达到设定的最大传输率而停止发送数据,此时数据未全部发送完,将可写状态设置为0,等待下一次(即至少是1秒后)继续发送。*/
2660.con->is_writable=0;
2661./*not finished yet->WRITE*/
2662.break;
2663.}
2664.return 0;
2665.}
//network.c
2666.int network_write_chunkqueue(server*srv,connection*con,chunkqueue*cq){
2667.int ret=-1;
2668.off_t written=0;
2669.#ifdef TCP_CORK
2670.int corked=0;
2671.#endif
2672.server_socket*srv_socket=con->srv_socket;
2673.if(con->conf.global_kbytes_per_second&&
2674.*(con->conf.global_bytes_per_second_cnt_ptr)>
con->conf.global_kbytes_per_second*1024){
2675./*we reached the global traffic limit*//*达到设定的最大数据传输率。*/
2676.con->traffic_limit_reached=1;
2677.joblist_append(srv,con);
2678.return 1;
2679.}
2680.written=cq->bytes_out;/*保存已经发送出去的字节数。*/
2681.#ifdef TCP_CORK
2682./*Linux:put a cork into the socket as we want to combine the write()calls
2683.*but only if we really have multiple chunks
2684.*/
/*选项TCP_NODELAY和TCP_CORK都对网络连接的行为具有十分重要的作用,它们控制了数据包的“Nagle化”,“Nagle化”是指通过自动连接一定量的小缓冲器数据包来组装为更大的数据包以减少发送包的个数而增加网络程序系统的效率。这一过程中使用的Nagle算法是由其发明人John Nagle的名字来命名的,John Nagle在1984年首次用这种方法来尝试解决福特汽车公司的网络拥塞问题(读者可以参考文档RFC 896),该问题描述的是小包问题:如果我们的应用程序一次产生一字节数据,这样会导致网络由于太多的包而过载。所谓的Silly Window Syndrome(愚蠢窗口症候群),指由于普遍终端应用程序每产生一次击键操作就会发送一个数据包,因此典型情况下一个包会拥有一个字节的数据载荷以及40个字节长的包头,于是产生4000%的过载(即1字节的“有用”数据和40字节的“无用”数据),这种情况对于轻负载的网络来说还是可以接受的,但是对于重负载的网络就极有可能受不了而很轻易地就发生拥塞瘫痪。75
Nagle算法的具体做法为:如发送端应用程序欲多次发送包含少量字符的数据包,则发送端会先将第一个少量字符数据包发送出去,而将后面到达的少量字符数据包都缓存起来而不发送,直到收到接收端对前一个数据包报文段的ACK确认后或者攒到了一定数量的数据包(如缓存的数据包字符数据已达到窗口大小的一半或者已达到报文段的最大长度)才发送。
TCP中的Nagle算法是默认启用的(即默认配置),但是某些应用情况下我们需要关闭这个算法,如当我们发送一个较短的请求能得到一个较大的响应时,相关过载与传输的数据总量相比就会比较低,而且如果请求立即发出那么响应时间也会短一些。通过设置套接口描述符的TCP_NODELAY76选项即可禁用Nagle算法,即设置TCP_NODELAY选项表示数据包被立即发送,即使该数据包内只包含最少量的数据。
选项TCP_NODELAY在大多数UNIX系统上都有实现,而TCP_CORK则是Linux系统所特有且相对较新的选项,它首先在内核版本2.4上得以实现。TCP_CORK选项的功能类似于在发送数据管道出口插入一个“塞子”使得发送数据全部被阻塞直到取消TCP_CORK选项(即拔去塞子)或阻塞超时200ms才有可能被发送出去。
选项TCP_NODELAY和TCP_CORK都禁用Nagle算法,但是设置这两个选项导致的行为动作却完全不相同(甚至可以说相对),然而在内核版本2.5.7177后,这两个选项可以相互接合使用。对于某套接口描述符,选项TCP_NODELAY和TCP_CORK同时存在时,选项TCP_CORK起主导作用(即发送数据被阻塞),而选项TCP_NODELAY要在选项TCP_CORK被清除后才起作用,但是在对套接口描述符设置选项TCP_NODELAY时会强制进行一次数据发送,即使当前该套接口描述符已经设置有选项TCP_CORK。
总的来说,当我们需要将一连续的数据组合成一个单独的大块数据包再发送出去时则应考虑使用选项TCP_CORK;而当我们需要及时发送数据和快速获取接收方响应数据时应考虑使用选项TCP_NODELAY。
在Web服务响应中,绝大多数情况都将包含多个数据集合,因此我们需要套接口描述符的TCP_CORK选项,这样能使得传送这些数据之间不存在延迟,在一定程度上提高Lighttpd服务器的性能。
下面代码设置套接口描述符con->fd的TCP_CORK选项,只有当需要合并多个write调用(也就是有多个chunk块存在时)才发生此设置。*/
2685.if(cq->first&&cq->first->next){
2686.corked=1;
2687.setsockopt(con->fd,IPPROTO_TCP,TCP_CORK,&corked,sizeof(corked));
2688.}
2689.#endif
2690.
2691.if(srv_socket->is_ssl){/*调用数据发送函数。*/
2692.#ifdef USE_OPENSSL
2693.ret=srv->network_ssl_backend_write(srv,con,con->ssl,cq);
2694.#endif
2695.}else{
/*函数network_backend_write()是一个回调函数,在后面将会用一章的内容来分析,在此只需要了解该函数返回在本次调用中被发送完的cq链中chunk块数目。*/
2696.ret=srv->network_backend_write(srv,con,con->fd,cq);
2697.}
/*清理cq链中已经使用完毕(即数据已经被发送出去)的chunk块,然后根据判断数据是否已经全部发送完设置ret值。*/
2698.if(ret>=0){
2699.chunkqueue_remove_finished_chunks(cq);
2700.ret=chunkqueue_is_empty(cq)?0:1;
2701.}
2702.
2703.#ifdef TCP_CORK
2704.if(corked){/*取消套接口描述符con->fd上的TCP_CORK选项。*/
2705.corked=0;
2706.setsockopt(con->fd,IPPROTO_TCP,TCP_CORK,&corked,sizeof(corked));
2707.}
2708.#endif
/*现在发送出去的字节总数-之前发送出去的字节总数=本次一共发送出去的字节数。*/
2709.written=cq->bytes_out-written;
2710.con->bytes_written+=written;
2711.con->bytes_written_cur_second+=written;
2712.*(con->conf.global_bytes_per_second_cnt_ptr)+=written;
2713.if(con->conf.kbytes_per_second&&
2714.(con->bytes_written_cur_second>con->conf.kbytes_per_second*1024)){
2715./*we reached the traffic limit*//*达到设定的最大数据传输率。*/
2716.con->traffic_limit_reached=1;
2717.joblist_append(srv,con);
2718.}
2719.return ret;
2720.}
//joblist.c
2721.int joblist_append(server*srv,connection*con){
2722.if(con->in_joblist)return 0;
/*将连接con加入到作业链srv->joblist,但是字段in_joblist在整个1.4.20版本的源码里都未找到有对其进行的非0设置,因此同一个连接con会多次加入到作业链srv->joblist,这可以算是一个bug(仅导致同一个连接con被多次无用处理而使程序运行效率下降,并不会导致严重的错误结果),在Lighttpd版本1.5.0得到修正,即在代码2722行下加上了语句"con->in_joblist=1;"。*/
2723.if(srv->joblist->size==0){
2724.srv->joblist->size=16;
2725.srv->joblist->ptr=malloc(sizeof(*srv->joblist->ptr)*srv->joblist->size);
2726.}else if(srv->joblist->used==srv->joblist->size){
2727.srv->joblist->size+=16;
2728.srv->joblist->ptr=realloc(srv->joblist->ptr,sizeof(*srv->joblist->ptr)*
srv->joblist->size);
2729.}
2730.srv->joblist->ptr[srv->joblist->used++]=con;
2731.return 0;
2732.}

在响应数据未全部发送完的情况下,连接状态将一直保持为CON_STATE_WRITE,这样在再次运行连接有限状态机转换函数connection_state_machine()时仍会执行该状态下的工作,即继续发送未传输完的响应数据。当所有响应数据都发送完后,连接状态将切换到CON_STATE_RESPONSE_END(代码2646行,图10-5内的状态切换箭头t所示情况)进行响应完成后的清理和日志工作以及根据情况进行状态的相应切换。

这有两种情况。一是保持连接情况,也就是说该连接马上有后续的请求数据从客户端传送过来,此时将状态切换到CON_STATE_REQUEST_START(代码668行,图10-5内的状态切换箭头v所示情况)并调用函数connection_reset()将连接管理结构体变量con进行重新初始化以保存管理一个“新”的连接相关信息。二是不保持连接情况,那么很简单,先调用插件的connection_close回调函数进行清理工作,再调用connection_close()进行套接口描述符事件监控的取消、套接口描述符的关闭工作并将连接管理结构体变量con状态设置为CON_STATE_CONNECT以等待接管其他客户端请求处理。

不管哪种情况,至此,Lighttpd应用程序对于客户端一次GET请求的处理和响应过程基本就算完成,当然上面讨论的都是只考虑了最简单的主线流程情况,而未考虑出错异常情况,但至少帮助我们把握和理解GET处理的大体流程。

  POST请求的Web服务响应过程

以前面获得的响应页面提交POST请求,利用Wireshark获取POST请求数据信息,对于Lighttpd的响应数据,由于此处我并没有加载对提交的数据进行动态处理的模块,因此响应的仍然是index.htm的HTML源码数据,和图10-12、图10-13给出的数据相同。

Lighttpd对POST请求的处理和GET请求的处理基本一致,但是由于POST请求有额外的消息主体数据提交,因此连接的状态切换中将包含一个CON_STATE_READ_POST状态,该状态下的执行代码和CON_STATE_READ状态一样(代码724~725行),它们的处理函数connection_handle_read_state()内对从客户端读取传送数据是放在一起进行的,因为请求消息头数据和请求消息体数据在字节上本身就没有差别,仅仅通过我们的解析而使得它们各自具有不同的语法意义。下面来分析POST数据的解析部分(代码966~1034行)。
通过这一部分的代码分析知道,函数首先利用函数connection_handle_read()读取的数据被统一存放在con->read_queue内,通过我们之前解析的Content-Length头域获知本次需要读取的POST数据大小,然后将这些数据逐步从con->read_queue内的各chunk块内转存到con->request_content_queue链内,这些POST数据Lighttpd主程序并不处理,而是交给动态执行模块(比如cgi、fastcgi、scgi等)来解析,因此此处我们分析的源码很简单也很容易看懂。

图 10-13 POST请求提交数据

图10-13显示了利用form表单提交的POST信息。form表单默认提交的信息是按"application/x-www-form-urlencoded"来编码的,即HTML里的form元素的enctype属性值默认为"application/x-www-form-urlencoded",这种编码方式采用URL编码机制将form表单各字段域数据编码为名称/值对并通过字符“&”串联起来形成字符串的形式进行传送。但是如果向服务器传送包含非ASCII字符或二进制的数据(如图片文件)时,那么此时的form表单enctype属性必须设置为"multipart/form-data"78。这两种传送方式传送的内容都装载在消息主体内,都是HTTP协议携带的内容,只是前者为字符串类型(String),而后者则是一个通用的数据对象类型(Object)。另外一个enctype属性为"text/plain"<sup>79</sup>,它表示form表单数据以纯文本形式进行编码,其中不含任何控件或格式字符,通过回车换行连接各字段域数据。这三种编码的示例如表10-1所示,理解它们的格式,对于我们理解POST数据解析功能模块源码有极大帮助。

对于客户端的GET或POST等请求,Lighttpd服务程序的处理主要流程基本一致,从上面的分析我们也可以看到,事实上,Lighttpd对客户端请求响应更多的是协调各插件模块来帮助处理,正因为如此,使我们在需要增加、删除、修改Lighttpd服务功能时变得异常容易。Lighttpd工程源程序提供的插件模块多达40多个,功能自然灵活丰富

10.4 本章总结

本章通过分析一个简单的网络服务通信模型过渡到Lighttpd自身网络服务通信模型,使得我们对Lighttpd网络服务通信流程有了整体的把握。接着按照UNIX/Linux网络编程的一般流程分析了Lighttpd监听套接口描述符和连接套接口描述符的创建过程,并对连接套接口描述符上发生的客户端网络请求(以GET/POST为例)服务响应过程进行了详细的分析。本章包含了Lighttpd应用程序的核心部分,因为也是我们理清Lighttpd程序源码的重点,下一章我们将讲解一个相对比较独立但又是目前网络服务性能研究方面比较热门的话题。

参考文档 lighttpd源码分析 高群凯著          

下载路径:https://download.csdn.net/download/caofengtao1314/10576306 

lighttpd 之十二 网络请求服务响应流程相关推荐

  1. flutter 项目实战二 网络请求

    本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...

  2. Android中使用logger打印完整的okhttp网络请求和响应的所有相关信息(请求行、请求头、请求体、响应行、响应行、响应头、响应体)

    如果你的项目中的网络请求库是Retrofit的话,他的底层封装的是OkHttp,通常调试网络接口时都会将网络请求和响应相关数据通过日志的形式打印出来.OkHttp也提供了一个网络拦截器okhttp-l ...

  3. Linux(b站视频兄弟连)自学笔记第十二章——Linux服务管理

    Linux(b站视频兄弟连)自学笔记第十二章--Linux服务管理 服务分类 RPM包安装服务的管理 独立服务的管理 基于xinetd 的服务管理 源码包服务管理 服务分类 RPM包安装服务的管理 独 ...

  4. linux中ftp的工作原理,Linux系统学习 十二、VSFTP服务—简介与原理

    1.简介与原理 互联网诞生之初就存在三大服务:WWW.FTP.邮件 FTP主要针对企业级,可以设置权限,对不同等级的资料针对不同权限人员显示. 但是像网盘这样的基本没有权限划分. 简介: FTP(Fi ...

  5. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  6. HTTP HTTPS 及网络请求与响应

    HTTP URI的全称为 Uniform Resource Identifier,即统一资源标志符 URL的全称为 Universal Resource Locator,即统一资源定位符 URL是UR ...

  7. 深入操作系统底层分析nginx网络请求及响应过程

    0. 网络传输阶段 比如说主机A是家里windows的一台笔记本电脑,主机B是linux服务器上的一个nginx,其监听80或443等web端口. 在笔记本的浏览器发送了一个http get请求,其数 ...

  8. 孙鑫MFC笔记之十二--网络编程

    网络状况: ü多种通信媒介--有线.无线-- ü不同种类的设备--通用.专用-- ü不同的操作系统--Unix.Windows -- ü不同的应用环境--固定.移动-- ü不同业务种类--分时.交互. ...

  9. 小菜鸡的html初步教程(第十二章 初步构建响应式网站)

    小菜鸡的第三篇博客  今天是3/19,天气不错,跑到自习室来更新博客. 本系列文章仅仅是对基础的HTML5以及CSS进行讲解,更加详细的内容均会附上链接,以便查阅和版权保护.  昨晚我思考了下,决定对 ...

  10. 第十届网络文化节活动和流程

    喵- 北京信息科技大学学生信息科技协会(原北京信息工程学院---网络与创业协会,下简称网协)成立于1997年11月24日,是一个以技术为主体.以网络为依托的科技性学生组织.是这个校园必然的产物.她肩负 ...

最新文章

  1. 线程VS进程,多线程VS多进程,并行VS并发,单核cpuVS多核cpu
  2. c语言if-else的效率比较
  3. 从喧闹与富有中搞懂搜索和拓扑
  4. LAMP 搭建BBS论坛实战
  5. SprinBoot 集成 Flowable/Activiti工作流引擎
  6. 化繁就简 · 万物互联,华为云All-Connect企业级云网络正式发布
  7. Word AddIn编译出现LINK2001 _main
  8. qq影音linux版本下载官网下载软件,QQ for Linux
  9. plc编程技术发展历程
  10. 【2023王道数据结构】【树与二叉树】通过C++实现中序遍历的非递归算法(手动入出栈)C、C++完整实现(可直接运行
  11. matlab速成学习
  12. 使用google earth engine(GEE)提取亚马逊每年森林火灾区域
  13. DevExpress 单元格的设置(可设字体、字号、前景色、背景色)
  14. SSH密匙对登录Linux服务器提示Permissions 0644 for ‘.pem’ are too open
  15. 数据结构--基本概念
  16. mybatis异常:java.lang.ExceptionInInitializerError
  17. MPSK通信系统的Monte Carl仿真(matlab实现,附源码)
  18. cshop是什么开发语言_学会了 C 语言真的可以开发出很多东西吗?
  19. Linux学习 十二单元
  20. 如何使用分布是缓存Hazelcast

热门文章

  1. 解决尝试连接“ECONNREFUSED - 连接被服务器拒绝”失败的问题
  2. 网页截图怎么截一整张_如何对整个网页页面进行截图
  3. 关于浏览器被2345恶意篡改被设置主页
  4. delphi编程实现免杀捆绑
  5. 脸上长了黄褐斑怎么办
  6. 普通话测试-短文60篇文章,附带拼音(51-60篇)
  7. 【Map】map集合及特点_IDEA中Debug追踪
  8. cocos 微信小游戏 加载云端资源
  9. Android 10文档阅读总结
  10. flash网页播放器