lighttpd - Plugin: CGI

本文是阅读lighttpd源代码时做的笔记,mod_cgi部分。这次(文字部分)直接从笔记软件复制过来(代码还是一段段嵌入),速度是快不少。缺点是字体难看了一些。
由于是第一遍读lighttpd,加上水平有限,难免会有些错误的地方,不吝赐教。
 
CGI和FastCGI的支持,是每个Web Server相当重要的功能。本章分析了Lighttpd CGI的实现,即mod_cgi。
 
1. Config CGI for lighttpd
 
CGI原理和如何写CGI不是本文的重点,不过配置CGI有所了解有助于理解mod_cgi的代码。要配置lighttpd来运行CGI进程,先要加载mod_cgi模块,在server.modules里面加入"mod_cgi";还要告诉WebServer,何时将数据的处理交给CGI进程,这个可以通过cgi.assign选项指定。
server.modules = (... "mod_cgi", ...)

cgi.assign     = ( ".pl"  => "/usr/bin/perl",
                   ".perl" => "/usr/bin/perl",              这个是我杜撰的后缀,没人用的吧
                   ".cgi" => "",                            可以直接执行,无需解释器
                   ".py"  => "/usr/bin/python" 
                   "/testfile" => "/usr/bin/mycgi" )        无后缀
对于“.pl”,".cgi",".py"结尾的文档,都会视为CGI程序。后面紧跟而这些程序的解释器。
  • 可以为不同的后缀文件指定相同的解释器,
  • 可以不指定解释器(例如,C语言写的cgi),后者解释器部分为空字符串""。
  • 如果URL的path部分没有指定扩展名而是路径+可执行文件,那么需要指定可执行文件的绝对路径。
将CGI归拢到一个目录“/cgi-bin”,并且支持此目录下的index文件是比较通用的做法,不过这里不打算涉及[如果好奇心太重,或者老板真的需要,一定要知道怎么弄,参考doc/outdated/cgi.txt,或者google吧])。

1.1 Example
 
着手弄个可以运行的例子很简单。在docroot下建两个文件,hello和hello.pl,并且有可执行权限。hello用C语言写(其实只要是可执行文件就行了,是脚本也可以),
    #include <stdio.h>
     
    int main(int argc, char *argv[])
    {
         printf("Content-Type: text/plain\r\n\r\n");
         printf("hello, lighttpd (hello)");
         return 0;
    } 
hello.pl用Perl写,
#!/usr/bin/perl -w

print "Content-Type: text/plain\r\n\r\n";
print "hello, lighttpd (hello.pl)";
配置文件如下,
    cgi.assign = (
        ".pl"               =>  "/usr/bin/perl",
        ".cgi"              =>  "",
        "/hello"    => "/tmp/lighttpd/www/hello"
    )
 
2. mod_cgi
 
lighttpd的CGI支持是通过plugin实现的(大多数主流Web Server都是这样,mini_httpd算是另类,因为它太小了,更本没有plugin机制)。
 
2.1 Data Structure
 
这里先简单罗列出一些数据结构,并简单介绍,它们的具体意义则在稍后的章节介绍。到时候可以回来查看。首先是两个数组,
  43 typedef struct {   
  44     char **ptr;
  45    
  46     size_t size;
  47     size_t used;
  48 } char_array;

  50 typedef struct {
  51     pid_t *ptr;
  52     size_t used;
  53     size_t size;   
  54 } buffer_pid_t; 
ascii字符串的数组,和PID数组。然后是两个CGI模块重定义的结构体plugin_data, plugin_config。
  56 typedef struct {
  57     array *cgi;
  58     unsigned short execute_x_only;
  59 } plugin_config;

  61 typedef struct {
  62     PLUGIN_DATA;
  63     buffer_pid_t cgi_pid;
  64
  65     buffer *tmp_buf;
  66     buffer *parse_response;
  67
  68     plugin_config **config_storage;
  69
  70     plugin_config conf;
  71 } plugin_data;
