[项目][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;
}
相关推荐
_平凡之路_8 分钟前
解决ubuntu22.04 gnome-terminal 无法启动的问题
linux·运维·python
YRr YRr11 分钟前
在Windows上安装WSL2和Ubuntu 20.04以搭建C++开发环境的详细指南
c++·windows·ubuntu·wsl2
豆本-豆豆奶11 分钟前
23个Python在自然语言处理中的应用实例
开发语言·python·自然语言处理·编程语音
凯子坚持 c11 分钟前
0基础带你入门Linux之使用
linux·运维·服务器
曳渔17 分钟前
Java-数据结构-二叉树-习题(三)  ̄へ ̄
java·开发语言·数据结构·算法·链表
Farewell_me25 分钟前
C++模拟实现list:list、list类的初始化和尾插、list的迭代器的基本实现、list的完整实现、测试、整个list类等的介绍
c++·list
EterNity_TiMe_28 分钟前
【Linux基础IO】深入Linux文件描述符与重定向:解锁高效IO操作的秘密
linux·运维·服务器·学习·性能优化·学习方法
python-码博士28 分钟前
Rosetta 一:手把手教你用Linux安装Rosetta(全网最简洁)
linux·运维·服务器
你可以自己看33 分钟前
python中函数式编程与高阶函数,装饰器与生成器,异常处理与日志记录以及项目实战
服务器·开发语言·python
神秘的土鸡1 小时前
Linux中Docker容器构建MariaDB数据库教程
linux·运维·服务器·数据库·docker·mariadb