写在最前面

cgi接口实现相对比较简单,现在基本不受人关注了,但是鹅厂好多程序依旧采用的cgi接口标准实现的,所以还是懂细节为好

CGI 1.1 规范 http://www.faqs.org/rfcs/rfc3875.html

cgi

通用网关接口(Common Gateway Interface)是一个Web服务器主机提供信息服务的标准接口。通过CGI接口,Web服务器就能够获取客户端提交的信息,转交给服务器端的CGI程序进行处理,最后返回结果给客户端。

The Common Gateway Interface (CGI) is a simple interface for running external programs, software or gateways under an information server in a platform–idependent manner. Currently, the supported information servers are HTTP servers

疑问

  • 接口标准是什么
  • webserver又是如何调起cgi程序的

cgi 接口标准

标准输入

CGI程序像其他可执行程序一样,可通过标准输入(stdin)从Web服务器得到输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的POST方法。这意味着在操作系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是常用的方法

环境变量

操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了自己的一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过环境变量QUERY-STRING向CGI程序传递Form中的数据。
对于CGI程序来说,它继承了系统的环境变量。CGI环境变量在CGI程序启动时初始化,在结束时销毁。当一个CGI程序不是被HTTP服务器调用时,它的环境变量几乎是系统环境变量的复制。当这个CGI程序被HTTP服务器调用时,它的环境变量就会多了以下关于HTTP服务器、客户端、CGI传输过程等项目。

说明 分类
REQUEST_METHOD 请求方法 请求
QUERY_STRING GET所传输信息 请求
CONTENT_LENGTH stdin中有效信息长度 请求
CONTENT_TYPE 所传信息的mime类型 请求
CONTENT_FILE 传送文件名 请求
PATH_INFO 路径信息 请求
PATH_TRANSLATED CGI程序的完整路径名 请求
SCRIPT_NAME 所调用的CGI程序的名字 请求
GATEWAY_INTERFACE CGI 版本 默认为1.1 服务器相关
SERVER_NAME 服务器ip或名字
SERVER_PORT 主机端口
SERVER_SOFTWARE 调用CGI程序的HTTP服务器名称和版本号
REMOTE_ADDR 客户机主机 客户端
REMOTE_HOST ip 地址
ACCEPT 列出能被次请求接受的应答方式
ACCEPT_ENCODING 列出客户机支持的编码方式
ACCEPT_LANGUAGE 表明客户机可接受语言的ISO代码
AUTHORIZAION 认证
FROM email地址
IF_MODIFIED_SINGCE
PRAGMA
REFFERER 指出连接到当前文档的文档的URL
USER_AGENT 客户端浏览器的信息

标准输出

CGI程序通过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息可以用各种格式,通常是以纯文本或者HTML文本的形式,这样我们就可以在命令行状态调试CGI程序,并且得到它们的输出。

CGI的格式输出内容必须组织成标题/内容的形式
CGI标准规定了CGI程序可以使用的三个HTTP标题。标题必须占据第一行输出, 而且必须随后带有一个空行。

标题 描述
Content-Type 数据的MIME类型
Location 重定向
Status HTTP状态码

实现原理

clientclientwebserverwebservercgi程序cgi程序http协议根据CGI接口标准传递环境变量、标准输入给CGI程序根据CGI接口标准返回html数据可选封装发给client

webserver调用cgi程序采用fork-and-exec方式,复制一个进程,传递环境变量,并通过pipe将cgi程序的输入流和输出流进行重定向
(cgi程序)read <- - stdin <- - read endpoint – –pipe1– –write endpoint <- - write(web server)
(cgi程序)write - -> stdout <- - write endpoint – –pipe2– –read endpoint - -> read(web server)

C 模拟

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int forkexec(char * cmd, char * input, char * env[]);

int main(){
    // 设置环境变量
    char * envp[] = {"REQUEST_METHOD=GET", 0};
    // 模拟需要发送给cgi程序的数据
    char *str = "this a text";
    // cgi程序路径
    char path[100];
    getcwd(path,sizeof(path));
    strcat(path,"/test");
    forkexec(path,str,envp);
}
// 核心
int forkexec(char *cmd, char *input, char * const envp[]) {
    pid_t pid;
    int fd[2];
    int fds[2];
    int n;
    int status;
    char message[1024];
    pipe(fd);
    pipe(fds);
    pid = fork();
    if (pid == 0) {
        close(fd[0]);
        close(fds[1]);
        dup2(fds[0], fileno(stdin));  // 将stdin重定向到fds[0]
        dup2(fd[1],fileno(stdout)); // 将stdout 重定向到 fd[1]
        char * argv[]={"test",(char *)0};
        execve(cmd,argv,envp);    // 执行外部程序
    } else {
        close(fd[1]);
        close(fds[0]);
        printf("parent process, pid=%d\n",getpid());
        char *str = "this is parent message\n";
        write(fds[1], str, strlen(str));
        close(fds[1]);
        memset(message,0,sizeof(message));
        char line[100];
        while(1) {
            memset(line,0,sizeof(line));
            n = read(fd[0],line,sizeof(line));
            if(n < 1){
                break;
            }
            strcat(message, line);
        }
        printf("读取子进程返回的数据 n = %d\n", n);
        printf("子进程的执行结果为: %s\n",message);
        wait(&status);
        printf("status = %d\n",status);
    }
    return 0;
}

核心函数为: dup2(oldfd,newfd) 、 execve(char *path, char *argv[], char *envp[])、pipe(fd)
cgi脚本如下,使用php编写

#!/usr/bin/php
<?php
$content = file_get_contents("php://stdin");
echo $content, "\n";
echo "this is a test\n";
var_dump(getenv());
echo "the end of child\n";
文档更新时间: 2021-01-27 02:13   作者:周国强