由名字可以判断出,cgi_pid是用来保存和mod_cgi相关的CGI进程的PID数组。config_storage保存server的所有选项,不光光是mod_cgi的选项。
87 typedef enum { T_CONFIG_UNSET,
88         T_CONFIG_STRING,
89         T_CONFIG_SHORT,
90         T_CONFIG_INT,
91         T_CONFIG_BOOLEAN,
92         T_CONFIG_ARRAY,
93         T_CONFIG_LOCAL,
94         T_CONFIG_DEPRECATED,
95         T_CONFIG_UNSUPPORTED
96 } config_values_type_t;
97
98 typedef enum { T_CONFIG_SCOPE_UNSET,     
99         T_CONFIG_SCOPE_SERVER,
100         T_CONFIG_SCOPE_CONNECTION
101 } config_scope_type_t;
102
103 typedef struct {
104     const char *key;
105     void *destination;
106
107     config_values_type_t type;
108     config_scope_type_t scope;
109 } config_values_t;
对于每个plugin_config的cv(config value)。key是配置名,例如"cgi.assign",destination根据不同的选项指向不同的内容,该内容用于保存具体的选项值。然后是选项值类型,和作用范围。
  73 typedef struct {
  74     pid_t pid;
  75     int fd;
  76     int fde_ndx; /* index into the fd-event buffer */
  77        
  78     connection *remote_conn;  /* dumb pointer */
  79     plugin_data *plugin_data; /* dumb pointer */
  80        
  81     buffer *response;
  82     buffer *response_header;
  83 } handler_ctx;
此结构是递交个fdevent的私有结构,无需把整个plugin_data交个它,指需要必要的部分。pid是CGI进程的PID,fd是Web Server和CGI进程之间,用于读取CGI数据的fd。fde_ndx如注释所说是fd-event的下标。response和response_header是两个buffer,fdevent的Handle会填写这两个buffer。

2.2 Initilization
 
初始化的代码显示对应的lighttpd版本、模块名,以及各个hook都在何时被调用,Server初始化的时候加载各个module时,plugins_load,时调用mod_cgi_plugin_init
1435 int mod_cgi_plugin_init(plugin *p) {
1436     p->version     = LIGHTTPD_VERSION_ID;
1437     p->name        = buffer_init_string("cgi");
1438
1439     p->connection_reset = cgi_connection_close_callback;
1440     p->handle_subrequest_start = cgi_is_handled;
1441     p->handle_subrequest = mod_cgi_handle_subrequest;
...
1445     p->handle_trigger = cgi_trigger;
1446     p->init           = mod_cgi_init;
1447     p->cleanup        = mod_cgi_free;
1448     p->set_defaults   = mod_fastcgi_set_defaults;
...
1453 }
各个加载plugin加载完毕,后Server通过plugins_call_init调用各个模块的初始化函数,此处即mod_cgi_init。它用来初始化并分配此模块的plugin_data结构。mod_cgi_free则释放相应的资源。这两个hook具体就不看了。
 
2.3 Configration
 
