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{}。

lighttpd - Plugin: CGI

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_fds from_cgi_fds 分别是管道的fd,稍后解释。 st cgi_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部分稍后就会提到,

lighttpd - Plugin: CGI

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的关系,以及不同阶段总结如下图,

lighttpd - Plugin: CGI

之前有一个函数我们还没看,即如果有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)

你可能感兴趣的:(lighttpd)