18.3.7 SHTTPD支持CGI的实现
CGI支持的实现主要包含CGI命令获取、CGI参数获取、管道进程间连接、主进程从CGI进程读取数据和发送数据、CGI进程执行并发送结果给主进程。
(1)实现CGI的过程,先初始化变量,这已经是比较通用的方法。
#define CGISTR "/cgi-bin/" /*CGI目录的字符串*/
#define ARGNUM 16 /*CGI程序变量的最大个数*/
#define READIN 0 /*读出管道*/
#define WRITEOUT 1 /*写入管道*/
int cgiHandler(struct worker_ctl *wctl)
{
struct conn_request *req = &wctl->conn.con_req;
struct conn_response *res = &wctl->conn.con_res;
char *command = strstr(req->uri, CGISTR) + strlen(CGISTR);
/*获得匹配字符串/cgi-bin/之后的地址*/
char *arg[ARGNUM];
int num = 0;
char *rpath = wctl->conn.con_req.rpath;
stat *fs = &wctl->conn.con_res.fsate;
int retval = -1;
(2)将指针指向字符串CGI命令,并找到之后的“?”或者结束符,作为CGI命令的字符串,在字符串末尾用“/0”填充,构成字符串,并与CGIRoot共同生成一个CGI全路径命令。
char *pos = command; /*查找CGI的命令*/
for(;*pos != '?' && *pos !='/0';pos++) /*找到命令末尾*/
;
*pos = '/0';
sprintf(rpath, "%s%s",conf_para.CGIRoot,command); /*构建全路径*/
(3)CGI的参数为紧跟CGI命令后“?”的字符串,多个变量之间用“+”连接起来。所以可以根据“+”的数量确定参数的个数,这里假设参数最多有16个。参数放在arg中,参数的个数由变量num确定。
/*CGI的参数*/
pos++;
for(;*pos != '/0' && num < ARGNUM;)
{
arg[num] = pos;
for(;*pos != '+' && *pos!='/0';pos++)
;
if(*pos == '+')
{
*pos = '/0';
pos++;
num++;
}
}
arg[num] = NULL;
(4)查看CGI命令的属性,确定不是目录并且可以执行。
/*命令的属性*/
if(stat(rpath,fs)<0)
{
/*错误*/
res->status = 403;
retval = -1;
goto EXITcgiHandler;
}
else if((fs->st_mode & S_IFDIR) == S_IFDIR)
{
/*是一个目录,列出目录下的文件*/
}
else if((fs->st_mode & S_IXUSR) != S_IXUSR)
{
/*所指文件不能执行*/
res->status = 403;
retval = -1;
goto EXITcgiHandler;
}
(5)创建管道,用于进程间通信。
/*创建进程间通信的管道*/
int pipe_in[2];
int pipe_out[2];
if(pipe[pipe_in] < 0)
{
res->status = 500;
retval = -1;
goto EXITcgiHandler;
}
if(pipe[pipe_out] < 0)
{
res->status = 500;
retval = -1;
goto EXITcgiHandler;
}
(6)将进程分叉,主进程处理客户端的连接,CGI进程处理CGI脚本。在主进程中,关闭管道pipe_out的写和管道pipe_in的读。
/*进程分叉*/
int pid = 0;
pid = fork();
if(pid < 0) /*错误*/
{
res->status = 500;
retval = -1;
goto EXITcgiHandler;
}
else if(pid > 0) /*父进程*/
{
close(pipe_out[WRITEOUT]);
close(pipe_in[READIN]);
(7)主进程从CGI端的标准输出读取数据,并将数据发送到网络资源请求的客户端。当CGI进程端结束后,等待其全部子进程的结束。最后关闭管道。
int size = 0;
int end = 0;
while(size > 0 && !end)
{
size = read(pipe_out[READIN], res->res.ptr, sizeof(wctl->conn.
dres)); /*读取CGI进程端数据*/
if(size > 0)
{
send(wctl->conn.cs, res->res.ptr, strlen(res->res.ptr));
/*将数据发送给客户端*/
}
else
{
end = 1;
}
}
wait(&end); /*等待其子进程全部结束*/
close(pipe_out[READIN]); /*关闭管道*/
close(pipe_in[WRITEOUT]);
retval = 0;
}
(8)在CGI进程中,先将客户端发送过来的CGI脚本及参数形成一个字符串,然后将pipe_out管道的写端和标准输出绑定在一起。这个管道和主进程的读端是连在一起的,当CGI进程的程序向标准输出发送数据的时候,主进程中会收到其发送的数据。最后执行 脚本。
Else /*子进程*/
{
char cmdarg[2048];
char onearg[2048];
char *pos = NULL;
int i = 0;
/*形成执行命令*/
memset(onearg, 0, 2048];
for(i = 0;i<num;i++)
sprintf(cmdarg,"%s %s", onearg, arg[i]);
/*将写入的管道绑定到标注输出*/
close(pipe_out[READIN]); /*关闭无用的读管道*/
dup2(pipe_out[WRITEOUT], 1); /*将写管道绑定到标注输出*/
close(pipe_out[WRITEOUT]); /*关闭写管道*/
close(pipe_in[WRITEOUT]);
dup2(pipe_in[READIN], 0);
close(pipe_in[READIN]);
execlp(rpath, arg); /*执行命令,命令的输出需要为标准输出*/
}
EXITcgiHandler:
return retval;
}