虚函数set_defaults用来处理配置选项。它会将配置选项解析成内部形式,并保存到模块自定义的plugin_config中。mod_cgi的实现是mod_fastcgi_set_defaults。(为什么叫fastcgi?mod_fastcgi.c也有个同名函数)。
148 SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
...
152     config_values_t cv[] = {
153         { "cgi.assign",                  NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
154         { "cgi.execute-x-only",          NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },     /* 1 */
155         { NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
156     };
157  
声明选项的名字、类型,作用范围。但它们的具体值留空。第一个是array类型,array类型可以存放[key, value]的数组。
160     p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
到每个模块的的set_defaults前srv->config_context已经被设置成模块相关的配置,如果配置文件中只有
 
     cgi.assign = ("" => "", 
                   "" => "")
 
那么srv->config_context->used就是1,不会有别的配置漏进来!具体原因见configuration部分。
162     for (i = 0; i < srv->config_context->used; i++) {
163         plugin_config *s;
164  
165         s = calloc(1, sizeof(plugin_config));
166         assert(s);
167  
168         s->cgi    = array_init();     
169         s->execute_x_only = 0;
170  
171         cv[0].destination = s->cgi;   
172         cv[1].destination = &(s->execute_x_only);
173
174         p->config_storage[i] = s;     
175
176         if (0 != config_insert_values_global(srv, 
                    ((data_config *)srv->config_context->data[i])->value, cv)) {
177             return HANDLER_ERROR;         
178         }
179     }
180
181     return HANDLER_GO_ON;
182 }
cv[i]的->destination的赋值部分每次都是不同的值,即新分配的plugin_data的cgi和execute_x_only。然后调用config_insert_values_global,后者根据cv[i]>key,为每个cv找到Server对应的选项,然后调用此config_insert_values_intenal。后者根据cv[i].type,将具体的值放到cv[i].destination中。
 
要知道,cv[i].destination指向了plugin_config对应的字段cgi,execute_x_only,所以config_insert_values_global相当于,从server的config把值取到了mod_cgi的plugin_config对应的位置。
 
config_insert_values_global(config_insert_values_intenal)在key不匹配的情况下也是返回0,所以,虽然p->config_storage里面包含了srv->config_context->used个plugin_data,但只有NELEM(cv) - 1个会被真正赋值。另外,plugin_config->cgi是不带类型的数组array,如果它对应的cv类型是T_CONFIG_ARRAY的话,那么cgi的元素就是data_string{}。

2.4 Timer (Tigger)
 
cgi_trigger每秒钟被调用一次。它的作用是为每个Plugin下已经结束的CGI(子进程)处理后事(如果CGI已经结束会留下僵尸)。使用的是waitpid的WNOHANG模式,WNOHANG除了不会在子进程没结束的时候挂起调用者,它的返回值,
 
>0: 状态改变的子进程的PID
 0: 进程状态还没改变(还没结束)
-1: 如果未退出,或者正常退出,都返回0。如果出错,返回-1。
 
退出的时候会把pid从plugin中删除,如果是异常退出,通常会记录log。
 
2.5 Patch Connection
 
我们在很多Plugin中会看到patch connection函数,它到底是做什么的呢?在[2.3]我们看到了如何解析保存配置到plugin_data->config_storage。但config_storage并非真正起作用的config。plugin_data除了有一个config_storage,还有一个conf字段。
  61 typedef struct {
  ...
  68     plugin_config **config_storage;
  69
  70     plugin_config conf;
  71 } plugin_data;
这是因为同样的配置可能会出现多次,
 
     cgi.assign = (...)       全局配置
 
     $SERVER("host") = "" {
          cgi.assign = (...)  非全局配置,或者"条件”配置
     }
 
所谓“全局配置”,即config_storage[0]保存的配置,它通常是config文件中未包含在某个配置块中的配置。关于“条件配置”的具体定义参考doc/outdated/configuration.txt。

接下来我们看mod_cgi_patch_connection的实现,
1206 #define PATCH(x) \
1207     p->conf.x = s->x;
1208 static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p) {
1209     size_t i, j;
1210     plugin_config *s = p->config_storage[0];
1211
1212     PATCH(cgi);
1213     PATCH(execute_x_only);
1214
先复制全局配置,如果没有全局配置,那么先复制第一个。
1215     /* skip the first, the global context */
1216     for (i = 1; i < srv->config_context->used; i++) {
1217         data_config *dc = (data_config *)srv->config_context->data[i];
1218         s = p->config_storage[i];
1219
1220         /* condition didn't match */
1221         if (!config_check_cond(srv, con, dc)) continue;
1222
这里的“条件”检查,是决定用哪个配置的关键。如何符合条件就要merge。什么叫符合条件? 也就是connection是否符合<field> <operator> <value>,例如,是不是符合某个cookie,目的是否是某个host,IP:port是否是某个socket等等。config_check_cond具体的实现就不看了。
1223         /* merge config */
1224         for (j = 0; j < dc->value->used; j++) {
1225             data_unset *du = dc->value->data[j];
1226
1227             if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.assign"))) {
1228                 PATCH(cgi);
1229             } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.execute-x-only"))) {
1230                 PATCH(execute_x_only);
1231             }
1232         }
1233     }
1234
1235     return 0;
1236 }
1237 #undef PATCH

