lighttpd 之九 配置信息加载

1 概述  

       一个优秀的软件必须给予用户自由来针对其特有的环境条件做不同的设置,Lighttpd在这一方面做得非常不错,它提供用户足够的自由配置权力。用户使用Lighttpd时,可以根据自身的需求选择加载使用Lighttpd的不同功能插件、日志的记录方式、I/O多路复用技术类型、是否使用某些扩展库、对某些Web目录的访问权限设置等。如何以最简单明了的格式组织Lighttpd配置信息,如何将用户的各个具体配置值读取Lighttpd程序得以应用实现是本章将要分析的主要内容。
        本节相关部分源码:

        base.h
        server.h
        configparser.h
        configfile.h
        configfile.c
        configparser.c
        server.c
        configfile-glue.c
        configparser.y

2 配置信息范例与程序加载结果

2.1 Lighttpd配置信息的范例

      先来看一个Lighttpd服务器的配置信息实例(该例子里的设置并不代表任何实际意义,仅仅为了本书后面章节对源码的对照分析讲解),如清单7-1所示。

      //conf
1.#井号是配置文件的行注释,Lighttpd解析配置文件的时候忽略每一行里井号之后的任何字符。
2.#下面是插件选择加载配置,这里选择了三个。前面三行每行以井号开头,因此被忽略。值得注意的是,通过本章后面的学习可以知道mod_indexfile、mod_dirlisting和mod_staticfile这三个插件是被
Lighttpd默认加载的,也就是说,就算配置文件里没有选择加载这三个插件,但是程序里会自动加上。
3.server.modules=(
4.#"mod_indexfile",
5.#"mod_dirlisting",
6.#"mod_staticfile",
7."mod_access",
8."mod_auth",
9."mod_accesslog")
10.#Web站点的主目录
11.server.document-root="/home/lenky/source/lighttpd-1.4.20/lenky/"
12.#Web站点的主IP地址
13.server.bind="127.0.0.1"
14.#Web站点的端口号
15.server.port=3000
16.#设置Lighttpd工作进程数
17.server.max-worker=4
18.#错误日志记录文件
19.server.errorlog="/home/lenky/source/lighttpd-1.4.20/lenky/error.log"
20.#Web站点访问日志记录,在mod_accesslog插件分析章节还要分析到这个配置项
21.accesslog.filename="/home/lenky/source/lighttpd-1.4.20/lenky/access.log"
22.#设置Lighttpd采用的I/O多路复用技术
23.server.event-handler="linux-sysepoll"
24.#目录默认文件设置,在实际使用中这两个配置值只应该指定其中的一个,否则和预期效果不一致,在mod_indexfile插件分析章节还要分析到这两个配置项
25.index-file.names=("index.htm","index.html")
26.server.indexfiles=("index.htm","index.html")
27.#目录禁止列表显示
28.dir-listing.activate="disable"
29.#用户自定义文件扩展名与类型对应关系
30.mimetype.assign=(
31.".pdf"=>"application/pdf",
32.".swf"=>"application/x-shockwave-flash",
33.".tar.gz"=>"application/x-tgz",
34.".tgz"=>"application/x-tgz",
35.".tar"=>"application/x-tar",
36.".mp3"=>"audio/mpeg",
37.".gif"=>"image/gif",
38.".jpg"=>"image/jpeg",
39.".html"=>"text/html",
40.".htm"=>"text/html lenky",
41.".js"=>"text/javascript",
42.".xml"=>"text/xml",
43.".mpeg"=>"video/mpeg",
44.".mpg"=>"video/mpeg",
45.".tar.bz2"=>"application/x-bzip-compressed-tar",
46.#default mime type
47.""=>"application/octet-stream",
48.)
49.#任何其他符合配置信息格式(BNF)的选项设置
50.test.tmp=3
51.#Lighttpd提供的基于IP/端口的虚拟主机
52.$SERVER["socket"]=="127.0.0.1:3001"{
53.server.document-root="/home/lenky/source/lighttpd-1.4.20/www"
54.#对该虚拟主机下的所有aaa目录禁止文本文件访问。
55.$HTTP["url"]=~"^/aaa/"{
56.url.access-deny=(".txt")
57.}#对该虚拟主机下的所有download目录禁止列表。
58.else$HTTP["url"]=~"^/download/"{
59.dir-listing.activate="disable"
60.}#以download.lenky.cn域名访问时改变主目录。
61.else$HTTP["host"]=="www.example.org"{
62.server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
63.}
64.}
65.#其他条件配置信息
66.$HTTP["url"]=~"\.pdf$"{
67.server.range-requests="disable"
68.}
69.
70.$HTTP["remoteip"]=="127.0.0.1"{
71.dir-listing.activate="enable"
72.}
73.else$HTTP["url"]=~"^/www/"{
74.dir-listing.activate="disable"
75.}
76.else$HTTP["useragent"]=~"Google"{
77.url.access-deny=("")
78.}
79.

        从清单7-1中,可以看到Lighttpd配置信息非常丰富,既有全局的设置(比如主目录、端口号),又有仅针对某个插件的设置(比如url.access-deny=("~",".inc")),既有固定的配置(比如dir-listing.activate="enable"),还有条件性的配置信息(比如$HTTP["url"]=~"^/www($|/)"{dir-listing.activate="disable"})。
Lighttpd配置信息的基础语法规则采用BNF范式<sup>46</sup>表示,则如下所示。

option:NAME=VALUE
merge:NAME+=VALUE
NAME:modulename.key
VALUE:(<string>|<integer>|<boolean>|<array>|VALUE[+VALUE]*)
<string>:"text"
<integer>:digit*
<boolean>:("enable"|"disable")
<array>:"("[<string>"=>"]<value>[,[<string>"=>"]<value>]*")"
INCLUDE:"include"VALUE
INCLUDE_SHELL:"include_shell"STRING_VALUE

在进行条件性设置的时候,其语法如下所示。

<field><operator><value>{
...
<field><operator><value>{
...nesting:match only when parent match
}
}
else<field><operator><value>{
...the"else if"block
}

上面语法中的<field>取值如表7-1所示。

    

<operator>取值如表7-2所示。

<value>取值是一串被引号引起来的字符串或者是一个正则表达式。关于Lighttpd配置信息语法的更多高级话题请参考Lighttpd官方文档。



2.2 Lighttpd配置信息范例的加载结果

      用户设置的配置信息被Lighttpd解析后存储在一个array数据结构体内,以供程序各部分从中获取相应的配置值。清单7-2给出了清单7-1提供的配置信息范例被Lighttpd解析后的实际结果(该结果是在源文件configfile.c的config_read()函数体内加上代码array_print(srv->config_context,0)得到的打印结果(汉字注释是后来加上,后面章节将讨论到),Lighttpd程序提供了选项p用于打印全局配置信息)。

      清单7-2 Lighttpd配置信息实例加载结果

    lenky@lenky-desktop:~/source/lighttpd-1.4.20/src$./lighttpd-f conf
