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

DieSnowK 2024-09-16 09:03:07 阅读 55

目录

1.何为CGI机制?2.理解CGI机制3.CGI接口设计1.ProcessNonCgi2.ProcessCgi


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后面的

如:<code>www.baidu.com/test_cgi?x=100&y=200GET方法,如果没有传参,http按照一般的方式进行,返回资源即可

静态网页、各种资源 GET方法,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器 POST方法从浏览器传参给http服务器时,是将参数放到请求正文的

POST方法,一般都需要使用CGI方式来进行处理 什么时候,需要使用CGI来进行数据处理呢?

只要用户有数据上传上来 如何看待CGI程序呢?

子CGI程序的标准输入时浏览器子CGI程序的标准输出时浏览器通信细节由http完成 浏览器和Server进行数据交互的本质,就是进程间通信,也是socket通信的本质

请添加图片描述


3.CGI接口设计

1.ProcessNonCgi

该接口处理静态网页的请求,只需要将静态网页打开即可

<code>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重定向

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;

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。