2.7 
Handle subrequest_start
1239 URIHANDLER_FUNC(cgi_is_handled) {                                                                                                  
1240     size_t k, s_len;                                                                                                               
1241     plugin_data *p = p_d;                                                                                                          
1242     buffer *fn = con->physical.path;                                                                                               
1243     stat_cache_entry *sce = NULL;                                                                                                  
1244                                                                                                                                    
1245     if (con->mode != DIRECT) return HANDLER_GO_ON;                                                                                 
1246                                                                                                                                    
1247     if (fn->used == 0) return HANDLER_GO_ON;                                                                                       
1248                                                                                                                                    
1249     mod_cgi_patch_connection(srv, con, p);                                                                                         
1250                  
在一些sanity检查后选择适当的配置。
1251     if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) return HANDLER_GO_ON;                          
1252     if (!S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;                                                                          
1253     if (p->conf.execute_x_only == 1 && (sce->st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) 
             return HANDLER_GO_ON;               
1254
接下来调用stat_cache_get_entry读取物理路径文件到stat_cache_entry结构。之后做类型,和执行权限相关检查。我们可以看到execute_x_only 选项的使用。
1255     s_len = fn->used - 1;                                                                                                          
1256                                                                                                                                    
1257     for (k = 0; k < p->conf.cgi->used; k++) {                                                                                      
1258         data_string *ds = (data_string *)p->conf.cgi->data[k];                                                                     
1259         size_t ct_len = ds->key->used - 1;                                                                                         
1260                                                                                                                                    
1261         if (ds->key->used == 0) continue;                                                                                          
1262         if (s_len < ct_len) continue;                                                                                              
1263                                                                                                                                    
1264         if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {                                                        
1265             if (cgi_create_env(srv, con, p, ds->value)) {                                                                          
1266                 con->mode = DIRECT;                                                                                                
1267                 con->http_status = 500;                                                                                            
1268                                                                                                                                    
1269                 buffer_reset(con->physical.path);                                                                                  
1270                 return HANDLER_FINISHED;                                                                                           
1271             }                                                                                                                      
1272             /* one handler is enough for the request */                                                                            
1273             break;                                                                                                                 
1274         }                                                                                                                          
1275     }                                                                                                                              
1276                                                                                                                                    
1277     return HANDLER_GO_ON;                                                                                                          
1278 }   
对于cgi.assign中的每个“key/value”条目,一律视为“后缀”,和文件名进行比较,如果匹配,调用cgi_create_env建立CGI进程。如果key不是后缀,而是整个URL的path部分,例如 
 
     cgi.assign = ("/testfile" => "")
 
也能够匹配(必须和整个“物理”path匹配)。
 
2.7.1 cgi_create_env
 
cgi_create_env是整个mod_cgi的核心函数,用于为CGI设置环境、fd,创建CGI进程。
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler);
它的前3个参数简单明了,最后一个参数cgi_handler是配置信息"=>"后面的部分,实际上也就是要执行的CGI程序,例如解释器,或者直接是可执行文件。结合此函数的调用的地方(cgi_is_handled),理一下三个变量的关系,
 
con->physical.path (fn->ptr)
用户访问的“物理”文档名,及URL的Path部分经过处理后对应的Server上的文件
p->conf.cgi->data[k]->key (ds->key)
file-extensions,要匹配的文件后缀或者路径
p->conf.cgi->data[k]->value (ds->value)
CGI Handler (解释器,或者可执行文件)
 