(
/*全局(global)块。*/
"global"=>config{
/*全局(global)块内的配置项。*/
var.PID=23310
var.CWD="/home/lenky/source/lighttpd-1.4.20/src"
server.modules=("mod_access","mod_auth","mod_accesslog")
server.document-root="/home/lenky/source/lighttpd-1.4.20/lenky/"
server.bind="127.0.0.1"
dir-listing.activate="disable"
server.port=3000
server.max-worker=4
server.errorlog="/home/lenky/source/lighttpd-1.4.20/lenky/error.log"
accesslog.filename="/home/lenky/source/lighttpd-1.4.20/lenky/access.log"
server.event-handler="linux-sysepoll"
index-file.names=("index.htm","index.html")
server.indexfiles=("index.htm","index.html")
mimetype.assign=(
".pdf"=>"application/pdf",
".swf"=>"application/x-shockwave-flash",
".tar.gz"=>"application/x-tgz",
".tgz"=>"application/x-tgz",
".tar"=>"application/x-tar",
#5
".mp3"=>"audio/mpeg",
".gif"=>"image/gif",
".jpg"=>"image/jpeg",
".html"=>"text/html",
".htm"=>"text/html lenky",
#10
".js"=>"text/javascript",
".xml"=>"text/xml",
".mpeg"=>"video/mpeg",
".mpg"=>"video/mpeg",
".tar.bz2"=>"application/x-bzip-compressed-tar",
#15
""=>"application/octet-stream",
#16
)
test.tmp=3
/*全局(global)块内的子块。*/
$SERVER["socket"]=="127.0.0.1:3001"{
#block 1
server.document-root="/home/lenky/source/lighttpd-1.4.20/www"
$HTTP["url"]=~"^/aaa/"{
#block 2
url.access-deny=(".txt")
}#end of$HTTP["url"]=~"^/aaa/"
else$HTTP["url"]=~"^/download/"{
#block 3
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/download/"
else$HTTP["host"]=="www.example.org"{
#block 4
server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
}#end of$HTTP["host"]=="www.example.org"
}#end of$SERVER["socket"]=="127.0.0.1:3001"
$HTTP["url"]=~"\.pdf$"{
#block 5
server.range-requests="disable"
}#end of$HTTP["url"]=~"\.pdf$"
$HTTP["remoteip"]=="127.0.0.1"{
#block 6
dir-listing.activate="enable"
}#end of$HTTP["remoteip"]=="127.0.0.1"
else$HTTP["url"]=~"^/www/"{
#block 7
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/www/"
else$HTTP["useragent"]=~"Google"{
#block 8
url.access-deny=("")
}#end of$HTTP["useragent"]=~"Google"
},
/*条件(Conditional)块。*/
"global/SERVERsocket==127.0.0.1:3001"=>$SERVER["socket"]=="127.0.0.1:3001"{
#block 1
/*条件(Conditional)块内的配置项。*/
server.document-root="/home/lenky/source/lighttpd-1.4.20/www"
/*条件(Conditional)块内的子条件块。*/
$HTTP["url"]=~"^/aaa/"{
#block 2
url.access-deny=(".txt")
}#end of$HTTP["url"]=~"^/aaa/"
else$HTTP["url"]=~"^/download/"{
#block 3
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/download/"
else$HTTP["host"]=="www.example.org"{
#block 4
server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
}#end of$HTTP["host"]=="www.example.org"
}#end of$SERVER["socket"]=="127.0.0.1:3001",
"global/SERVERsocket==127.0.0.1:3001/HTTPurl=~^/aaa/"=>$HTTP["url"]=~"^/aaa/"{
#block 2
url.access-deny=(".txt")
}#end of$HTTP["url"]=~"^/aaa/"
else$HTTP["url"]=~"^/download/"{
#block 3
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/download/"
else$HTTP["host"]=="www.example.org"{
#block 4
server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
}#end of$HTTP["host"]=="www.example.org",
"global/SERVERsocket==127.0.0.1:3001/HTTPurl=~^/download/"=>$HTTP["url"]=~"^/download/"{
#block 3
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/download/"
else$HTTP["host"]=="www.example.org"{
#block 4
server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
}#end of$HTTP["host"]=="www.example.org",
"global/SERVERsocket==127.0.0.1:3001/HTTPhost==www.example.org"=>
$HTTP["host"]=="www.example.org"{
#block 4
server.document-root="/home/lenky/source/lighttpd-1.4.20/download/"
}#end of$HTTP["host"]=="www.example.org",
#5
"global/HTTPurl=~\.pdf$"=>$HTTP["url"]=~"\.pdf$"{
#block 5
server.range-requests="disable"
}#end of$HTTP["url"]=~"\.pdf$",
"global/HTTPremoteip==127.0.0.1"=>$HTTP["remoteip"]=="127.0.0.1"{
#block 6
dir-listing.activate="enable"
}#end of$HTTP["remoteip"]=="127.0.0.1"
else$HTTP["url"]=~"^/www/"{
#block 7
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/www/"
else$HTTP["useragent"]=~"Google"{
#block 8
url.access-deny=("")
}#end of$HTTP["useragent"]=~"Google",
"global/HTTPurl=~^/www/"=>$HTTP["url"]=~"^/www/"{
#block 7
dir-listing.activate="disable"
}#end of$HTTP["url"]=~"^/www/"
else$HTTP["useragent"]=~"Google"{
#block 8
url.access-deny=("")
}#end of$HTTP["useragent"]=~"Google",
"global/HTTPuseragent=~Google"=>$HTTP["useragent"]=~"Google"{
#block 8
url.access-deny=("")
}#end of$HTTP["useragent"]=~"Google",
#9
)

       贴出这个范例解析结果并不是完全没有任何目的的,一方面,使读者可以对Lighttpd的配置信息有个大概的概念;另一方面,在接下来的配置信息加载源码分析章节可以对照实例,便于理解。

3 加载配置信息的源码分析

3.1 Lighttpd配置信息存储结构

      与Lighttpd配置信息相关的存储结构比较多,首先是定义在头文件base.h里的结构体server&nbsp;(如清单7-3所示),该结构体在Lighttpd通篇源码都会用到,因此以后还会讲到它,这里仅关注其中的几个字段:config、config_touched、config_context、config_storage、srvconf、config_deprecated、config_unsupported。

      清单7-3 Server数据结构定义

       //base.h
80.typedef struct server{
81.server_socket_array srv_sockets;
82./*the errorlog*/
83.int errorlog_fd;
84.enum{ERRORLOG_STDERR,ERRORLOG_FILE,ERRORLOG_SYSLOG}errorlog_mode;
85.buffer*errorlog_buf;
86.fdevents*ev,*ev_ins;
87.buffer_plugin plugins;
88.void*plugin_slots;
89./*counters*/
90.int con_opened;
91.int con_read;
92.int con_written;
93.int con_closed;
94.int ssl_is_init;
95.int max_fds;/*max possible fds*/
96.int cur_fds;/*currently used fds*/
97.int want_fds;/*waiting fds*/
98.int sockets_disabled;
99.size_t max_conns;
100./*buffers*/
101.buffer*parse_full_path;
102.buffer*response_header;
103.buffer*response_range;
104.buffer*tmp_buf;
105.buffer*tmp_chunk_len;
106.buffer*empty_string;/*is necessary for cond_match*/
107.buffer*cond_check_buf;
108./*caches*/
109.#ifdef HAVE_IPV6
110.inet_ntop_cache_type inet_ntop_cache[INET_NTOP_CACHE_MAX];
111.#endif
112.mtime_cache_type mtime_cache[FILE_CACHE_MAX];
113.array*split_vals;
114./*Timestamps*/
115.time_t cur_ts;
116.time_t last_generated_date_ts;
117.time_t last_generated_debug_ts;
118.time_t startup_ts;
119.buffer*ts_debug_str;
120.buffer*ts_date_str;
121./*config-file*/
/*指向config_context->data[0],也就是global配置信息。*/
122.array*config;
/*记录曾被Lighttpd使用过的配置信息key记录。*/
123.array*config_touched;
/*Lighttpd配置文件解析后的上下文,包含配置文件里所有有用信息。*/
124.array*config_context;
/*存储被Lighttpd使用过的配置信息。*/
125.specific_config**config_storage;
/*和Lighttpd服务相关极近的基本全局配置信息,例如服务的IP地址、端口、I/O多路复用技术等。*/
126.server_config srvconf;
/*是否包含有被Lighttpd所摒弃的配置信息标记。*/
127.short int config_deprecated;
/*是否包含有不被Lighttpd所支持的配置信息标记。*/
128.short int config_unsupported;
129.connections*conns;
130.connections*joblist;
131.connections*fdwaitqueue;
132.stat_cache*stat_cache;
133./**
134.*The status array can carry all the status information you want
135.*the key to the array is<module-prefix>.<name>
136.*and the values are counters
137.*
138.*example:
139.*fastcgi.backends=10
140.*fastcgi.active-backends=6
141.*fastcgi.backend.<key>.load=24
142.*fastcgi.backend.<key>....
143.*
144.*fastcgi.backend.<key>.disconnects=...
145.*/
146.array*status;
147.
148.fdevent_handler_t event_handler;
149.int(*network_backend_write)(struct server*srv,connection*con,int fd,chunkqueue*cq);
150.int(*network_backend_read)(struct server*srv,connection*con,int fd,chunkqueue*cq);
151.#ifdef USE_OPENSSL
152.int(*network_ssl_backend_write)(struct server*srv,connection*con,SSL*ssl,chunkqueue*cq);
153.int(*network_ssl_backend_read)(struct server*srv,connection*con,SSL*ssl,chunkqueue*cq);
154.#endif
155.uid_t uid;
156.gid_t gid;
157.}server;
158.

       与配置信息相关的各字段已经在上面源码里给出了基本的介绍,在看完本章剩下的内容后,读者对这些字段的具体作用会更清晰。其中字段config_storage为specific_config类型,字段srvconf为server_config类型,这两个结构体也定义在头文件base.h内,其具体内容如清单7-4所示。

//base.h
159.typedef struct{
160.array*mimetypes;
161.
162./*virtual-servers*/
163.buffer*document_root;
164.buffer*server_name;
165.buffer*error_handler;
166.buffer*server_tag;
167.buffer*dirlist_encoding;
168.buffer*errorfile_prefix;
169.
170.unsigned short max_keep_alive_requests;
171.unsigned short max_keep_alive_idle;
172.unsigned short max_read_idle;
173.unsigned short max_write_idle;
174.unsigned short use_xattr;
175.unsigned short follow_symlink;
176.unsigned short range_requests;
177.
178./*debug*/
179.
180.unsigned short log_file_not_found;
181.unsigned short log_request_header;
182.unsigned short log_request_handling;
183.unsigned short log_response_header;
184.unsigned short log_condition_handling;
185.unsigned short log_ssl_noise;
186.
187.
188./*server wide*/
189.buffer*ssl_pemfile;
190.buffer*ssl_ca_file;
191.buffer*ssl_cipher_list;
192.unsigned short ssl_use_sslv2;
193.
194.unsigned short use_ipv6;
195.unsigned short is_ssl;
196.unsigned short allow_http11;
197.unsigned short etag_use_inode;
198.unsigned short etag_use_mtime;
199.unsigned short etag_use_size;
200.unsigned short force_lowercase_filenames;/*if the FS is case-insensitive,force all files to lower-case*/
201.unsigned short max_request_size;
202.unsigned short kbytes_per_second;/*connection kb/s limit*/
203./*configside*/
204.unsigned short global_kbytes_per_second;/**/
205.
206.off_t global_bytes_per_second_cnt;
207./*server-wide traffic-shaper
208.*
209.*each context has the counter which is inited once
210.*a second by the global_kbytes_per_second config-var
211.*
212.*as soon as global_kbytes_per_second gets below 0
213.*the connected conns are"offline"a little bit
214.*
215.*the problem:
216.*we somehow have to loose our"we are writable"signal
217.*on the way.
218.*
219.*/
220.off_t*global_bytes_per_second_cnt_ptr;/**/
221.
222.#ifdef USE_OPENSSL
223.SSL_CTX*ssl_ctx;
224.#endif
225.}specific_config;
226.
227.typedef struct{
228.unsigned short port;
229.buffer*bindhost;
230.
231.buffer*errorlog_file;
232.unsigned short errorlog_use_syslog;
233.
234.unsigned short dont_daemonize;
235.buffer*changeroot;
236.buffer*username;
237.buffer*groupname;
238.buffer*pid_file;
239.buffer*event_handler;
240.buffer*modules_dir;
241.buffer*network_backend;
242.array*modules;
243.array*upload_tempdirs;
244.unsigned short max_worker;
245.unsigned short max_fds;
246.unsigned short max_conns;
247.unsigned short max_request_size;
248.
249.unsigned short log_request_header_on_error;
250.unsigned short log_state_handling;
251.
252.enum{STAT_CACHE_ENGINE_UNSET,
253.STAT_CACHE_ENGINE_NONE,
254.STAT_CACHE_ENGINE_SIMPLE,
255.#ifdef HAVE_FAM_H
256.STAT_CACHE_ENGINE_FAM
257.#endif
258.}stat_cache_engine;
259.unsigned short enable_cores;
260.}server_config;

清单7-4中的两个结构体specific_config、server_config字段虽然很多,但并不是很复杂,根据各字段的命名就基本可以知道其含义。

3.2 Lighttpd配置信息加载的函数调用流程

       Lighttpd配置信息加载的主要函数调用流程如图7-1所示,图中直角矩形框表示函数(冒号前是函数所在文件,后面为函数名),圆角矩形框指示某函数功能文字说明,由直角矩形框左方到直角矩形框右方的指向箭头表示函数调用,由直角矩形框下方到直角矩形框上方的指向箭头表示顺序执行,后面章节的函数调用流程图都是按这种规则。下面对这几个函数逐个进行分析。

     

1.函数main()取得配置文件路径

       Lighttpd主函数main在进行必要的初始化操作(比如设置信号量、申请内存等)后就以程序运行参数选项f指定的配置文件路径字符串为参数调用函数config_read()开始解析并加载用户配置,如清单7-5所示。
清单7-5 函数main()取得配置文件路径

//server.c
/*这段代码主要是根据用户运行Lighttpd程序时提供的选项进行不同的操作,对于函数getopt的理解请读者参考表7-1。参数argc、argv都是函数main()传递过来的。*/
261.while(-1!=(o=getopt(argc,argv,"f:m:hvVDpt"))){
262.switch(o){
263.case'f':/*指定配置文件路径。*/
/*srv是结构体server的指针变量(已经被函数server_init()初始化),optarg是随函数getopt()一起的全局变量。*/
264.if(config_read(srv,optarg)){/*读入配置信息。*/
265.server_free(srv);/*读入配置信息失败则退出。*/
266.return-1;
267.}
268.break;
269.case'm':/*指定插件保存目录。*/
270.buffer_copy_string(srv->srvconf.modules_dir,optarg);
271.break;
272.case'p':print_config=1;break;/*打印配置信息标记。*/
273.case't':test_config=1;break;/*测试配置信息标记。*/
/*不进行守护化转换标记。*/
274.case'D':srv->srvconf.dont_daemonize=1;break;
275.case'v':show_version();return 0;/*显示版本信息。*/
276.case'V':show_features();return 0;/*显示特征信息。*/
277.case'h':show_help();return 0;/*显示帮助信息。*/
278.default:
279.show_help();
280.server_free(srv);
281.return-1;
282.}
283.}

函数server_init()定义在源文件server.c内,进行server结构体的内存申请和初始化,其源码如清单7-6所示。

//server.c
284.static server*server_init(void){
285.int i;
286.
287.server*srv=calloc(1,sizeof(*srv));/*内存申请。*/
288.assert(srv);
289.#define CLEAN(x)\
290.srv->x=buffer_init();
/*注意这种利用宏来进行同一类操作的应用,在Lighttpd源码里很多地方如此使用。*/
291.CLEAN(response_header);
292.CLEAN(parse_full_path);
293.CLEAN(ts_debug_str);
294.CLEAN(ts_date_str);
295.CLEAN(errorlog_buf);
296.CLEAN(response_range);
297.CLEAN(tmp_buf);
/*不要认为这句代码没起什么作用,事实上它是在为empty_string申请内存空间,并利用空字符串进行初始化。*/
298.srv->empty_string=buffer_init_string("");
299.CLEAN(cond_check_buf);
300.
301.CLEAN(srvconf.errorlog_file);
302.CLEAN(srvconf.groupname);
303.CLEAN(srvconf.username);
304.CLEAN(srvconf.changeroot);
305.CLEAN(srvconf.bindhost);
306.CLEAN(srvconf.event_handler);
307.CLEAN(srvconf.pid_file);
308.
309.CLEAN(tmp_chunk_len);
310.#undef CLEAN
311.
312.#define CLEAN(x)\
313.srv->x=array_init();
314.
315.CLEAN(config_context);
316.CLEAN(config_touched);
317.CLEAN(status);
318.#undef CLEAN
319.
320.for(i=0;i<FILE_CACHE_MAX;i++){
321.srv->mtime_cache[i].mtime=(time_t)-1;
322.srv->mtime_cache[i].str=buffer_init();
323.}
324.
325.srv->cur_ts=time(NULL);
326.srv->startup_ts=srv->cur_ts;
327.
328.srv->conns=calloc(1,sizeof(*srv->conns));
329.assert(srv->conns);
330.
331.srv->joblist=calloc(1,sizeof(*srv->joblist));
332.assert(srv->joblist);
333.
334.srv->fdwaitqueue=calloc(1,sizeof(*srv->fdwaitqueue));
335.assert(srv->fdwaitqueue);
336.
337.srv->srvconf.modules=array_init();
338.srv->srvconf.modules_dir=buffer_init_string(LIBRARY_DIR);
339.srv->srvconf.network_backend=buffer_init();
340.srv->srvconf.upload_tempdirs=array_init();
341.
342./*use syslog*/
343.srv->errorlog_fd=-1;
344.srv->errorlog_mode=ERRORLOG_STDERR;
345.
346.srv->split_vals=array_init();
347.
348.return srv;
349.}

2.函数config_read()读取配置信息

函数config_read()位于源文件configfile.c内,该函数先获取程序的一些默认的属性(例如程序ID、当前工作目录等),接着调用函数config_parse_file()对配置文件进行解析以获得配置信息,然后对用户是否配置有三个必须加载插件进行判断,如果没有程序则添上(这点在前一小节的配置信息范例注释里提到过),最后调用函数config_insert()根据配置信息设置程序里对应变量值,使得用户的设置在程序里得以实际表现,如清单7-7所示。
清单7-7 函数config_read()读取配置信息

//configfile.c
350.int config_read(server*srv,const char*fn){
/*config_t为一结构体类型,定义在头文件configfile.h内:
//configfile.h
typedef struct{
server*srv;
int ok;
array*all_configs;
array*configs_stack;/*to parse nested block*/
data_config*current;/*current started with{*/
buffer*basedir;
}config_t;
*/
351.config_t context;
352.data_config*dc;
353.data_integer*dpid;
354.data_string*dcwd;
355.int ret;
356.char*pos;
357.data_array*modules;
/*该函数进行context数据结构的初始化,内存分配以及初值设定,定义在configfile.c内。
//configfile.c
static void context_init(server*srv,config_t*context){
context->srv=srv;
context->ok=1;
context->configs_stack=array_init();
context->configs_stack->is_weakref=1;
context->basedir=buffer_init();
}
*/
358.context_init(srv,&context);
/*all_configs指向config_context,config_context已经在函数server_init()里进行过结构初始化了。这里将all_configs和config_context指向同一内存块,因此该函数后面对all_configs的修改就是对config_context的修改。*/
359.context.all_configs=srv->config_context;
360.
361.pos=strrchr(fn,
362.#ifdef__WIN32
363.'\\'
364.#else
365.'/'
366.#endif
367.);
368.if(pos){
/*配置文件的存放目录保存在basedir中。*/
369.buffer_copy_string_len(context.basedir,fn,pos-fn+1);
370.fn=pos+1;/*fn仅指向文件名。*/
371.}
372.
373.dc=data_config_init();/*结构体data_config变量的初始化。*/
/*设置key为"global"。*/
374.buffer_copy_string_len(dc->key,CONST_STR_LEN("global"));
/*此时应该是还没有任何配置信息进入存储数组。*/
375.assert(context.all_configs->used==0);
/*到存储数组的位置反映射,也就是存储其在存储数组里的位置索引。*/
376.dc->context_ndx=context.all_configs->used;
/*保存到存储数组内。*/
377.array_insert_unique(context.all_configs,(data_unset*)dc);
/*在解析配置文件的时候将用到这个变量。*/
378.context.current=dc;
379.
380./*default context*/
/*收集一些默认的配置属性。必须看清这个赋值,dc是array结构体context.all_configs的第一个数据元素,而又context.all_configs=srv->config_context,所以srv->config==srv->config_context->data[0]。*/
381.srv->config=dc->value;
/*加入程序ID信息。*/
382.dpid=data_integer_init();
/*函数getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。*/
383.dpid->value=getpid();
384.buffer_copy_string_len(dpid->key,CONST_STR_LEN("var.PID"));
385.array_insert_unique(srv->config,(data_unset*)dpid);
/*加入程序当前工作目录信息。*/
386.dcwd=data_string_init();
387.buffer_prepare_copy(dcwd->value,1024);
388.if(NULL!=getcwd(dcwd->value->ptr,dcwd->value->size-1)){
389.dcwd->value->used=strlen(dcwd->value->ptr)+1;
390.buffer_copy_string_len(dcwd->key,CONST_STR_LEN("var.CWD"));
391.array_insert_unique(srv->config,(data_unset*)dcwd);
392.}
393.
394.ret=config_parse_file(srv,&context,fn);/*解析配置文件。*/
395.
396./*remains nothing if parser is ok*/
/*诊断是否解析完成。*/
397.assert(!(0==ret&&context.ok&&0!=context.configs_stack->used));
/*释放不再用了的内存空间。
函数context_free定义在源文件configfile.c内:
//configfile.c
static void context_free(config_t*context){
array_free(context->configs_stack);
buffer_free(context->basedir);
}
*/
398.context_free(&context);
/*判断是否解析成功。*/
399.if(0!=ret){
400.return ret;
401.}
/*保证全局配置信息存在。*/
402.if(NULL!=(dc=(data_config*)array_get_element(srv->config_context,"global"))){
403.srv->config=dc->value;
404.}else{
405.return-1;
406.}
/*下列三个插件mod_indexfile,mod_dirlisting,mod_staticfile是必须要加载的。*/
407.if(NULL!=(modules=(data_array*)array_get_element(srv->config,"server.modules"))){
408.data_string*ds;
409.data_array*prepends;
/*检查确定是array类型数据结构。*/
410.if(modules->type!=TYPE_ARRAY){
411.fprintf(stderr,"server.modules must be an array");
412.return-1;
413.}
414.prepends=data_array_init();
415.
416./*prepend default modules*/
/*下面这么一大段代码都是为了保证插件mod_indexfile添加在插件序列的最前面。因为对于客户端的请求,插件的处理是一个接一个进行的,有的插件的处理必须在其他插件的前面进行,比如客户端对服务器某目录的访问,那么应该首先是由插件mod_indexfile处理,比如在该目录下查找是否有默认页面显示,如果有则直接显示给客户端,请求处理结束。否则将请求交给下一个插件处理,比如mod_dirlisting插件,插件mod_dirlisting根据用户配置进行目录列表显示或显示错误或继续交给下一个插件处理等。这个介绍比较粗糙,后面在插件源码解析章节将详细讲解这个过程。*/
417.if(NULL==array_get_element(modules->value,"mod_indexfile")){
418.ds=data_string_init();
419.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_indexfile"));
420.array_insert_unique(prepends->value,(data_unset*)ds);
421.}
422.
423.prepends=(data_array*)configparser_merge_data((data_unset*)prepends,(data_unset*)modules);/*将modules合并到prepends并返回prepends。*/
424.buffer_copy_string_buffer(prepends->key,modules->key);/*复制key。*/
425.array_replace(srv->config,(data_unset*)prepends);/*替换。*/
426.modules->free((data_unset*)modules);/*释放。*/
427.modules=prepends;/*重新指向。*/
428.
429./*append default modules*/
430./*添加mod_dirlisting默认插件。*/
431.if(NULL==array_get_element(modules->value,"mod_dirlisting")){
432.ds=data_string_init();
433.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_dirlisting"));
434.array_insert_unique(modules->value,(data_unset*)ds);
435.}
436./*添加mod_staticfile默认插件。*/
437.if(NULL==array_get_element(modules->value,"mod_staticfile")){
438.ds=data_string_init();
439.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_staticfile"));
440.array_insert_unique(modules->value,(data_unset*)ds);
441.}
442.}else{
443.data_string*ds;
444.
445.modules=data_array_init();
446./*server.modules is not set*/
/*当用户没有开启任何插件的时候,程序按顺序自动添加mod_indexfile、mod_dirlisting、mod_staticfile这三个插件,同样还是保证插件mod_indexfile添加在最前面。*/
447.ds=data_string_init();
448.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_indexfile"));
449.array_insert_unique(modules->value,(data_unset*)ds);
450.
451.ds=data_string_init();
452.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_dirlisting"));
453.array_insert_unique(modules->value,(data_unset*)ds);
454.
455.ds=data_string_init();
456.buffer_copy_string_len(ds->value,CONST_STR_LEN("mod_staticfile"));
457.array_insert_unique(modules->value,(data_unset*)ds);
458.
459.buffer_copy_string_len(modules->key,CONST_STR_LEN("server.modules"));
460.array_insert_unique(srv->config,(data_unset*)modules);
461.}
/*调用函数config_insert()根据配置信息设置程序里对应变量值。*/
462.if(0!=config_insert(srv)){
463.return-1;
464.}
465.return 0;
466.}
467.
//configparser.c
468./*op1 is to be eat/return by this function if success,op1->key is not cared
469.op2 is left untouch,unreferenced
470.*/
/*合并op1和op2两参数。*/
471.data_unset*configparser_merge_data(data_unset*op1,const data_unset*op2){
472./*type mismatch*/
/*当两参数类型不匹配时,有两种情况可以进行数据合并。*/
473.if(op1->type!=op2->type){
474.if(op1->type==TYPE_STRING&&op2->type==TYPE_INTEGER){
475.data_string*ds=(data_string*)op1;
476.buffer_append_long(ds->value,((data_integer*)op2)->value);
477.return op1;
478.}else if(op1->type==TYPE_INTEGER&&op2->type==TYPE_STRING){
479.data_string*ds=data_string_init();
480.buffer_append_long(ds->value,((data_integer*)op1)->value);
481.buffer_append_string_buffer(ds->value,((data_string*)op2)->value);
482.op1->free(op1);
483.return(data_unset*)ds;
484.}else{/*打印错误。*/
485.fprintf(stderr,"data type mismatch,cannot be merge\n");
486.return NULL;
487.}
488.}
/*当两参数类型匹配情况,分别按各类型进行合并操作。*/
489.switch(op1->type){
490.case TYPE_STRING:
491.buffer_append_string_buffer(((data_string*)op1)->value,
((data_string*)op2)->value);
492.break;
493.case TYPE_INTEGER:
494.((data_integer*)op1)->value+=((data_integer*)op2)->value;
495.break;
496.case TYPE_ARRAY:{
497.array*dst=((data_array*)op1)->value;
498.array*src=((data_array*)op2)->value;
499.data_unset*du;
500.size_t i;
501.
502.for(i=0;i<src->used;i++){
503.du=(data_unset*)src->data[i];
504.if(du){
505.array_insert_unique(dst,du->copy(du));
506.}
507.}
508.break;
509.default:
510.assert(0);
511.break;
512.}
513.}
514.return op1;/*返回结果。*/
515.}

3.函数config_parse_file()解析配置文件

      函数config_parse_file()位于源文件Configfile.c内,该函数主要调用另外两个函数tokenizer_init()和config_parse()来解析配置文件获取用户对Lighttpd的配置值。在阅读该函数源码前先看看另外两个结构体tokenizer_t和stream及其操作。
结构体tokenizer_t定义在源文件Configfile.c内,各字段如清单7-8所示。
清单7-8 tokenizer_t数据结构定义

//configfile.c
516.typedef struct{
/*临时变量48。*/
517.int foo;
518.int bar;
/*指向文件名。*/
519.const buffer*source;
/*指向文件内容的字符指针。*/
520.const char*input;
/*类似于打开的文件的当前文件偏移量(current file offset,CFO),以下简称为CFO。CFO通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于CFO,并且使CFO增大,增量为读写的字节数。文件被打开时,CFO会被初始化为0,除非使用了O_APPEND。*/
521.size_t offset;
522.size_t size;/*文件大小。*/
523.
524.int line_pos;/*行内位置,即列号。*/
525.int line;/*行号。*/
526.
527.int in_key;/*符号标记。*/
528.int in_brace;/*记录当前处理进入到括号内的层数。*/
529.int in_cond;/*当前状态是否进入了条件判断标记。*/
530.}tokenizer_t;

结构体stream定义在头文件Stream.h内,只有两个字段,另外还有两个stream的操作函数声明,如清单7-9所示。
清单7-9 stream数据结构定义与操作

//stream.h
531.#ifndef_STREAM_H_
532.#define_STREAM_H_
533.
534.#include"buffer.h"
535.
536.typedef struct{
537.char*start;/*当前流位置指针。*/
538.off_t size;/*流长度,也就是对应的文件大小。*/
539.}stream;
540.
541.int stream_open(stream*f,buffer*fn);/*函数声明。*/
542.int stream_close(stream*f);/*函数声明。*/
543.
544.#endif
545.
546.//stream.c
547.#ifndef O_BINARY
548.#define O_BINARY 0
549.#endif
/*打开文件并map到内存(起始地址和内存长度存储在stream结构体参数f内),出错返回-1。*/
550.int stream_open(stream*f,buffer*fn){
551.struct stat st;
552.#ifdef HAVE_MMAP
553.int fd;
554.#elif defined__WIN32
555.HANDLE*fh,*mh;
556.void*p;
557.#endif
558.f->start=NULL;
559.if(-1==stat(fn->ptr,&st)){/*获取指定文件属性。*/
560.return-1;
561.}
562.f->size=st.st_size;/*获取文件的大小属性。*/
563.#ifdef HAVE_MMAP
564.if(-1==(fd=open(fn->ptr,O_RDONLY|O_BINARY))){/*打开文件。*/
565.return-1;
566.}
567.f->start=mmap(0,f->size,PROT_READ,MAP_SHARED,fd,0);/*映射。*/
568.close(fd);/*关闭文件。*/
569.if(MAP_FAILED==f->start){/*映射失败。*/
570.return-1;
571.}
572.#elif defined__WIN32/*Windows环境。*/
573.fh=CreateFile(fn->ptr,
574.GENERIC_READ,
575.FILE_SHARE_READ,
576.NULL,
577.OPEN_EXISTING,
578.FILE_ATTRIBUTE_READONLY,
579.NULL);
580.if(!fh)return-1;
581.mh=CreateFileMapping(fh,
582.NULL,
583.PAGE_READONLY,
584.(sizeof(off_t)>4)?f->size>>32:0,
585.f->size&0xffffffff,
586.NULL);
587.if(!mh){
588./*
589.LPVOID lpMsgBuf;
590.FormatMessage(
591.FORMAT_MESSAGE_ALLOCATE_BUFFER|
592.FORMAT_MESSAGE_FROM_SYSTEM,
593.NULL,
594.GetLastError(),
595.MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
596.(LPTSTR)&lpMsgBuf,
597.0,NULL);
598.*/
599.return-1;
600.}
601.
602.p=MapViewOfFile(mh,
603.FILE_MAP_READ,
604.0,
605.0,
606.0);
607.CloseHandle(mh);
608.CloseHandle(fh);
609.
610.f->start=p;
611.#else/*编译强制出错。*/
612.#error no mmap found
613.#endif
614.
615.return 0;
616.}
/*关闭文件内存映射。*/
617.int stream_close(stream*f){
618.#ifdef HAVE_MMAP
619.if(f->start)munmap(f->start,f->size);
620.#elif defined(__WIN32)
621.if(f->start)UnmapViewOfFile(f->start);
622.#endif
623.
624.f->start=NULL;
625.
626.return 0;
627.}

下面再来看看函数config_parse_file()的源码,如清单7-10所示。
清单7-10 函数config_parse_file

//configfile.c
628.int config_parse_file(server*srv,config_t*context,const char*fn){
629.tokenizer_t t;
630.stream s;
631.int ret;
632.buffer*filename;
/*配置文件路径,当用户只指定了配置文件名时,如执行lighttpd-f lighttpd.conf。*/
633.if(buffer_is_empty(context->basedir)||
634.(fn[0]=='/'||fn[0]=='\\')||
635.(fn[0]=='.'&&(fn[1]=='/'||fn[1]=='\\'))){
636.filename=buffer_init_string(fn);
637.}else{
638.filename=buffer_init_buffer(context->basedir);/*组合目录名和文件名。*/
639.buffer_append_string(filename,fn);
640.}
641.
642.if(0!=stream_open(&s,filename)){/*将配置文件打开并映射到内存。*/
/*打开文件出错,但是有两种可能。*/
643.if(s.size==0){/*一种可能就是配置文件内容为空。*/
644./*the file was empty,nothing to parse*/
645.ret=0;
646.}else{/*其他可能。*/
647.log_error_write(srv,__FILE__,__LINE__,"sbss",
648."opening configfile",filename,"failed:",strerror(errno));
649.ret=-1;
650.}
651.}else{
652.tokenizer_init(&t,filename,s.start,s.size);/*对结构体t的初始化。*/
653.ret=config_parse(srv,context,&t);
654.}
655.
656.stream_close(&s);/*关闭映射。*/
657.buffer_free(filename);/*释放配置文件路径字符串所占内存。*/
658.return ret;
659.}
660.static int tokenizer_init(tokenizer_t*t,const buffer*source,const char
*input,size_t size){
661.t->source=source;
662.t->input=input;
663.t->size=size;
664.t->offset=0;
665.t->line=1;
666.t->line_pos=1;
667.
668.t->in_key=1;
669.t->in_brace=0;
670.t->in_cond=0;
671.return 0;
672.}
673.

4.函数config_parse()用于完成对配置文件的语法分析

     函数config_parse()完成对Lighttpd配置文件实际的词法分析,它使用一个词法分析器(LEMON语法分析生成器<sup>49</sup>)来对Lighttpd配置文件里的语法规则(用BNF描述的上下文无关语言且语法满足LALR(1)文法)进行分析处理并将对应的分析结果存储到参数context中隐性传出,函数返回0如果执行成功,否则返回-1。该函数涉及的知识已经完全属于编译原理领域课程,和作者写本书的原意相距甚远,因此在此对该函数就不做过多详细解释,以免误导读者<sup>50</sup>,读者可以参考关于这方面的专注<sup>51</sup>。这里只分析一下该函数得到的结果数据的组 […]

int config_parse_file(server*srv,config_t*context,const char*fn);

在函数config_read()里采用"ret=config_parse_file(srv,&context,fn);"来调用,如果函数config_parse_file()执行成功,ret为0,同时解析结果存储在参数context的字段all_configs内(即context->all_configs),也既是srv->config_context(因为这两个字段指向同一处),所以利用srv->config_context打印得到的图7-2就是最直观的配置文件通过函数config_read()解析得到的结果数据组织结构。srv->config_context是array指针类型,其指向元素为结构体data_config,该结构体在前面章节提到过,为了更容易理解图7-2,这里有必要再次对它的各字段以及打印函数data_config_print()做分析。
data_config结构体定义在头文件array.h内,如清单7-11所示。
清单7-11 data_config数据结构定义

//array.h
674./**
675.*possible compare ops in the configfile parser
676.*/
677.typedef enum{
678.CONFIG_COND_UNSET,
679.CONFIG_COND_EQ,/**==*/
680.CONFIG_COND_MATCH,/**=~*/
681.CONFIG_COND_NE,/**!=*/
682.CONFIG_COND_NOMATCH/**!~*/
683.}config_cond_t;
684.
685./**
686.*possible fields to match against
687.*/
688.typedef enum{
689.COMP_UNSET,
690.COMP_SERVER_SOCKET,
691.COMP_HTTP_URL,
692.COMP_HTTP_HOST,
693.COMP_HTTP_REFERER,
694.COMP_HTTP_USER_AGENT,
695.COMP_HTTP_COOKIE,
696.COMP_HTTP_REMOTE_IP,
697.COMP_HTTP_QUERY_STRING,
698.COMP_HTTP_SCHEME,
699.COMP_HTTP_REQUEST_METHOD,
700.
701.COMP_LAST_ELEMENT
702.}comp_key_t;
703.
704./*$HTTP["host"]=="incremental.home.kneschke.de"{...}
705.*for print:comp_key op string
706.*for compare:comp cond string/regex
707.*/
708.
709.typedef struct_data_config data_config;
710.struct_data_config{
711.DATA_UNSET;
712.array*value;/*当前块的所有配置项。*/
/*下面四个字段和后面的string记录条件块的状态条件。比如:"$HTTP["remoteip"]=="127.0.0.1"{",则comp_key为"$HTTP["remoteip"]",op为“==”,comp为"COMP_HTTP_REMOTE_IP",cond为"CONFIG_COND_EQ",string为“127.0.0.1”。*/
713.buffer*comp_key;
714.comp_key_t comp;
715.config_cond_t cond;
716.buffer*op;
717.int context_ndx;/*more or less like an id*/
718.array*childs;/*该块下的所有其他块(即子块)。*/
719./*nested*/
720.data_config*parent;/*包含本块的块(即父块)。*/
721./*for chaining only*/
/*链接块的前一块和后一块。所谓链接块是指存在前后关联的块,比如if...else...。*/
722.data_config*prev;
723.data_config*next;
724.buffer*string;
725.#ifdef HAVE_PCRE_H
726.pcre*regex;
727.pcre_extra*regex_study;
728.#endif
729.};

各字段含义很清晰,函数data_config_print()如清单7-12所示。
清单7-12 函数data_config_print

//data_config.c
730.static void data_config_print(const data_unset*d,int depth){
731.data_config*ds=(data_config*)d;
732.array*a=(array*)ds->value;
733.size_t i;
734.size_t maxlen;
735.
736.if(0==ds->context_ndx){
737.fprintf(stdout,"config{\n");
738.}
739.else{/*打印当前配置信息项字段。*/
740.fprintf(stdout,"$%s%s\"%s\"{\n",
741.ds->comp_key->ptr,ds->op->ptr,ds->string->ptr);
742.array_print_indent(depth+1);
743.fprintf(stdout,"#block%d\n",ds->context_ndx);
744.}
745.depth++;
746.maxlen=array_get_max_key_length(a);
747.for(i=0;i<a->used;i++){
748.data_unset*du=a->data[i];
749.size_t len=strlen(du->key->ptr);
750.size_t j;
751.array_print_indent(depth);
752.fprintf(stdout,"%s",du->key->ptr);
753.for(j=maxlen-len;j>0;j--){
754.fprintf(stdout,"");
755.}
756.fprintf(stdout,"=");
757.du->print(du,depth);
758.fprintf(stdout,"\n");
759.}
/*打印孩子节点项。*/
760.if(ds->childs){
761.fprintf(stdout,"\n");
762.for(i=0;i<ds->childs->used;i++){
763.data_unset*du=ds->childs->data[i];
764.
765./*only the 1st block of chaining*/
/*只打印链接的第一项,剩下的将在后面打印。*/
766.if(NULL==((data_config*)du)->prev){
767.fprintf(stdout,"\n");
768.array_print_indent(depth);
769.du->print(du,depth);
770.fprintf(stdout,"\n");
771.}
772.}
773.}
774.depth--;
775.array_print_indent(depth);
776.fprintf(stdout,"}");
777.if(0!=ds->context_ndx){
778.fprintf(stdout,"#end of$%s%s\"%s\"",
779.ds->comp_key->ptr,ds->op->ptr,ds->string->ptr);
780.}
/*打印链接下一个项。*/
781.if(ds->next){
782.fprintf(stdout,"\n");
783.array_print_indent(depth);
784.fprintf(stdout,"else");
785.ds->next->print((data_unset*)ds->next,depth);
786.}
787.}

有了对结构体data_config以及打印函数的理解,对照分析图7-2就容易多了。首先,配置信息被归为很多块(block),最顶层的为全局(global)块,其他的为条件(conditional)块,每一块对应一个data_config结构体变量,通过data_config结构体的四个字段parent、childs、prev和next将各块关联起来。以清单7-1配置信息为例,下面给出它们之间相互的组织结构如图7-2所示(各结构体未标出的字段都指向NULL)。

5.函数config_insert()将配置信息转换成对应的变量值

       函数config_insert()完成将(部分关心的)配置信息到对应的变量值的实际转换。Lighttpd配置文件通过函数config_parse()解析之后,统一存储在server结构体的array类型字段config_context内,其配置值还没有转变成程序里对应相关变量的值,因此有必要有这么一个函数来完成这个功能,将配置值分配到Lighttpd程序的各个变量中去,使得用户的配置在程序的运行中得以表现。

       先来看一个与配置值有关的结构体config_values_t,该结构体定义在头文件base.h内,与之有关的另外两个结构体config_values_type_t、config_scope_type_t也一同出现,如清单7-13所示。
清单7-13 config_values_t数据结构定义

   //base.h
788.typedef enum{T_CONFIG_UNSET,/*未知。*/
789.T_CONFIG_STRING,/*字符串。*/
790.T_CONFIG_SHORT,/*短整类型。*/
791.T_CONFIG_BOOLEAN,/*布尔类型。*/
792.T_CONFIG_ARRAY,/*数组类型。*/
793.T_CONFIG_LOCAL,/*本地类型。*/
794.T_CONFIG_DEPRECATED,/*已被摒弃,不再使用的类型。*/
795.T_CONFIG_UNSUPPORTED/*不被支持类型。*/
796.}config_values_type_t;
797.
798.typedef enum{T_CONFIG_SCOPE_UNSET,/*作用域未知。*/
799.T_CONFIG_SCOPE_SERVER,/*服务器全局作用域。*/
800.T_CONFIG_SCOPE_CONNECTION/*请求连接局部作用域。*/
801.}config_scope_type_t;
802.
803.typedef struct{
804.const char*key;/*配置信息key。*/
805.void*destination;/*配置信息值的保存位置,根据值类型不同而指
向不同类型变量,如unsigned short、buffer、array等。*/
806.
807.config_values_type_t type;/*类型。*/
808.config_scope_type_t scope;/*作用域。*/
809.}config_values_t;

      这几个结构体都很简单,但是它们从类型和作用域两方面对配置项做了严格的定义,这对于接下来的配置值设置正确与否的判断以及成功转换都至关重要,比如布尔类型的配置项"server.follow-symlink",其对应的值必定只能是"enable"或"disable",对于其他情况的值设置都是错误的("ERROR:unexpected value for key:server.follow-symlink")。下面来具体看这个从配置值到变量值的转换过程,如清单7-14所示。

      //configfile.c
/*这里列出的config_insert()函数源码省略了部分不影响读者理解其功能含义的部分重复性代码,具体函数源码请读者参看源文件configfile.c。*/
810.static int config_insert(server*srv){
811.size_t i;
812.int ret=0;
813.buffer*stat_cache_string;
/*cv里列出的都是Lighttpd主程序关心的配置值。还有很多配置信息是主程序并不关心的,比如属于某一特定插件的配置信息(这些信息的使用将在关于插件的内容中详细讲解。*/
814.config_values_t cv[]={
815.{"server.bind",NULL,
816.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*0*/
817.{"server.errorlog",NULL,
818.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*1*/
819.{"server.errorfile-prefix",NULL,
820.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*2*/
821.{"server.chroot",NULL,
822.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*3*/
823.{"server.username",NULL,
824.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*4*/
825.{"server.groupname",NULL,
826.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*5*/
827.{"server.port",NULL,
828.T_CONFIG_SHORT,T_CONFIG_SCOPE_SERVER},/*6*/
829.{"server.tag",NULL,
830.T_CONFIG_STRING,T_CONFIG_SCOPE_CONNECTION},/*7*/
831.{"server.use-ipv6",NULL,
832.T_CONFIG_BOOLEAN,T_CONFIG_SCOPE_CONNECTION},/*8*/
833.{"server.modules",NULL,
834.T_CONFIG_ARRAY,T_CONFIG_SCOPE_SERVER},/*9*/
835.
836.{"server.event-handler",NULL,
837.T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER},/*10*/
838.//……省略……
839.{"server.stat-cache-engine",NULL,
840.T_CONFIG_STRING,T_CONFIG_SCOPE_CONNECTION},/*42*/
841.//……省略……
842.
/*一共有51个项目主程序关心的(也即是要被转换保存的),为了节约版面,这里省略了大部分。*/
843./*下面是已经被摒弃的配置选项,提示使用更新的选项。*/
844.{"server.host","use server.bind instead",
845.T_CONFIG_DEPRECATED,T_CONFIG_SCOPE_UNSET},
846.{"server.docroot","use server.document-root instead",
847.T_CONFIG_DEPRECATED,T_CONFIG_SCOPE_UNSET},
848.{"server.virtual-root",
849."load mod_simple_vhost and use simple-vhost.server-root instead",
850.T_CONFIG_DEPRECATED,T_CONFIG_SCOPE_UNSET},
851.//……省略……
/*在循环遍历该数组的时候,最后这个元素用作数组结束标记判断。*/
852.{NULL,NULL,
853.T_CONFIG_UNSET,T_CONFIG_SCOPE_UNSET}
854.};
/*下面这些值的赋值将cv项目和配置变量指向同一个位置,从而对cv项的赋值就是对相应配置变量的赋值,从而做到当该函数退出的时候,就算cv被清除掉(cv是局部变量),但从配置值转换而来的配置变量值依旧可以通过相应配置变量来访问到,达到预定目的。这里是基本的全局配置信息,对应的设置在srv->srvconf结构体各字段内。*/
855./*0*/
856.cv[0].destination=srv->srvconf.bindhost;
857.cv[1].destination=srv->srvconf.errorlog_file;
858.cv[3].destination=srv->srvconf.changeroot;
859.cv[4].destination=srv->srvconf.username;
860.cv[5].destination=srv->srvconf.groupname;
861.cv[6].destination=&(srv->srvconf.port);
862.
863.cv[9].destination=srv->srvconf.modules;
864.cv[10].destination=srv->srvconf.event_handler;
865.//……省略……
866.stat_cache_string=buffer_init();
867.cv[42].destination=stat_cache_string;
868.//……省略……
869.srv->config_storage=calloc(1,srv->config_context->used*sizeof(specific_config*));
870.assert(srv->config_storage);
/*获取其他配置信息,大部分是条件判断配置信息。*/
871.for(i=0;i<srv->config_context->used;i++){
872.specific_config*s;
873.s=calloc(1,sizeof(specific_config));
874.assert(s);
875.s->document_root=buffer_init();
876.s->mimetypes=array_init();
877.s->server_name=buffer_init();
878.//……省略……
/*进行默认设置。*/
879.s->range_requests=1;
880.s->force_lowercase_filenames=0;
881.s->global_kbytes_per_second=0;
882.s->global_bytes_per_second_cnt=0;
883.s->global_bytes_per_second_cnt_ptr=&s->global_bytes_per_second_cnt;
884.
/*下面同样也是为了将配置值存入对应变量内而将cv和对应变量进行等同赋值。*/
885.cv[2].destination=s->errorfile_prefix;
886.
887.cv[7].destination=s->server_tag;
888.cv[8].destination=&(s->use_ipv6);
889.//……省略……
/*保存在config_storage中,config_storage的每个元素的值有部分是完全一样的(即全局配置信息),不同的只是该循环内设置的选项。*/
890.srv->config_storage[i]=s;
/*根据cv结构体数组每个元素的key从配置信息存储数组data中获取配置信息值value保存到cv结构体各个元素中的destination字段(即完成对Lighttpd对应变量值的设置)。出错返回-1,否则返回0。*/
891.if(0!=(ret=config_insert_values_global(srv,
892.((data_config*)srv->config_context->data[i])->value,cv))){
893.break;
894.}
895.}
/*stat缓存存储器引擎设置转换。*/
896.if(buffer_is_empty(stat_cache_string)){
897.srv->srvconf.stat_cache_engine=STAT_CACHE_ENGINE_SIMPLE;
898.}else if(buffer_is_equal_string(stat_cache_string,CONST_STR_LEN("simple"))){
899.srv->srvconf.stat_cache_engine=STAT_CACHE_ENGINE_SIMPLE;
900.#ifdef HAVE_FAM_H
901.}else if(buffer_is_equal_string(stat_cache_string,CONST_STR_LEN("fam"))){
902.srv->srvconf.stat_cache_engine=STAT_CACHE_ENGINE_FAM;
903.#endif
904.}else if(buffer_is_equal_string(stat_cache_string,CONST_STR_LEN("disable"))){
905.srv->srvconf.stat_cache_engine=STAT_CACHE_ENGINE_NONE;
906.}else{
907.log_error_write(srv,__FILE__,__LINE__,"sb",
908."server.stat-cache-engine can be one of\"disable\",\"simple\","
909.#ifdef HAVE_FAM_H
910."\"fam\","
911.#endif
912."but not:",stat_cache_string);
913.ret=HANDLER_ERROR;
914.}
915.
916.buffer_free(stat_cache_string);
917.
918.return ret;
919.
920.}

6.函数config_insert_values_global()等将完成实际配置值到变量值的转换

    函数config_insert_values_global()和config_insert_values_internal()(如清单7-15所示)等相关函数都位于源文件Configfile-glue.c内,函数config_insert_values_global()先逐个记录那些被转换了的配置信息项(记录在结构体server的array结构体类型config_touched字段内),以便计算和提示用户那些未被使用到的配置项;接着调用函数config_insert_values_internal()获取用户配置值,完成用户配置项到程序变量的转换。
清单7-15 函数config_insert_values_internal、config_insert_values_global

//configfile-glue.c
921./**
922.*like all glue code this file contains functions which
923.*are the external interface of lighttpd.The functions
924.*are used by the server itself and the plugins.
925.*
926.*The main-goal is to have a small library in the end
927.*which is linked against both and which will define
928.*the interface itself in the end.
929.*
930.*/
931./*handle global options*/
932./*parse config array*/
933.int config_insert_values_internal(server*srv,array*ca,const
config_values_t cv[]){
934.size_t i;
935.data_unset*du;
936.for(i=0;cv[i].key;i++){
/*根据key获取配置项。*/
937.if(NULL==(du=array_get_element(ca,cv[i].key))){
938./*no found*/
/*没有找到对应的配置项,即使用默认配置值,继续下一个处理。*/
939.continue;
940.}
941.switch(cv[i].type){
942.case T_CONFIG_ARRAY:
/*数组类型的配置信息。
server.modules=(
#"mod_indexfile",
#"mod_dirlisting",
#"mod_staticfile",
"mod_access",
"mod_auth",
"mod_accesslog")
*/
943.if(du->type==TYPE_ARRAY){
944.size_t j;
945.data_array*da=(data_array*)du;
946.for(j=0;j<da->value->used;j++){
/*必定是string类型。*/
947.if(da->value->data[j]->type==TYPE_STRING){
948.data_string*ds=data_string_init();
949.buffer_copy_string_buffer(ds->value,
950.((data_string*)(da->value->data[j]))->value);
951.if(!da->is_index_key){
952./*the id's were generated automaticly,as we copy now
953.we might have to renumber them this is used to
954.prepend server.modules by mod_indexfiles as it has
955.to be loaded before mod_fastcgi and friends*/
956.buffer_copy_string_buffer(ds->key,
957.((data_string*)(da->value->data[j]))->key);
958.}
959.array_insert_unique(cv[i].destination,(data_unset*)ds);
960.}else{/*提示配置文件有错误。*/
961.log_error_write(srv,__FILE__,__LINE__,"sssd",
"the key of an array can only be a string or a integer,variable:",cv[i].key,"type:",da->value->data[j]->type);
962.return-1;
963.}
964.}
965.}else{
966.log_error_write(srv,__FILE__,__LINE__,"ss",cv[i].key,
"should have been a array of strings like...=(\"...\")");
967.return-1;
968.}
969.break;
970.case T_CONFIG_STRING:
/*字符串类型的配置信息。
server.document-root="/home/lenky/source/lighttpd-1.4.20/lenky/"*/
971.if(du->type==TYPE_STRING){
972.data_string*ds=(data_string*)du;
973.buffer_copy_string_buffer(cv[i].destination,ds->value);
974.}else{
975.log_error_write(srv,__FILE__,__LINE__,"ssss",cv[i].key,
"should have been a string like...=\"...\"");
976.return-1;
977.}
978.break;
979.case T_CONFIG_SHORT:
/*短整型类型的配置信息。
server.port=3000
server.max-worker=4
server.max-fds=800
*/
980.switch(du->type){
981.case TYPE_INTEGER:{
982.data_integer*di=(data_integer*)du;
983.*((unsigned short*)(cv[i].destination))=di->value;
984.break;
985.}
986.case TYPE_STRING:{
987.data_string*ds=(data_string*)du;
988.log_error_write(srv,__FILE__,__LINE__,"ssb",
"got a string but expected a short:",cv[i].key,ds->value);
989.return-1;
990.}
991.default:
992.log_error_write(srv,__FILE__,__LINE__,"ssds","unexpected
type for key:",cv[i].key,du->type,
"expected a integer,range 0...65535");
993.return-1;
994.}
995.break;
996.case T_CONFIG_BOOLEAN:
/*布尔型类型的配置信息。
dir-listing.activate="enable"
$HTTP["url"]=~"^/www/"{
dir-listing.activate="disable"
}*/
997.if(du->type==TYPE_STRING){
998.data_string*ds=(data_string*)du;
999.if(buffer_is_equal_string(ds->value,CONST_STR_LEN("enable"))){
/*"enable"转为真值1。*/
1000.*((unsigned short*)(cv[i].destination))=1;
1001.}else if(buffer_is_equal_string(ds->value,
CONST_STR_LEN("disable"))){
/*"disable"转为假值0。*/
1002.*((unsigned short*)(cv[i].destination))=0;
1003.}else{
1004.log_error_write(srv,__FILE__,__LINE__,"ssbs","ERROR:unexpected value for key:",cv[i].key,ds->value,"(enable|disable)");
1005.return-1;
1006.}
1007.}else{
1008.log_error_write(srv,__FILE__,__LINE__,"ssss","ERROR:
unexpected type for key:",cv[i].key,"(string)","\"(enable|disable)\"");
1009.return-1;
1010.}
1011.break;
/*本地类型和未知类型不获取值。*/
1012.case T_CONFIG_LOCAL:
1013.case T_CONFIG_UNSET:
1014.break;
/*不支持类型。*/
1015.case T_CONFIG_UNSUPPORTED:
1016.log_error_write(srv,__FILE__,__LINE__,"ssss","ERROR:
found unsupported key:",cv[i].key,"-",(char*)(cv[i].destination));
1017.srv->config_unsupported=1;
1018.break;
1019.case T_CONFIG_DEPRECATED:
/*已被摒弃。*/
1020.log_error_write(srv,__FILE__,__LINE__,"ssss","ERROR:
found deprecated key:",cv[i].key,"-",(char*)(cv[i].destination));
1021.srv->config_deprecated=1;
1022.break;
1023.}
1024.}
1025.return 0;
1026.}
1027.
1028.int config_insert_values_global(server*srv,array*ca,const config_values_t cv[]){
1029.size_t i;
1030.data_unset*du;
/*对将要被转换的配置值逐个判断以记录被使用了的配置项。*/
1031.for(i=0;cv[i].key;i++){
1032.data_string*touched;
1033.if(NULL==(du=array_get_element(ca,cv[i].key))){
1034./*no found*/
/*配置文件里没有对其的配置项(即用户对这个配置项采用默认值)。*/
1035.continue;
1036.}
1037./*touched*//*有设置。*/
1038.touched=data_string_init();
/*并不关心其配置值。*/
1039.buffer_copy_string_len(touched->value,CONST_STR_LEN(""));
/*配置项。*/
1040.buffer_copy_string_buffer(touched->key,du->key);
/*记录保存被使用的配置项。*/
1041.array_insert_unique(srv->config_touched,(data_unset*)touched);
1042.}
/*调用函数config_insert_values_internal()获取配置值。*/
1043.return config_insert_values_internal(srv,ca,cv);
1044.}

       至此,用户对Lighttpd的主要配置信息(还有一些配置信息将在插件加载的时候根据需要进行实际转换)就实际进入到Lighttpd程序中的各个变量中,将决定着程序运行的表现。对于Lighttpd应用程序有些配置信息是必须有的,比如站点主目录、监听端口号、I/O端口多路复用技术等。这些配置信息中有些必须要由用户指定(如站点主目录,如果没有Lighttpd程序将异常退出),有些可以根据常规(如监听端口号设置为80)或系统环境(如I/O端口多路复用技术根据系统提供的支持)来设置默认值。这些操作都在函数config_set_defaults()内,清单7-16是该函数的源码。

清单7-16 函数config_set_defaults

//configfile.c
1045.int config_set_defaults(server*srv){
1046.size_t i;
1047.specific_config*s=srv->config_storage[0];
1048.struct stat st1,st2;
/*当前系统支持的I/O端口多路复用技术列表数组。*/
1049.struct ev_map{fdevent_handler_t et;const char*name;}event_handlers[]=
1050.{
1051./*-poll is most reliable
1052.*-select works everywhere
1053.*-linux-*are experimental
1054.*/
1055.#ifdef USE_POLL
1056.{FDEVENT_HANDLER_POLL,"poll"},
1057.#endif
1058.#ifdef USE_SELECT
1059.{FDEVENT_HANDLER_SELECT,"select"},
1060.#endif
1061.#ifdef USE_LINUX_EPOLL
1062.{FDEVENT_HANDLER_LINUX_SYSEPOLL,"linux-sysepoll"},
1063.#endif
1064.#ifdef USE_LINUX_SIGIO
1065.{FDEVENT_HANDLER_LINUX_RTSIG,"linux-rtsig"},
1066.#endif
1067.#ifdef USE_SOLARIS_DEVPOLL
1068.{FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll"},
1069.#endif
1070.#ifdef USE_FREEBSD_KQUEUE
1071.{FDEVENT_HANDLER_FREEBSD_KQUEUE,"freebsd-kqueue"},
1072.{FDEVENT_HANDLER_FREEBSD_KQUEUE,"kqueue"},
1073.#endif
1074.{FDEVENT_HANDLER_UNSET,NULL}
1075.};
1076.
1077.if(buffer_is_empty(s->document_root)){
/*用户未设置站点主目录,异常返回。*/
1078.log_error_write(srv,__FILE__,__LINE__,"s",
"a default document-root has to be set");
1079.return-1;
1080.}
/*站点根目录未设置,则s->document_root保存的就是主目录的完整路径。*/
1081.if(buffer_is_empty(srv->srvconf.changeroot)){
1082.if(-1==stat(s->document_root->ptr,&st1)){/*判断主目录是否存在。*/
1083.log_error_write(srv,__FILE__,__LINE__,"sb",
"base-docroot doesn't exist:",
s->document_root);
1084.return-1;
1085.}
1086.}else{/*组合站点根目录和主目录得到站点主目录的完整路径。*/
1087.buffer_copy_string_buffer(srv->tmp_buf,srv->srvconf.changeroot);
1088.buffer_append_string_buffer(srv->tmp_buf,s->document_root);
1089.if(-1==stat(srv->tmp_buf->ptr,&st1)){/*判断主目录是否存在。*/
1090.log_error_write(srv,__FILE__,__LINE__,"sb",
"base-docroot doesn't exist:",
srv->tmp_buf);
1091.return-1;
1092.}
1093.}
1094.
1095.buffer_copy_string_buffer(srv->tmp_buf,s->document_root);
1096.buffer_to_lower(srv->tmp_buf);
/*UNIX/Linux下大小写敏感,而Windows下又大小写不敏感,即在UNIX/Linux目录下可以同时存在a.txt和A.txt两个文件,而Windows下不允许。*/
1097.if(0==stat(srv->tmp_buf->ptr,&st1)){
1098.int is_lower=0;
1099.is_lower=buffer_is_equal(srv->tmp_buf,s->document_root);
1100./*lower-case existed,check upper-case*/
1101.buffer_copy_string_buffer(srv->tmp_buf,s->document_root);
1102.buffer_to_upper(srv->tmp_buf);
/*有些特殊根目录没有字符大小写的影响,比如"/"或"/12345/"。*/
/*we have to handle the special case that upper and lower-casing results in
the same filename
*as in server.document-root="/"or"/12345/"*/
1103.if(is_lower&&buffer_is_equal(srv->tmp_buf,s->document_root)){
/*lower-casing and upper-casing didn't result in
*an other filename,no need to stat(),
*just assume it is case-sensitive.*/
1104.s->force_lowercase_filenames=0;
1105.}else if(0==stat(srv->tmp_buf->ptr,&st2)){
/*upper case exists too,doesn't the FS handle this?*/
/*upper and lower have the same inode->case-insensitve FS*/
1106.if(st1.st_ino==st2.st_ino){/*大小不敏感的文件系统。*/
/*upper and lower have the same inode->case-insensitve FS*/
1107.s->force_lowercase_filenames=1;
1108.}
1109.}
1110.}
/*设置默认端口,HTTP端口80,HTTPS端口443。*/
1111.if(srv->srvconf.port==0){
1112.srv->srvconf.port=s->is_ssl?443:80;
1113.}
1114.if(srv->srvconf.event_handler->used==0){/*choose a good default
*
*the event_handler list is sorted by'goodness'
*taking the first available should be the best solution
*/
/*用户未对采用何种I/O端口多路复用技术做设置,则选择一个默认最好的选项,event_handler里的选项是按好坏评价来排列的,因此直接选择被系统支持的第一项。*/
1115.srv->event_handler=event_handlers[0].et;
/*使用FDEVENT_HANDLER_UNSET则说明没有用当前系统所支持的多路复用技术,异常返回。*/
1116.if(FDEVENT_HANDLER_UNSET==srv->event_handler){
1117.log_error_write(srv,__FILE__,__LINE__,"s",
1118."sorry,there is no event handler for this system");
1119.return-1;
1120.}
1121.}else{
1122./*
1123.*User override
1124.*/
/*对于用户的设置,还须判断是否被系统所支持。*/
1125.for(i=0;event_handlers[i].name;i++){
1126.if(0==strcmp(event_handlers[i].name,
srv->srvconf.event_handler->ptr)){
1127.srv->event_handler=event_handlers[i].et;
1128.break;
1129.}
1130.}
/*用户选择不被支持,异常返回。*/
1131.if(FDEVENT_HANDLER_UNSET==srv->event_handler){
1132.log_error_write(srv,__FILE__,__LINE__,"sb",
"the selected event-handler in unknown or not supported:",
srv->srvconf.event_handler);
1133.return-1;
1134.}
1135.}
1136.if(s->is_ssl){
1137.if(buffer_is_empty(s->ssl_pemfile)){
/*PEM file is require*/
/*SSL协议需要PEM文件。*/
1138.log_error_write(srv,__FILE__,__LINE__,"s",
"ssl.pemfile has to be set");
1139.return-1;
1140.}
/*还需判断SSL是否被系统所支持,不支持则异常返回。*/
1141.#ifndef USE_OPENSSL
1142.log_error_write(srv,__FILE__,__LINE__,"s",
"ssl support is missing,recompile with--with-openssl");
1143.return-1;
1144.#endif
1145.}
1146.return 0;
1147.}

       通过以上对配置信息解析加载的几个核心函数的分析可以知道,Lighttpd配置文件被LALR(1)词法分析器解析后,配置信息被存储在array数组变量srv->config_context内,Lighttpd程序将要获取用值从这个array数组里转换出来存储在specific_config结构体变量内,如主程序需要的配置信息都存储在srv->config_storage内,插件需要的配置信息存储在相应的插件结构体数据内的config_storage字段内,前面内容只讲叙了主程序配置信息的转储过程,但是插件从配置信息里获取相应的配置值也采用同样的流程,这将在关于插件的内容提到这一点。

 4 客户端连接配置信息

      对于Lighttpd主程序运行需要的某些配置信息(比如server.modules、server.max-worker、server.event-handler等)都是固定的配置,在Lighttpd的整个运行过程中都是不会改变的,而对于客户端的每次连接的某些配置信息则会根据客户端的不同请求选用不同的配置值(即所谓的Lighttpd条件配置信息),这个根据每次请求连接如何动态的选择配置信息的过程就是本节试图解析的内容。

4.1 条件配置信息缓存存储结构

       对于客户端的访问请求,绝大部分都是交给Lighttpd的插件链(将在后面插件章节看到关于这个的详细内容,代码416行的注释也提到这点)来处理的,各插件在获取连接的配置信息时可以共享前面插件解析配置信息的结果,比如第一个插件解析的配置信息条件状态应该缓存起来,以便在后面的插件处理过程中直接使用从而提高程序执行效率。

缓存条件配置值的结构体定义在base.h头文件内,如清单7-17所示。
清单7-17 cond_cache_t数据结构定义

//base.h
1148.typedef enum{COND_RESULT_UNSET,COND_RESULT_FALSE,
COND_RESULT_TRUE}cond_result_t;
1149.typedef struct{
1150.cond_result_t result;
1151.int patterncount;
1152.int matches[3*10];
1153.buffer*comp_value;/*just a pointer*/
1154.comp_key_t comp_type;
1155.}cond_cache_t;

     每一个cond_cache_t结构体对应一个条件配置信息块(即data_config)的解析结果,多个cond_cache_t结构体组成一个数组(相当于cond_cache_t缓存器),通过con->cond_cache字段(该字段在第10章可以看到其定义,是cond_cache_t指针类型变量)来指向而可访问,并且通过cond_cache_t结构体各自对应的data_config间接(如图7-2所示)地关联起来。

4.2 客户端连接配置信息动态获取

       对于每一个客户端连接的配置信息都包括有固定的全局配置值和动态的条件配置值,全局配置值和条件配置值共同作用才得到该次连接的实际配置值,例如:当客户端IP地址不是“127.0.0.1”时,该连接的"dir-listing.activate"配置值为"disable"(代码28行,此时仅全局配置值起作用),但是如果客户端IP地址是“127.0.0.1”,则该连接的"dir-listing.activate"配置值为"enable"(代码70行,即此时条件配置值将优先全局配置值而对该连接起作用)。下面开始客户端连接配置信息动态获取过程的源码分析。      

    首先来看在connection结构体变量的初始化函数connection_init()里对配置信息的操作具体有些什么。可以看到与本节内容相关的 代码只有两行,代码1186行为cond_cache_t缓存器申请存储空间,由于每一cond_cache_t结构体对应一个配置信息块,因此申请的存储空间大小为config_context的保存配置数据长度,即配置信息块(包括全局配置信息块和条件配置信息块)数目;代码1187行调用函数config_setup_connection()获取固定的全局配置值,该函数实现也比较简单,仅是将con的字段指向srv的对应字段。这两个函数如清单7-18所示。

   清单7-18 函数connection_init、config_setup_connection

   //connections.c
1156.connection*connection_init(server*srv){
1157.connection*con;
1158.UNUSED(srv);
1159.con=calloc(1,sizeof(*con));
1160.con->fd=0;con->ndx=-1;
1161.con->fde_ndx=-1;con->bytes_written=0;
1162.con->bytes_read=0;con->bytes_header=0;
1163.con->loops_per_request=0;
1164.#define CLEAN(x)\
1165.con->x=buffer_init();CLEAN(request.uri);
1166.CLEAN(request.request_line);CLEAN(request.request);
1167.CLEAN(request.pathinfo);CLEAN(request.orig_uri);
1168.CLEAN(uri.scheme);CLEAN(uri.authority);
1169.CLEAN(uri.path);CLEAN(uri.path_raw);
1170.CLEAN(uri.query);CLEAN(physical.doc_root);
1171.CLEAN(physical.path);CLEAN(physical.basedir);
1172.CLEAN(physical.rel_path);CLEAN(physical.etag);
1173.CLEAN(parse_request);CLEAN(authed_user);
1174.CLEAN(server_name);CLEAN(error_handler);
1175.CLEAN(dst_addr_buf);
1176.#undef CLEAN
1177.con->write_queue=chunkqueue_init();
1178.con->read_queue=chunkqueue_init();
1179.con->request_content_queue=chunkqueue_init();
1180.chunkqueue_set_tempdirs(con->request_content_queue,srv->srvconf.upload_tempdirs);
1181.con->request.headers=array_init();
1182.con->response.headers=array_init();
1183.con->environment=array_init();
1184./*init plugin specific connection structures*/
1185.con->plugin_ctx=calloc(1,(srv->plugins.used+1)*sizeof(void*));
1186.con->cond_cache=calloc(srv->config_context->used,sizeof(cond_cache_t));
1187.config_setup_connection(srv,con);
1188.return con;
1189.}
1190.#define PATCH(x)con->conf.x=s->x
1191.int config_setup_connection(server*srv,connection*con){
1192.specific_config*s=srv->config_storage[0];
1193.PATCH(allow_http11);PATCH(mimetypes);
1194.PATCH(document_root);PATCH(max_keep_alive_requests);
1195.PATCH(max_keep_alive_idle);PATCH(max_read_idle);
1196.PATCH(max_write_idle);PATCH(use_xattr);
1197.PATCH(error_handler);PATCH(errorfile_prefix);
1198.#ifdef HAVE_LSTAT
1199.PATCH(follow_symlink);
1200.#endif
1201.PATCH(server_tag);PATCH(kbytes_per_second);
1202.PATCH(global_kbytes_per_second);PATCH(global_bytes_per_second_cnt);
1203.con->conf.global_bytes_per_second_cnt_ptr=&s->global_bytes_per_second_cnt;
1204.buffer_copy_string_buffer(con->server_name,s->server_name);
1205.PATCH(log_request_header);PATCH(log_response_header);
1206.PATCH(log_request_handling);PATCH(log_condition_handling);
1207.PATCH(log_file_not_found);PATCH(log_ssl_noise);
1208.PATCH(range_requests);PATCH(force_lowercase_filenames);
1209.PATCH(is_ssl);PATCH(ssl_pemfile);
1210.PATCH(ssl_ca_file);PATCH(ssl_cipher_list);
1211.PATCH(ssl_use_sslv2);PATCH(etag_use_inode);
1212.PATCH(etag_use_mtime);PATCH(etag_use_size);
1213.return 0;
1214.}

由于connection结构体变量在各请求连接中重复使用,因此在处理某连接之前先将其cond_cache_t缓存器重置,这部分相关函数源码如清单7-19所示。
清单7-19 cond_cache_t缓存器重置

//configfile-glue.c
1215.void config_cond_cache_reset(server*srv,connection*con){
1216.size_t i;
1217.config_cond_cache_reset_all_items(srv,con);
1218.for(i=0;i<COMP_LAST_ELEMENT;i++){
1219.con->conditional_is_valid[i]=0;
1220.}
1221.}
//configfile.h
1222.#define config_cond_cache_reset_all_items(srv,con)\
1223.config_cond_cache_reset_item(srv,con,COMP_LAST_ELEMENT);
//configfile-glue.c
1224.void config_cond_cache_reset_item(server*srv,connection*con,comp_key_t
item){
1225.size_t i;
1226.for(i=0;i<srv->config_context->used;i++){
1227.if(item==COMP_LAST_ELEMENT||
1228.con->cond_cache[i].comp_type==item){
1229.con->cond_cache[i].result=COND_RESULT_UNSET;
1230.con->cond_cache[i].patterncount=0;
1231.con->cond_cache[i].comp_value=NULL;
1232.}
1233.}
1234.}

   重置cond_cache_t缓存器的实现源码十分简单不做过多介绍,下面我们开始分析本节内容最重要的函数config_patch_connection(),该函数将实现根据当前连接状态获取条件配置值(即当前连接状态符合配置条件设置时),如清单7-20所示。
清单7-20 函数config_patch_connection

//configfile.c
1235.int config_patch_connection(server*srv,connection*con,comp_key_t comp){
1236.size_t i,j;
1237.con->conditional_is_valid[comp]=1;/*标记该条件配置缓存值有效。*/
1238./*skip the first,the global context*/
/*仅关注条件配置信息块,因此跳过全局配置信息块。*/
1239.for(i=1;i<srv->config_context->used;i++){
1240.data_config*dc=(data_config*)srv->config_context->data[i];
1241.specific_config*s=srv->config_storage[i];
1242./*condition didn't match*/
/*当前连接状态与条件配置信息块的条件设置匹配时才获取对应的配置值,否则直接continue跳过。*/
1243.if(!config_check_cond(srv,con,dc))continue;
1244./*merge config*/
1245.for(j=0;j<dc->value->used;j++){
1246.data_unset*du=dc->value->data[j];
/*获取对应的条件配置值。*/
1247.if(buffer_is_equal_string(du->key,
CONST_STR_LEN("server.document-root"))){
/*PATCH宏为代码1190行所示。*/
1248.PATCH(document_root);
1249.}else if(buffer_is_equal_string(du->key,
CONST_STR_LEN("server.range-requests"))){
1250.PATCH(range_requests);
1251.}
1252.//……省略……此处省略了很多else-if判断,但并不影响我们理解。
1253.}
1254.}/*ETag配置值,在第6.2.4.2节曾有提到。*/
1255.con->etag_flags=(con->conf.etag_use_mtime?ETAG_USE_MTIME:0)|
(con->conf.etag_use_inode?ETAG_USE_INODE:0)|
(con->conf.etag_use_size?ETAG_USE_SIZE:0);
1256.return 0;
1257.}
1258.#undef PATCH

可以看到函数config_patch_connection()中的核心调用函数为config_check_cond(),通过判断它的返回值来判断是否应该获取该条件配置值,如清单7-21所示。
清单7-21 函数config_check_cond、config_check_cond_nocache

//configfile-glue.c
1259.int config_check_cond(server*srv,connection*con,data_config*dc){
1260.if(con->conf.log_condition_handling){
1261.log_error_write(srv,__FILE__,__LINE__,"s",
"===start of condition block===");
1262.}
1263.return(config_check_cond_cached(srv,con,dc)==COND_RESULT_TRUE);
1264.}
1265.static cond_result_t config_check_cond_cached(server*srv,connection*con,data_config*dc){
1266.cond_cache_t*caches=con->cond_cache;
/*dc->context_ndx记录该dc在srv->config_context->data[]数组中存储位置的下标,而cond_cache_t与data_config一一对应。*/
1267.if(COND_RESULT_UNSET==caches[dc->context_ndx].result){
/*缓存未命中,调用函数config_check_cond_nocache()进行条件配置块的条件设置匹配。*/
1268.if(COND_RESULT_TRUE==(caches[dc->context_ndx].result=
config_check_cond_nocache(srv,con,dc))){
/*if-else条件配置链接块的if块或某else块取值为真,那么该块后面块的取值则都为假。*/
1269.if(dc->next){
1270.data_config*c;
1271.if(con->conf.log_condition_handling){
1272.log_error_write(srv,__FILE__,__LINE__,"s",
"setting remains of chaining to false");
1273.}
1274.for(c=dc->next;c;c=c->next){
1275.caches[c->context_ndx].result=COND_RESULT_FALSE;
1276.}
1277.}
1278.}
1279.caches[dc->context_ndx].comp_type=dc->comp;
1280.if(con->conf.log_condition_handling){
1281.log_error_write(srv,__FILE__,__LINE__,"dss",dc->context_ndx,
"(uncached)result:",caches[dc->context_ndx].result==
COND_RESULT_UNSET?"unknown"
:(caches[dc->context_ndx].result==
COND_RESULT_TRUE?"true":"false"));
1282.}
1283.}else{/*缓存命中的情况。*/
1284.if(con->conf.log_condition_handling){
1285.log_error_write(srv,__FILE__,__LINE__,"dss",dc->context_ndx,
"(cached)result:",caches[dc->context_ndx].result==
COND_RESULT_UNSET?"unknown"
:(caches[dc->context_ndx].result==
COND_RESULT_TRUE?"true":"false"));
1286.}
1287.}
1288.return caches[dc->context_ndx].result;
1289.}
1290.static cond_result_t config_check_cond_nocache(server*srv,connection*con,data_config*dc){
1291.buffer*l;
1292.server_socket*srv_sock=con->srv_socket;
1293./*check parent first*/
1294.if(dc->parent&&dc->parent->context_ndx){
1295./**
1296.*a nested conditional
1297.*if the parent is not decided yet or false,we can't be true either
1298.*/
1299.if(con->conf.log_condition_handling){
1300.log_error_write(srv,__FILE__,__LINE__,"sb","go parent",dc->parent->key);
1301.}
/*父块状态为假或未知,那么包含在其内的子块状态也返回假或未知。*/
1302.switch(config_check_cond_cached(srv,con,dc->parent)){
1303.case COND_RESULT_FALSE:
1304.return COND_RESULT_FALSE;
1305.case COND_RESULT_UNSET:
1306.return COND_RESULT_UNSET;
1307.default:
1308.break;
1309.}
1310.}
1311.if(dc->prev){
1312./**
1313.*a else branch
1314.*we can only be executed,if all of our previous brothers
1315.*are false
1316.*/
/*存在前驱块,那么需要先判断前驱块状态。*/
1317.if(con->conf.log_condition_handling){
1318.log_error_write(srv,__FILE__,__LINE__,"sb","go prev",
dc->prev->key);
1319.}
1320./*make sure prev is checked first*/
1321.config_check_cond_cached(srv,con,dc->prev);
1322./*one of prev set me to FALSE*/
/*在判断前驱块状态时有可能就已经设置了本块的状态(1269行),如果为假则直接返回。这儿用switch/case句型而不用if句型来判断取值,我觉得原因有二个:一是代码前后统一,前后都是用的switch/case句型;二是便于扩展,如果以后要加入新的condition,则直接加个case即可。*/
1323.switch(con->cond_cache[dc->context_ndx].result){
1324.case COND_RESULT_FALSE:
1325.return con->cond_cache[dc->context_ndx].result;
1326.default:
1327.break;
1328.}
1329.}
1330.if(!con->conditional_is_valid[dc->comp]){
1331.if(con->conf.log_condition_handling){
1332.log_error_write(srv,__FILE__,__LINE__,"dss",
dc->comp,
dc->key->ptr,
con->conditional_is_valid[dc->comp]?"yeah":"nej");
1333.}
1334.return COND_RESULT_UNSET;
1335.}
1336./*pass the rules*/
/*开始实际的连接状态判断,Lighttpd1.4.20提供的条件配置有10,分别为SERVER_SOCKET、HTTP_URL、HTTP_HOST、HTTP_REFERER、HTTP_USER_AGENT、HTTP_COOKIE、HTTP_REMOTE_IP、HTTP_QUERY_STRING、HTTP_SCHEME、HTTP_REQUEST_METHOD,下面开始按类别进行处理。*/
1337.switch(dc->comp){
1338.case COMP_HTTP_HOST:{
1339.char*ck_colon=NULL,*val_colon=NULL;
/*authority内保存是请求连接的Host信息(可能是域名也可能是IP地址)。*/
1340.if(!buffer_is_empty(con->uri.authority)){
1341./*
1342.*append server-port to the HTTP_POST if necessary
1343.*/
1344.l=con->uri.authority;
1345.switch(dc->cond){
1346.case CONFIG_COND_NE:
1347.case CONFIG_COND_EQ:
1348.ck_colon=strchr(dc->string->ptr,':');
1349.val_colon=strchr(l->ptr,':');
/*请求连接的Host信息与条件配置块的Host条件设置格式一致(即两者都包含有端口号或都没有包含端口号),则什么也不做。*/
1350.if(ck_colon==val_colon){
1351./*nothing to do with it*/
1352.break;
1353.}
1354.if(ck_colon){
/*请求连接的Host信息没有包含端口号而条件配置块的Host包含端口号,因此给请求连接的Host加上端口号。*/
1355./*condition"host:port"but client send"host"*/
1356.buffer_copy_string_buffer(srv->cond_check_buf,l);
1357.buffer_append_string_len(srv->cond_check_buf,
CONST_STR_LEN(":"));
1358.buffer_append_long(srv->cond_check_buf,
sock_addr_get_port(&(srv_sock->addr)));
1359.l=srv->cond_check_buf;
1360.}else if(!ck_colon){
/*请求连接的Host信息包含端口号而条件配置块的Host没有包含端口号,因此将请求连接Host的端口号去掉。*/
1361./*condition"host"but client send"host:port"*/
1362.buffer_copy_string_len(srv->cond_check_buf,l->ptr,
val_colon-l->ptr);
1363.l=srv->cond_check_buf;
1364.}
1365.break;
1366.default:
1367.break;
1368.}
1369.}else{
1370.l=srv->empty_string;
1371.}
1372.break;
1373.}
1374.case COMP_HTTP_REMOTE_IP:{
1375.char*nm_slash;
1376./*handle remoteip limitations
1377.*"10.0.0.1"is provided for all comparisions
1378.*only for==and!=we support
1379.*"10.0.0.1/24"
1380.*/
1381.if((dc->cond==CONFIG_COND_EQ||
1382.dc->cond==CONFIG_COND_NE)&&
1383.(con->dst_addr.plain.sa_family==AF_INET)&&
1384.(NULL!=(nm_slash=strchr(dc->string->ptr,'/')))){
1385.int nm_bits;
1386.long nm;
1387.char*err;
1388.struct in_addr val_inp;
/*对于无分类域间路由选择CIDR(CIDR记法,斜线记法),读者应该不会陌生,这里对CIDR格式字符串进行检验。*/
1389.if(*(nm_slash+1)=='\0'){
/*CIDR格式不对,缺少表示网络前缀位数的数字。*/
1390.log_error_write(srv,__FILE__,__LINE__,"sb",
"ERROR:no number after/",dc->string);
1391.return COND_RESULT_FALSE;
1392.}
/*函数strtol()声明在头文件stdlib.h内,原型为long int strtol(const char*nptr,char**endptr,int base);,用于将参数nptr字符串根据参数base指定的进制来转换成对应的长整型数。参数base范围从2至36,或0(即默认采用十进制做转换,但遇到如'0x'前置字符则会使用十六进制做转换)。strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。该函数执行成功返回转换后的长整型数,否则返回ERANGE(表示指定的转换字符串超出合法范围)并将错误代码存入errno中。此处用于获取端口十进制的整型数。*/
1393.nm_bits=strtol(nm_slash+1,&err,10);
1394.if(*err){
1395.log_error_write(srv,__FILE__,__LINE__,"sbs",
"ERROR:non-digit found in netmask:",dc->string,err);
1396.return COND_RESULT_FALSE;
1397.}
1398./*take IP convert to the native*/
1399.
buffer_copy_string_len(srv->cond_check_buf,dc->string->ptr,
nm_slash-dc->string->ptr);
1400.#ifdef__WIN32
1401.//……省略……
1402.#else
/*函数inet_aton()声明在头文件sys/scoket.h内,原型为int inet_aton(const char*cp,struct in_addr*inp);,用于将参数cp所指的字符串形式的网络地址转换成网络使用的二进制的数字形式,然后存于参数inp所指的in_addr结构中。*/
1403.if(0==inet_aton(srv->cond_check_buf->ptr,&val_inp)){
1404.log_error_write(srv,__FILE__,__LINE__,"sb",
"ERROR:ip addr is invalid:",srv->cond_check_buf);
1405.return COND_RESULT_FALSE;
1406.}
1407.#endif
1408./*build netmask*/
/*函数htonl()声明在头文件arpa/inet.h内,原型为uint32_t htonl(uint32_t hostlong);,用来将参数hostlong指定的32位无符号长整型由主机字节顺序转换成网络字符顺序。*/
1409.nm=htonl(~((1<<(32-nm_bits))-1));
1410.if((val_inp.s_addr&nm)==(con->dst_addr.ipv4.sin_addr.s_addr&nm)){
/*当前连接的客户端IP地址与条件配置信息块的条件设置匹配,按需返回结果。*/
1411.return(dc->cond==CONFIG_COND_EQ)?
COND_RESULT_TRUE:COND_RESULT_FALSE;
1412.}else{/*不匹配。*/
1413.return(dc->cond==CONFIG_COND_EQ)?
COND_RESULT_FALSE:COND_RESULT_TRUE;
1414.}
1415.}else{
1416.l=con->dst_addr_buf;
1417.}
1418.break;
1419.}
1420.case COMP_HTTP_SCHEME:
1421.l=con->uri.scheme;
1422.break;
1423.case COMP_HTTP_URL:
1424.l=con->uri.path;
1425.break;
1426.case COMP_HTTP_QUERY_STRING:
1427.l=con->uri.query;
1428.break;
1429.case COMP_SERVER_SOCKET:
1430.l=srv_sock->srv_token;
1431.break;
1432.case COMP_HTTP_REFERER:{
1433.data_string*ds;
1434.if(NULL!=(ds=(data_string*)array_get_element(con->request.headers,"Referer"))){
1435.l=ds->value;
1436.}else{
1437.l=srv->empty_string;
1438.}
1439.break;
1440.}
1441.case COMP_HTTP_COOKIE:{
1442.data_string*ds;
1443.if(NULL!=(ds=(data_string*)array_get_element(con->request.headers,"Cookie"))){
1444.l=ds->value;
1445.}else{
1446.l=srv->empty_string;
1447.}
1448.break;
1449.}
1450.case COMP_HTTP_USER_AGENT:{
1451.data_string*ds;
1452.if(NULL!=(ds=(data_string*)array_get_element(con->request.headers,"User-Agent"))){
1453.l=ds->value;
1454.}else{
1455.l=srv->empty_string;
1456.}
1457.break;
1458.}
1459.case COMP_HTTP_REQUEST_METHOD:{
/*get_http_method_name()函数根据当前连接的请求方法(通过分析请求行得知,在第10章会讲到)返回对应的字符串,比如"GET"、"POST"等。*/
1460.const char*method=get_http_method_name(con->request.http_method);
1461./*we only have the request method as const char but we need a buffer for comparing*//*为了后面的统一匹配比较,利用该字符串初始化buffer结构体。*/
1462.buffer_copy_string(srv->tmp_buf,method);
1463.l=srv->tmp_buf;
1464.break;
1465.}
1466.default:
1467.return COND_RESULT_FALSE;
1468.}
1469.if(NULL==l){/*当前连接待匹配字段为空,则返回假。*/
1470.if(con->conf.log_condition_handling){
1471.log_error_write(srv,__FILE__,__LINE__,"bsbs",dc->comp_key,
1472."(",l,")compare to NULL");
1473.}
1474.return COND_RESULT_FALSE;
1475.}
1476.if(con->conf.log_condition_handling){
1477.log_error_write(srv,__FILE__,__LINE__,"bsbsb",dc->comp_key,
1478."(",l,")compare to",dc->string);
1479.}
1480.switch(dc->cond){
1481.case CONFIG_COND_NE:
1482.case CONFIG_COND_EQ:
1483.if(buffer_is_equal(l,dc->string)){/*相等或不等匹配。*/
1484.return(dc->cond==CONFIG_COND_EQ)?
COND_RESULT_TRUE:COND_RESULT_FALSE;
1485.}else{
1486.return(dc->cond==CONFIG_COND_EQ)?
COND_RESULT_FALSE:COND_RESULT_TRUE;
1487.}
1488.break;
1489.#ifdef HAVE_PCRE_H
/*正则式匹配需要相应库的支持,GNU/Linux下有两套正则式编程支持库:POSIX库和PCRE库。POSIX库不需要单独安装,能满足一般需求,但是速度稍慢些,读者查看MAN手册。PCRE库久负盛名,功能强大,匹配速度快,但是可能需要单独安装。关于PCRE库的更多介绍,读者可以查阅站点:http://www.pcre.org/。此处用的是PCRE库。*/
1490.case CONFIG_COND_NOMATCH:
1491.case CONFIG_COND_MATCH:{
1492.cond_cache_t*cache=&con->cond_cache[dc->context_ndx];
1493.int n;
1494.#ifndef elementsof
1495.#define elementsof(x)(sizeof(x)/sizeof(x[0]))
1496.#endif
/*利用PCRE库函数pcre_exec()执行匹配操作,如果不匹配或执行出错则返回一个负值(其中,不匹配则返回PCRE_ERROR_NOMATCH(该宏值为-1)),如果匹配成功将返回一个正数。关于函数pcre_exec()的详细说明请读者参考说明文档:http://www.pcre.org/pcre.txt。*/
1497.n=pcre_exec(dc->regex,dc->regex_study,l->ptr,l->used-1,0,0,
1498.cache->matches,elementsof(cache->matches));
1499.cache->patterncount=n;
1500.if(n>0){/*匹配成功。*/
1501.cache->comp_value=l;
1502.cache->comp_type=dc->comp;
1503.return(dc->cond==CONFIG_COND_MATCH)?
COND_RESULT_TRUE:COND_RESULT_FALSE;
1504.}else{
1505./*cache is already cleared*/
1506.return(dc->cond==CONFIG_COND_MATCH)?
COND_RESULT_FALSE:COND_RESULT_TRUE;
1507.}
1508.break;
1509.}
1510.#endif
1511.default:
1512./*no way*/
1513.break;
1514.}
1515.return COND_RESULT_FALSE;
1516.}
1517.unsigned short sock_addr_get_port(sock_addr*addr){
1518.#ifdef HAVE_IPV6
/*函数ntohs()声明在头文件arpa/inet.h内,原型为uint16_t ntohs(uint16_t netshort);,用来将参数netshort指定的16位无符号整型由网络字符顺序转换成主机字节顺序。*/
1519.return ntohs(addr->plain.sa_family?addr->ipv6.sin6_port:addr->ipv4.sin_port);
1520.#else
1521.return ntohs(addr->ipv4.sin_port);
1522.#endif
1523.}

     到此,根据每次请求连接动态的选择配置信息的总个实现代码就已经解析完毕,回过头来看看其实并不复杂,但是这部分代码实现的功能对于使用Lighttpd应用程序的用户来说却是至关重要的,因为它提供给用户灵活的信息配置选择。

5 本章总结

       本章主要对Lighttpd应用程序如何加载配置信息做了较为详细的介绍。Lighttpd配置文件采用BNF描述的上下文无关语言表达用户对Lighttpd应用程序各方面的设置,如何将这些用户自定义信息转换到成为程序中的变量值是本章内容涉及代码完成的主要工作。本章对于语法分析生成方面的编译原理知识并未做任何讲解(读者可以参考其他专著书籍),着重分析了由配置信息到程序配置值的转换存储,并对关于如何动态的选择条件状态配置信息值也做了详细的讲解。

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

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

猜你喜欢

转载自blog.csdn.net/caofengtao1314/article/details/82862006