粗谈CGI
先看看 维基百科上面关于 CGI的介绍http://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
一般我们在开发Web运用的时候很少接触CGI,这种底层的处理细节。但如果你想彻底理解Resquest-Response 过程,自己编写运用服务器就有必要详细了解CGI,很多语言的动态网页技术都是基于CGI的思想,对CGI进行扩展,比如Python的WSGI,Perl的PSGI。
有一篇介绍CGI很好的文章 http://www.jdon.com/idea/cgi.htm
我们知道HTTP Server只能处理用户的静态请求,但是如果用户的一个请求,请求的数据需要从数据库里面获取怎么办(我们称之为动态请求)。
我们在编写某个Web运用系统的时候,我们不使用任何任何HTTP服务器,我们使用最原始的Socket编程,我们编写程序自己解析用户的请求(HTTP协议),如果是静态文件,我们直接调用处理静态文件的方法,如果是动态请求我们则调用处理相应动态请求的方法,在整个过程中除了处理HTTP协议,我们没有使用任何其他协议,这种处理很直接,过程也很清楚。但是如果系统要增加新的功能,我们则要添加或修改对应的方法,很不容易扩展和维护。如果按照这种方式去编写一个Web网站,那么对程序员的要求就特别高,不仅要精通Socket编程和HTTP协议,还要会HTML编程。
下面是图示和为代码
1 while true: 2 conn = server.accept() #接受连接 3 req = conn.read() #读取用去请求 4 headers = parse_http(reqr) #解析用户请求 5 if is_static(headers[url]): #如果是静态请求 6 res_str = do_static(req_file) 7 8 else if is_dynamic(headers[url]): #如果是动态请求 9 res_str = do_dynamic(req) 10 11 res_str = end_handler(res_str) #对响应的字符串进行加工 12 conn.write(res_str) #向用户输出相应的结果 13 conn.close() #关闭连接
按照上面那种方式的话,我们每编写一个Web系统都要重复以上步骤。而且整个处理动态请求的方法要用同一种语言。所以这种可行但不适用。其实按照上面这种 思路的话编写的就是一个运用系统了。
思路没变,我们把重复的步骤提取出了,也就是除了动态处理的所有步骤,构成了一个HTTP Server。如果我们要开发一个网站系统,只用编写相应的HTTP的静态文件,和编写处理动态请求的脚本。我们称之为CGI脚本,CGI脚本可以由任何语言编写。 我们知道大部分高性能的HTTP Server 都是用C/C++编写的,如果HTTP Server要调用的CGI脚本,调用CGI脚本我们可以使用Unix中的 execve 系统,下面是一些execve系统的一些函数及其用法。
1 函数名: exec... 2 功 能: 装入并运行其它程序的函数 3 用 法: int execl(char *pathname, char *arg0, arg1, ..., argn, NULL); 4 int execle(char *pathname, char *arg0, arg1, ..., argn, NULL, 5 char *envp[]); 6 int execlp(char *pathname, char *arg0, arg1, .., NULL); 7 int execple(char *pathname, char *arg0, arg1, ..., NULL, 8 char *envp[]); 9 int execv(char *pathname, char *argv[]); 10 int execve(char *pathname, char *argv[], char *envp[]); 11 int execvp(char *pathname, char *argv[]); 12 int execvpe(char *pathname, char *argv[], char *envp[]); 13 程序例: 14 15 /* execv example */ 16 #include <process.h> 17 #include <stdio.h> 18 #include <errno.h> 19 20 void main(int argc, char *argv[]) 21 { 22 int i; 23 24 printf("Command line arguments:\n"); 25 for (i=0; i<argc; i++) 26 printf("[%2d] : %s\n", i, argv[i]); 27 28 printf("About to exec child with arg1 arg2 ...\n"); 29 execv("CHILD.EXE", argv); 30 31 perror("exec error"); 32 33 exit(1); 34 }
但是我们调用CGI脚本的时候要使用到一些请求的参数信息吧。在CGI里面有一个重要的名称 Environment variables 环境变量,下面列出 CGI/1.1 定义的环境变量
1 Environment variables 2 3 Environment variables are used to pass data about the request from 4 the server to the script. They are accessed by the script in a system 5 defined manner. In all cases, a missing environment variable is 6 equivalent to a zero-length (NULL) value, and vice versa. The 7 representation of the characters in the environment variables is 8 system defined. 9 10 Case is not significant in the names, in that there cannot be two 11 different variable whose names differ in case only. Here they are 12 shown using a canonical representation of capitals plus underscore 13 ("_"). The actual representation of the names is system defined; for 14 a particular system the representation may be defined differently to 15 this. 16 17 The variables are: 18 19 AUTH_TYPE 20 CONTENT_LENGTH 21 CONTENT_TYPE 22 GATEWAY_INTERFACE 23 HTTP_* 24 PATH_INFO 25 PATH_TRANSLATED 26 QUERY_STRING 27 REMOTE_ADDR 28 REMOTE_HOST 29 REMOTE_IDENT 30 REMOTE_USER 31 REQUEST_METHOD 32 SCRIPT_NAME 33 SERVER_NAME 34 SERVER_PORT 35 SERVER_PROTOCOL 36 SERVER_SOFTWARE
从字面上大家应该看出来所代表的含义吧,是不是有点类似 HTTP headers 。讲到这里大家应该很清楚 CGI 的处理过程了吧。总结来说当Web服务器接受CGI请求时,服务器将设置一些CGI程序的环境变量,运行CGI脚本时,CGI脚本在从环境变量中获取感兴趣的变量(比如获取 查询字符串 QUERY_STRING),进行处理,响应结果。至于如何设置和获取环境变量请查看 详解 Unix环境变量。
在列出一段 HTTP Server处理CGI请求的源码:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <string.h> 5 6 main(int ac, char *av[]) 7 { 8 int sock, fd; 9 FILE *fpin; 10 char request[BUFSIZ]; 11 12 if ( ac == 1 ){ 13 fprintf(stderr,"usage: ws portnum\n"); 14 exit(1); 15 } 16 sock = make_server_socket( atoi(av[1]) ); 17 if ( sock == -1 ) exit(2); 18 19 /* main loop here */ 20 21 while(1){ 22 /* take a call and buffer it */ 23 fd = accept( sock, NULL, NULL ); 24 fpin = fdopen(fd, "r" ); 25 26 /* read request */ 27 fgets(request,BUFSIZ,fpin); 28 printf("got a call: request = %s", request); 29 read_til_crnl(fpin); 30 31 /* do what client asks */ 32 process_rq(request, fd); 33 34 fclose(fpin); 35 } 36 } 37 38 /* ------------------------------------------------------ * 39 read_til_crnl(FILE *) 40 skip over all request info until a CRNL is seen 41 ------------------------------------------------------ */ 42 43 read_til_crnl(FILE *fp) 44 { 45 char buf[BUFSIZ]; 46 while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 ) 47 ; 48 } 49 50 /* ------------------------------------------------------ * 51 process_rq( char *rq, int fd ) 52 do what the request asks for and write reply to fd 53 handles request in a new process 54 rq is HTTP command: GET /foo/bar.html HTTP/1.0 55 ------------------------------------------------------ */ 56 57 process_rq( char *rq, int fd ) 58 { 59 char cmd[BUFSIZ], arg[BUFSIZ]; 60 61 /* create a new process and return if not the child */ 62 if ( fork() != 0 ) 63 return; 64 65 strcpy(arg, "./"); /* precede args with ./ */ 66 if ( sscanf(rq, "%s%s", cmd, arg+2) != 2 ) 67 return; 68 69 if ( strcmp(cmd,"GET") != 0 ) 70 cannot_do(fd); 71 else if ( not_exist( arg ) ) 72 do_404(arg, fd ); 73 else if ( isadir( arg ) ) 74 do_ls( arg, fd ); 75 else if ( ends_in_cgi( arg ) ) 76 do_exec( arg, fd ); 77 else 78 do_cat( arg, fd ); 79 }
上面只列出了一部分HTTP Server代码,下面列出处理CGI请求的代码:
1 do_exec( char *prog, int fd ) 2 { 3 FILE *fp ; 4 5 fp = fdopen(fd,"w"); 6 header(fp, NULL); 7 fflush(fp); 8 dup2(fd, 1); 9 dup2(fd, 2); 10 close(fd); 11 execl(prog,prog,NULL); 12 perror(prog); 13 }
这里用到了Unix I/O重定向技术,也就是把脚本里面的标准输出(Java的System.out.print()Python 的 print )直接连接到fd,也就是说你在脚本里面的 print 结果就是用户接受到的结果。
当然CGI这种技术是最基本的也是效率最低的,每次一个CGI请求都要fork()一次,而且你只能在CGI脚本要和Web Server在同一台机器上。现在出现很多技术取代它比如FastCGI,SCGI,而每个不同的语言都对CGI进行了扩展,形成了自己的规范比如Python的WSGI。而且每种语言在将Web系统部署在HTTP Server上面上的时候都有自己的解决方案,最常用的就是扩展HTTP Server的模块,编写相应的处理模块,比如Python的 mod_python其实本质上就是在Apache中嵌入一个Python解释器。而现在非常流行的一种架构方案就是 HTTP服务器做前端代理,接受用户请求,对于静态文件请求则直接响应给用户,对于动态请求则转发给运用服务器,运用服务器将处理的结果反馈给HTTP服务器,然后HTTP服务器在返回给用户。也就是 Server/Gateway 模式,比如 Python中组合 Nginx+Gunicorn,Nginx是代理服务器,而Gnicorn 是 WSGI服务器,Nginx将动态请求转发给Gnicorn,Gnicorn在将请求按照封装为符合WSGI规范的的请求,然后在调用相应的的app,由于WSGI服务器由Python编写,所以可以直接调用对应的方法即可,不用在fork()。(这里的处理过程类似于CGI但是规范之Python独有的WSGI规范,更加适合Python处理)详情处理过程可参看Python的 wsgiref模块。 WSGI类似于Java的Servlet,Gnicron类似于Tomcat.
如果想了解CGI的处理过程,建议直接看 Python CGI的源码,非常容易理解。
如果想了解 CGI/1.1 规范 请点击 http://tools.ietf.org/html/draft-robinson-www-interface-00
posted on 2013-12-11 21:55 Arts&Crafts 阅读(405) 评论(0) 编辑 收藏 举报