函数很长,我们分段解释,
738 static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
739     pid_t pid;
740  
741 #ifdef HAVE_IPV6        
742     char b2[INET6_ADDRSTRLEN + 1];
743 #endif
744  
745     int to_cgi_fds[2];
746     int from_cgi_fds[2];
747     struct stat st;     
748   
函数的内部参数pid保存CGI进程的PID,to_cgi_fdsfrom_cgi_fds分别是管道的fd,稍后解释。stcgi_handler的struct stat结构,只是用来保存一下,没有实际意义。b2是用来保存IPv6地址的buffer,也没有实际意义。
751     if (cgi_handler->used > 1) {
752         /* stat the exec file */
753         if (-1 == (stat(cgi_handler->ptr, &st))) {
754             log_error_write(srv, __FILE__, __LINE__, "sbss",
755                     "stat for cgi-handler", cgi_handler,
756                     "failed:", strerror(errno));
757             return -1;
758         }
759     }   

检查Handler文件是否存在。
 
Pipes: STDIN/STDOUT
 
Web Server(这里指lihttpd的mod_cgi模块)和CGI进程之间使用STDOUT/STDIN和环境变量进行通信(环境变量是单向的)[参考CGI标准]。将两个进程的stdxxx相连的是管道。fork之后,子进程(CGI进程)把from_cgi_fds[0](读端)关闭,把STDOUT设置为from_cgi_fd[1](写端);同时把STDIN设置为to_cgi_fds[0](读端),吧to_cgi_fds[1](写端)关闭。父进程(Web Server)则做类似的动作,但没有设置STDIN/STDOUT。
761     if (pipe(to_cgi_fds)) {
...
764     }
765        
766     if (pipe(from_cgi_fds)) {
...
771     }          
772            
773     /* fork, execve */
774     switch (pid = fork()) {
775     case 0: {
...
787         /* move stdout to from_cgi_fd[1] */
788         close(STDOUT_FILENO);
789         dup2(from_cgi_fds[1], STDOUT_FILENO);
790         close(from_cgi_fds[1]);
791         /* not needed */
792         close(from_cgi_fds[0]);
793
794         /* move the stdin to to_cgi_fd[0] */
795         close(STDIN_FILENO);
796         dup2(to_cgi_fds[0], STDIN_FILENO);
797         close(to_cgi_fds[0]);
798         /* not needed */
799         close(to_cgi_fds[1]);
...
1058         break;
1059     }   
....
1069     default: {
1070         handler_ctx *hctx;
1071         /* father */
1072
1073         close(from_cgi_fds[1]);
1074         close(to_cgi_fds[0]);        
....  
1196         break;
1197     }
1198     }  
下图是经过这些步骤之后的情况,env/exec部分稍后就会提到,

Child's Role
 
Child除了设置STDIN/STDOUT之外,还需要为Hander的执行准备环境变量。环境变量是根据配置,以及Server,Conn的相关信息重头准备的。下面的代码假设支持IPv6。
801         /* create environment */
802         env.ptr = NULL;
803         env.size = 0;
804         env.used = 0;
805
806         if (buffer_is_empty(con->conf.server_tag)) {
807             cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_DESC));
808         } else {
809             cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_BUF_LEN(con->conf.server_tag));
810         }
...
准备ENV的代码就不列了,没多大意义。不过可以看看都设置了、或者说可以设置(根据配置)哪些Env(从CGI标准中也可以找到类似的列表), 
 
SERVER_SOFTWARE       软件名,可配置
SERVER_NAME           Server名或者IP地址
GATEWAY_INTERFACE     CGI版本,“CGI/1.1”
SERVER_PROTOCOL       HTTP版本
SERVER_PORT
SERVER_ADDR
REQUEST_METHOD
PATHINFO
REDRECT_STATUS
QUERY_STRING
REQUEST_URI
REMOTE_ADDR
REMOTE_PORT
HTTPS                 是不是“on”
CONTENT_LENGTH
SCRIPT_FILENAME       con->physical.path
SCRIPT_NAME           con->uri.path
DUCUMENT_ROOT         con->physical.basedir
LD_PRELOAD            从Server继承给CGI
LD_LIBRARY_PATH       从Server继承给CGI
SYSTEMROOT            从Server继承给CGI
 
除了以上的变量,还需要冲request.header中把HTTP的各个Header传递到env里面。这些Header的名字转换为环境变量名的时候,会加上HTTP_的前缀(除了CONTENT_TYPE),并全部改为大写,
 
