CGI 即 Common Gateway Interface,译作“通用网关接口”,为了理解它,我们需要首先知道:静态网页和动态网页。
- 静态网页是指内容固定的网页,通常是事先写好的html文档,每次访问得到的都是相同的内容。
- 而动态网页是指多次访问可以得到不同内容的网页,现在流行的动态网页技术有PHP、JSP、ASP等。
CGI规定了外部应用程序(CGI程序)如何与Web服务器交换信息,但由于有许多缺点,现在几乎已经被淘汰。
CGI 如何工作
以http://guodongxiaren.me/cgi-bin/helloworld.cgi
为例子
输入
-
Web 服务器在调用
helloworld.cgi
之前,会把各类HTTP请求中的信息以环境变量的方式写入OS。CGI程序本质是OS上一个普通的可执行程序,它通过语言本身库函数来获取环境变量,从而获得数据输入。 -
除环境变量外,另外一个CGI程序获取数据的方式就是标准输入(stdin)。如post请求一个CGI的URL,那么POST的数据,CGI是通过标准输入来获取到的。
输出
而CGI如何构造出数据(比如HTML页面)返回给浏览器呢?其实CGI本身只要向标准输出去写入数据即可。比如printf、cout
等。因为Web服务器已经做了重定向,将标准输出重定向给Web服务器的与浏览器连接的socket。
实例
《深入理解计算机系统》中有一个简单的C语言Web 服务器实例,里面有一个简单CGI例子。
- 源代码:https://github.com/jingedawang/Tiny-WebServer
- 部署
gcc -O2 -Wall -I . -g -o tiny tiny.c csapp.c -lpthread chmod +x tiny cd cgi-bin && make && cd .. chmod +x cgi-bin/adder

关键代码分析
- main
... listenfd = Open_listenfd(argv[1]); while (1) { clientlen = sizeof(clientaddr); connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); printf("Accepted connection from (%s, %s)\n", hostname, port); doit(connfd); Close(connfd); }
- doit
Rio_readinitb(&rio, fd); // 读取请求行 if (!Rio_readlineb(&rio, buf, MAXLINE)) return; printf("%s", buf); sscanf(buf, "%s %s %s", method, uri, version); ... // parse_uri 里面查找"cgi-bin",判断是静态请求还是动态请求。 is_static = parse_uri(uri, filename, cgiargs); if (is_static) { /* Serve static content */ ... } else { /* Dynamic content */ if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program"); return; } serve_dynamic(fd, filename, cgiargs); }
- serve_dynamic
void serve_dynamic(int fd, char *filename, char *cgiargs) { char buf[MAXLINE], *emptylist[] = { NULL }; /* Return first part of HTTP response */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); Rio_writen(fd, buf, strlen(buf)); sprintf(buf, "Server: Tiny Web Server\r\n"); Rio_writen(fd, buf, strlen(buf)); if (Fork() == 0) { /* Child */ /* Real server would set all CGI vars here */ setenv("QUERY_STRING", cgiargs, 1); // 将参数通过系统的环境变量的方式设置 Dup2(fd, STDOUT_FILENO); // 将标准输出重定向到连接套接字 Execve(filename, emptylist, environ); /* Run CGI program */ } Wait(NULL); /* Parent waits for and reaps child */ //line:netp:servedynamic:wait }
- cgi 程序
int main(void) { char *buf, *p; char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE]; int n1=0, n2=0; /* Extract the two arguments 从系统换进变量中取出参数 */ if ((buf = getenv("QUERY_STRING")) != NULL) { p = strchr(buf, '&'); *p = '\0'; strcpy(arg1, buf); strcpy(arg2, p+1); n1 = atoi(arg1); n2 = atoi(arg2); } /* Make the response body */ sprintf(content, "Welcome to add.com: "); sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content); sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>", content, n1, n2, n1 + n2); sprintf(content, "%sThanks for visiting!\r\n", content); /* Generate the HTTP response 标准输出已经重定向 */ printf("Connection: close\r\n"); printf("Content-length: %d\r\n", (int)strlen(content)); printf("Content-type: text/html\r\n\r\n"); printf("%s", content); flush(stdout); exit(0); }
- cgi 程序
setenv/getenv
dup2
mmap
缺点
-
每次HTTP请求CGI时,Web服务器都有启动一个新的进程去执行这个CGI程序。当用户请求量大的时候,这个拉起一个新进程的操作会严重拖慢Web服务器的性能。
-
缺乏URL路由的功能,基本上一个CGI都是独立提供给外界访问,一个CGI就是独立的可执行程序。因此 不仅CGI的URL比较丑陋,而且容易暴露真实路径。
参考资料
1、https://www.jianshu.com/p/dd580395bf11
2、《深入理解计算机系统》
3、https://zhuanlan.zhihu.com/p/25013398
网友评论