[项目][WebServer][CGI机制 && 设计]详细讲解

目录


1.何为CGI机制?

  • CGI (Common Gateway Interface)是外部应用程序 (CGI程序)与WEB服务器之间的接口标准,是在CGI程序和WEB服务器之间传递信息的过程

2.理解CGI机制

  • 真正理解CGI并不简单,先从现象入手
    • 浏览器除了从服务器获得资源(网页、图片、文字等),有时候还能上传一些东西(提交表单、注册用户之类)
    • 目前HttpServer只能进行获得资源,并不能进行资源上传,所以目前HttpServer并不具有交互式功能
    • 为了让网站能够实现交互式,需要使用CGI功能完成
  • 理论上,可以使用任何语言来编写CGI程序
    • 注意:http提供CGI机制,和CGI程序是两码事
      • 如:学校(http)提供教学平台(CGI机制),学生(CGI程序)来学习
  • 实现上理解,首先要理解GET方法和POST方法的区别
    • GET方法从浏览器传参给http服务器时,是将参数跟套URI后面的
      • 如:www.baidu.com/test_cgi?x=100&y=200
      • GET方法,如果没有传参,http按照一般的方式进行,返回资源即可
        • 静态网页、各种资源
      • GET方法,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器
    • POST方法从浏览器传参给http服务器时,是将参数放到请求正文的
      • POST方法,一般都需要使用CGI方式来进行处理
  • 什么时候,需要使用CGI来进行数据处理呢?
    • 只要用户有数据上传上来
  • 如何看待CGI程序呢?
    • 子CGI程序的标准输入时浏览器
    • 子CGI程序的标准输出时浏览器
    • 通信细节由http完成
  • 浏览器和Server进行数据交互的本质,就是进程间通信,也是socket通信的本质

3.CGI接口设计

1.ProcessNonCgi

  • 该接口处理静态网页的请求,只需要将静态网页打开即可
cpp 复制代码
int ProcessNonCgi()
{
    _response.fd = open(_request.path.c_str(), O_RDONLY);
    if(_response.fd >= 0)
    {
        return OK;
    }

    return NOT_FOUND;
}

2.ProcessCgi

  • 父子进程间如何传递数据?
    • POST参数、子进程处理结果用匿名管道通信
    • 请求方法、GET参数用环境变量通信
      • 环境变量是具有全局属性的,可以被子进程继承下去,不受exec*程序替换的影响
  • 给子进程传递环境变量时,亲测用setenv()比putenv()靠谱些
    • putenv()不会复制传递的字符串大小,而setenv()会的
  • 注意:程序替换,只替换代码和数据,并不替换内核进程相关的数据结构
    • 如文件描述符表
    • 在程序替换之后,数据没有了,但是曾经打开的文件PIPE还在
  • 进程替换之后,子进程如何得知,对应的读写文件描述符时多少呢?
    • 虽然替换后子进程不知道对应的读写fd,但是一定知道0 && 1
    • 此时不需要知道读写fd了,只需要读0写1即可
    • 在执行exec*前,dup2重定向
cpp 复制代码
int ProcessCgi()
{
    int code = 0; // 退出码
    std::string &bin = _request.path;

    // 父子间通信用匿名管道 // TODO 待整理
    int input[2]; // 父进程读
    int output[2]; // 父进程写

    if(pipe(input) < 0)
    {
        LOG(ERROR, "Pipe Input Error");
        code = SERVER_ERROR;
        return code;
    }

    if(pipe(output) < 0)
    {
        LOG(ERROR, "Pipe Output Error");
        code = SERVER_ERROR;
        return code;
    }

    pid_t id = fork();
    if(id == 0) // Child
    {
        close(output[1]);
        close(input[0]);

        // 子进程如何知道方法是什么?
        setenv("METHOD", _request.method.c_str(), 1);

        // GET带参通过环境变量导入子进程
        if(_request.method == "GET")
        {
            setenv("ARG", _request.arg.c_str(), 1);
            LOG(INFO, "GET Method, Add ARG");
        }
        else if (_request.method == "POST")
        {
            setenv("CLENGTH", std::to_string(_request.content_length).c_str(), 1);
            LOG(INFO, "POST Method, Add Content_Length");
        }
        else
        {
            // Do Nothing
        }

        // 进程替换之后,子进程如何得知,对应的读写文件描述符是多少呢?
        // 虽然替换后子进程不知道对应读写fd,但是一定知道0 && 1
        // 此时不需要知道读写fd了,只需要读0写1即可
        // 在exec*执行前,dup2重定向
        dup2(input[1], 1);
        dup2(output[0], 0);

        execl(bin.c_str(), bin.c_str(), nullptr);

        exit(5);
    }
    else if(id < 0)
    {
        LOG(ERROR, "Fork Error");
        code = SERVER_ERROR;
        return code;
    }
    else // Parent
    {
        close(output[0]);
        close(input[1]);

        if(_request.method == "POST")
        {
            // 不能确保一次性就能写完,所以
            const char *start = _request.request_body.c_str();
            int size = 0, total = 0;
            while (total < _request.request_body.size() &&
                   (size = write(output[1], start + total, _request.request_body.size() - total) > 0))
            {
                total += size;
            }
        }

        // 读取CGI子进程的处理结果
        char ch = 'K';
        while(read(input[0], &ch, 1) > 0)
        {
            // CGI执行完之后的结果,并不可以直接返回给浏览器,因为这部分内容只是响应正文
            _response.response_body += ch;
        }

        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret == id)
        {
            if(WIFEXITED(status))
            {
                if(WEXITSTATUS(status) == 0)
                {
                    code = OK;
                }
                else
                {
                    code = BAD_REQUEST;
                }
            }
            else
            {
                code = SERVER_ERROR;
            }
        }

        close(output[1]);
        close(input[0]);
    }

    return OK;
}
相关推荐
UIU1144 分钟前
递归算法与汉诺塔
c++·学习·算法·c#·递归
Fruit_Caller20 分钟前
static
开发语言·c++
律宏阔5 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔5 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴5 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千6 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei376 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei376 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei376 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
codigger6 小时前
ObjectSense:一门千行内核、把可靠性写进骨子里的面向对象脚本语言
开发语言·编程·编程语言