CGI : Common Gateway Interface

引言

本篇文章学习了CGI的原理和一个简单的http服务器的实现,该服务器支持CGI。

给出CGI和http服务器参考地址,读者可以移步这里:

http://www.cnblogs.com/liuzhang/p/3929198.html (CGI)
https://github.com/EZLippi/Tinyhttpd (httpserver)

什么是CGI

引自wikipedia:
Common Gateway Interface (CGI) is a standard way for web servers to interface with executable programs installed on a server that generate web pages dynamically. Such programs are known as CGI scripts or simply CGIs; they are usually written in a scripting language, but can be written in any programming language.

为什么要引入CGI

引文: http://www.cnblogs.com/liuzhang/p/3929198.html (很好的文章)

最早的Web服务器简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态html。事物总是不断发展,网站也越来越复杂,所以出现动态技术。但是服务器并不能直接运行 php,asp这样的文件,自己不能做,外包给别人吧,但是要与第三做个约定,我给你什么,然后你给我什么,就是握把请求参数发送给你,然后我接收你的处理结果给客户端。那这个约定就是 common gateway interface,简称cgi。这个协议可以用vb,c,php,python 来实现。cgi只是接口协议,根本不是什么语言。下面图可以看到流程

cgi

一个简单的http服务器

https://github.com/EZLippi/Tinyhttpd

网友EZLippi用C语言写了一个简单的http服务器,逻辑比较清晰,展示了对HTTP协议的处理过程,以及对CGI的支持。HTTP协议是建立在TCP协议之上的,对HTTP协议的分析主要还是分析读取的每一行内容,比较简单。下面主要分析对CGI的实现:

大体过程是这样的:

服务器从URL中获取cgi的名字,然后fork一个子进程,设置好环境变量和管道(使用dup2函数将STDIN、STDOUT重定向到管道)之后,使用exec系列函数运行cgi程序。cgi程序可以从STDIN或环境变量表中读取数据,然后把结果写到STDOUT就完事了。
而服务器会从管道读取cgi程序的处理结果,返回给浏览器。

这里使用的进程间通信方法是管道。

    // 创建管道
    if (pipe(cgi_output) < 0) {
        cannot_execute(client);
        return;
    }
    if (pipe(cgi_input) < 0) {
        cannot_execute(client);
        return;
    }
    // 创建子进程
    if ( (pid = fork()) < 0 ) {
        cannot_execute(client);
        return;
    }
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    if (pid == 0)  /* child: CGI script */
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];

        dup2(cgi_output[1], STDOUT); // 做重定向,STDOUT指向cgi_output的写端
        dup2(cgi_input[0], STDIN); // 做重定向,STDIN指向cgi_input的读端
        close(cgi_output[0]); //子进程不需要从cgi_output读,所以close它
        close(cgi_input[1]);  //子进程不需要向cgi_input写,所以close它
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
        putenv(meth_env);     // 设置环境变量
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        }
        else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }
        execl(path, NULL); // 执行cgi程序
        exit(0);
    } else {    /* parent */
        close(cgi_output[1]); // 父进程不需要向cgi_output写,所以close它
        close(cgi_input[0]);  // 父进程不需要从cgi_input读,所以close它
        if (strcasecmp(method, "POST") == 0)
            for (i = 0; i < content_length; i++) {
                recv(client, &c, 1, 0);
                write(cgi_input[1], &c, 1); // 把POST的数据通过管道发给CGI程序
            }
        while (read(cgi_output[0], &c, 1) > 0) // 读取CGI程序的返回结果
            send(client, &c, 1, 0); // 然后把CGI的返回结果发给客户端(浏览器)

        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }

fork()之后,之前打开的文件描述符在子进程中仍是打开的,(使用了CLOSE_ON_EXEC的除外),所以父子进程关闭了无用的fd。

server—————————————-cgi
cgi_output[0] <—————- cgi_output[1] (STDOUT)
cgi_input[1] —————-> cgi_input[0] (STDIN)

因为执行CGI程序之前对管道做了重定向,所以cgi程序操作标准输入和标准输出就可以了。

附上作者给出的cgi脚本程序,使用perl语言编写:

#!/usr/bin/perl -Tw

use strict;
use CGI;

my($cgi) = new CGI;
print $cgi->header;
my($color) = "blue";
$color = $cgi->param('color') if defined $cgi->param('color');

print $cgi->start_html(-title => uc($color),
                       -BGCOLOR => $color);
print $cgi->h1("This is $color");
print $cgi->end_html;

在附上我用C语言写的CGI,与上面的perl脚本功能相同


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

int main()
{
    size_t content_len = 0; 
    char response[1024];

    printf("HTTP/1.0 200 OK\r\n");
    printf("Content-Type: text/html\r\n");
    printf("\r\n");

    content_len = atoi(getenv("CONTENT_LENGTH")); // 读取环境变量
    int nr = read(0, response, content_len); // 从stdin读取父进程通过管道发送过来的数据
    response[nr] = '\0';
    char *color = strchr(response, '=') + 1; // 分析出颜色

    // 结果写到stdout,服务器会从管道读取到这些数据,返回给浏览器显示
    puts("<html>\r\n");
    puts("<head>\r\n");
    printf("<title>%s</title>\r\n", color);
    puts("</head>\r\n");
    printf("<body bgcolor=\"%s\">\r\n", color);
    puts("<h1>This is green, Generate by CGI written in C</h1>\r\n");
    puts("</body>\r\n");
    puts("</html>\r\n");

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值