954         for (n = 0; n < con->request.headers->used; n++) {
955             data_string *ds;
956
957             ds = (data_string *)con->request.headers->data[n];
958
959             if (ds->value->used && ds->key->used) {
960                 size_t j;
961
962                 buffer_reset(p->tmp_buf);
963
964                 if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
965                     buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("HTTP_"));
966                     p->tmp_buf->used--; /* strip \0 after HTTP_ */
967                 }
968
969                 buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
970
971                 for (j = 0; j < ds->key->used - 1; j++) {
972                     char cr = '_';
973                     if (light_isalpha(ds->key->ptr[j])) {
974                         /* upper-case */
975                         cr = ds->key->ptr[j] & ~32;
976                     } else if (light_isdigit(ds->key->ptr[j])) {
977                         /* copy */
978                         cr = ds->key->ptr[j];
979                     }
980                     p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
981                 }
982                 p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
983
984                 cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
985             }
986         }
con->enviornment中的环境变量是用户配置的,要传递给CGI的变量,
988         for (n = 0; n < con->environment->used; n++) {
989             data_string *ds;
990
991             ds = (data_string *)con->environment->data[n];
992
993             if (ds->value->used && ds->key->used) {
994                 size_t j;
995
996                 buffer_reset(p->tmp_buf);
997
998                 buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
999
1000                 for (j = 0; j < ds->key->used - 1; j++) {
1001                     char cr = '_';
1002                     if (light_isalpha(ds->key->ptr[j])) {
1003                         /* upper-case */
1004                         cr = ds->key->ptr[j] & ~32;
1005                     } else if (light_isdigit(ds->key->ptr[j])) {
1006                         /* copy */
1007                         cr = ds->key->ptr[j];
1008                     }
1009                     p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
1010                 }
1011                 p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
1012
1013                 cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
1014             }
1015         }
接下来为exec调用准备参数,并把fd>=3的FD全部关闭,
1017         if (env.size == env.used) {
1018             env.size += 16;
1019             env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
1020         }
1021
1022         env.ptr[env.used] = NULL;
1023
1024         /* set up args */
1025         argc = 3;
1026         args = malloc(sizeof(*args) * argc);
1027         i = 0;
1028
1029         if (cgi_handler->used > 1) {
1030             args[i++] = cgi_handler->ptr;
1031         }
1032         args[i++] = con->physical.path->ptr;
1033         args[i  ] = NULL;
1034
1035         /* search for the last / */
1036         if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
1037             *c = '\0';
1038
1039             /* change to the physical directory */
1040             if (-1 == chdir(con->physical.path->ptr)) {
1041                 log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
1042             }
1043             *c = '/';
1044         }
1045
1046         /* we don't need the client socket */
1047         for (i = 3; i < 256; i++) {
1048             if (i != srv->errorlog_fd) close(i);
1049         }
1050 
最后,执行cgi,
1051         /* exec the cgi */
1052         execve(args[0], args, env.ptr);
1053
1054         /* log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]); */
1055
1056         /* */
1057         SEGFAULT();
1058         break;
 
Father's Role 
 
看看Web Server这边都需要做些什么。HTTP Request的部分已经通过环境变量传递给CGI了,如果Content-Length不为0,则还需要将之前从client收到的body部分传递给CGI,
1076         if (con->request.content_length) {
1077             chunkqueue *cq = con->request_content_queue;
1078             chunk *c;
1079
1080             assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
1081
1082             /* there is content to send */
1083             for (c = cq->first; c; c = cq->first) {
....
1131                     if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) {
....
1148                 if (r > 0) {
1149                     c->offset += r;
1150                     cq->bytes_out += r;
1151                 } else {
....
1155                 }
1156                 chunkqueue_remove_finished_chunks(cq);
1157             }
其间涉及的FILE_CHUNK, MEM_CHUNK暂不讨论。发送完body后,就没有什么在需要传递给CGI的了,所以先把写方向的管道关闭。接下来,需要从CGI读取返回的数据,作为HTTP Response递交给HTTP Client,但lighttpd是非阻塞IO加事件驱动的“单进程(Worker)” WebServer,我们不能阻塞在这里等待,于是为CGI注册一个fdevent的Handler来处理来自CGI(from_cgi_fd[0])的数据。
1160         close(to_cgi_fds[1]);
1161                        
1162         /* register PID and wait for them asyncronously */
1163         con->mode = p->id;
1164         buffer_reset(con->physical.path);
1165                        
1166         hctx = cgi_handler_ctx_init();
1167                                    
1168         hctx->remote_conn = con;
1169         hctx->plugin_data = p;
1170         hctx->pid = pid;   
1171         hctx->fd = from_cgi_fds[0];
1172         hctx->fde_ndx = -1;
1173
1174         con->plugin_ctx[p->id] = hctx;
1175                        
1176         fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
1177         fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);  只需要读取
1178                    
1179         if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
...              资源清理,记录日志,返回错误
1194         }
1195
1196         break;

hctx是要交给fdevent的私有数据,类型是handler_ctx,各自的的意义见[2.1]的描述。我们要看看cgi_handler_fdevent是如何实现的。
 
Receive From CGI: cgi_handler_fdevent
623 static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) {
624     handler_ctx *hctx = ctx;
625     connection  *con  = hctx->remote_conn;
626
627     joblist_append(srv, con);
...
635     if (revents & FDEVENT_IN) {
636         switch (cgi_demux_response(srv, hctx)) {
637         case FDEVENT_HANDLED_NOT_FINISHED:
638             break;
639         case FDEVENT_HANDLED_FINISHED:
645             cgi_connection_close(srv, hctx);
648             return HANDLER_FINISHED;
649         case FDEVENT_HANDLED_ERROR:
...
660             break;
661         }
662     }
663
664     if (revents & FDEVENT_OUT) {
665         /* nothing to do */
666     }
667 
668     /* perhaps this issue is already handled */
669     if (revents & FDEVENT_HUP) {
...
699     } else if (revents & FDEVENT_ERR) {
...
707         return HANDLER_ERROR;
708     }
709
710     return HANDLER_FINISHED;
711 }
cgi_handler_fdevent主要作用是接收CGI程序的返回数据,它是在cgi_demux_response中处理的。余下的部分负责处理信号和错误情况。
338 static int cgi_demux_response(server *srv, handler_ctx *hctx) {
339     plugin_data *p    = hctx->plugin_data;
340     connection  *con  = hctx->remote_conn;
341
342     while(1) {
...
349         if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) {
350             buffer_prepare_copy(hctx->response, 4 * 1024);
351         } else {
352             if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT;
353             buffer_prepare_copy(hctx->response, toread + 1);
354         }
看看一次能读多少数据,如果少于4K,以4K记。大于4K电话,不能大于上限。这里的read是非阻塞的,所有如果暂时没有数据可读(EAGAIN), 
则函数立即返回FDEVENT_HANDLED_NOT_FINISHED。把它们到hctx->response中。while(1)说明,如果有东西可以读,就持续的读出这是为了提高效率(由事件触发,一旦“可读”事件到来,就尽力的读取。这里没有像Kernel收网络数据那样做一个读取次数的限制,也没有NAPI那样的bottom half)。
357         if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) {
358             if (errno == EAGAIN || errno == EINTR) {
359                 /* would block, wait for signal */
360                 return FDEVENT_HANDLED_NOT_FINISHED;
361             }
362             /* error */
363             log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd);
364             return FDEVENT_HANDLED_ERROR;
365         }
366       
367         if (n == 0) {
368             /* read finished */
369
370             con->file_finished = 1;
371
372             /* send final chunk */
373             http_chunk_append_mem(srv, con, NULL, 0);
374             joblist_append(srv, con);
375
376             return FDEVENT_HANDLED_FINISHED;
377         }
378 
379         hctx->response->ptr[n] = '\0';
380         hctx->response->used = n+1;
能走到下面,代表读到了一些东西。虽然内容未必是ASCII字符,但最后留出一个'\0',对于调试很方便的。con->file_started标记表示,body,及document部分有没有开始。这个以连续的\r\n\r\n判定(当然如果少掉\r也是可以容忍的)。
 
header的处理阶段(file_started == 0)和body的处理阶段。header的处理本身没什么特别的,无非是看看有没有header,header什么时候结束。header结束有两种可能
  1. 1. 有header字段,并且出现"\n(\r?)\n"字符串
  2. 2. 尚未见到任何header字段的情况下,之间出现"(\r)\n"
这意味着CGI程序必须通过空行告知header域的结束,无论是否有header。
 
“body”过程的部分,这部分只需要调用http_chunk_append_mem将读到的任何数据写入con->write_queue,后者会作为response发送给Client。
384         if (con->file_started == 0) {
...
390             buffer_append_string_buffer(hctx->response_header, hctx->response);
...
453             if (is_header_end) {
454                 if (!is_header) {
455                     /* no header, but a body */
456
457                     if (con->request.http_version == HTTP_VERSION_1_1) {
458                         con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
459                     }
460
461                     http_chunk_append_mem(srv, con, hctx->response_header->ptr, 
                                hctx->response_header->used);
462                     joblist_append(srv, con);
463                 } else {
...
486                     /* parse the response header */
487                     cgi_response_parse(srv, con, p, hctx->response_header);
...
499                 }
500
501                 con->file_started = 1;
502             }
503         } else {
504             http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used);
505             joblist_append(srv, con);
506         }
各个buffer的关系,以及不同阶段总结如下图,

之前有一个函数我们还没看,即如果有header并且header已经读完后,会调用它:cgi_response_parse,这个函数会根据CGI的返回的header部分,设置一些重要的字段,例如con->http_status等。
231 static int cgi_response_parse(server *srv, connection *con, plugin_data *p, buffer *in) {
...
238     buffer_copy_string_buffer(p->parse_response, in);
plugin_data->parse_response用来解析CGI的response。然后一次一行,
240     for (s = p->parse_response->ptr;
241                 NULL != (ns = strchr(s, '\n'));
242                 s = ns + 1, line++) {
...
252         if (line == 0 &&
253             0 == strncmp(s, "HTTP/1.", 7)) {
...
262                 status = strtol(s+9, NULL, 10);
263
264                 if (status >= 100 &&
265                     status < 1000) {
266                     /* we expected 3 digits and didn't got them */
267                     con->parsed_response |= HTTP_STATUS;
268                     con->http_status = status;
269                 }
...
271         } else {
提取并设置http status,位图con->parsed_response包含了以下“各位:)”(base.h)。
 
/* fcgi_response_header contains ... */
#define HTTP_STATUS         BV(0)
#define HTTP_CONNECTION     BV(1)
#define HTTP_CONTENT_LENGTH BV(2)
#define HTTP_DATE           BV(3)
#define HTTP_LOCATION       BV(4)
 
后面解析各个Header,插入con->response.headers,并设置位图内相应的位。
...
285             if (NULL == (ds = (data_string *)array_get_unused_element(
                        con->response.headers, TYPE_STRING))) {
286                 ds = data_response_init();
287             }
288             buffer_copy_string_len(ds->key, key, key_len);
289             buffer_copy_string(ds->value, value);
290
291             array_insert_unique(con->response.headers, (data_unset *)ds);
292
293             switch(key_len) {
294             case 4:
295                 if (0 == strncasecmp(key, "Date", key_len)) {
296                     con->parsed_response |= HTTP_DATE;
297                 }
298                 break;
...             ...
322             default:
323                 break;
324             }
325         }
326     }
327
328     /* CGI/1.1 rev 03 - 7.2.1.2 */
329     if ((con->parsed_response & HTTP_LOCATION) &&
330         !(con->parsed_response & HTTP_STATUS)) {
331         con->http_status = 302;
332     }
333
334     return 0;
335 }
如果未能判定出 HTTP_Status,视为302。
 
2.8 Handle Subrequest
 
2.9 Connection Close (Reset)

posted @ 2012-09-16 21:30  beacer  阅读(2834)  评论(0编辑  收藏  举报