文章紧跟之前web服务器返回静态网页

文章目录

  • CGI设计思路:
  • 设计CGI代码

CGI设计思路:

CGI原理如下图:

其中CGI程序可以使用任何后端语言进行编写。

而服务器是可执行程序,而CGI程序也是可执行程序。
可以创建子进程使用进程替换来实现exec系列函数来实现。
详情见
Linux_进程替换(execl、execlp、execv、execle)


服务器与CGI程序之间还涉及进程通信。这里采用两个匿名管道进行双向通信。
Linux_Centos进程间通信_管道(匿名管道_命名管道)
这里为了不产生歧义,管道的读写全部站在父进程的角度。

父进程通过input来读取CGI程序数据,output来向CGI程序数据。
父进程关闭input[1],output[0];

子进程通过向input写入数据,output拿取数据
子进程关闭input[0],output[1];


注意:进程替换,原进程的文件描述符值新进程无法得知,但是原进程打开的文件,以及文件描述符组所指向不会改变。(只替换代码和数据,并不替换内核进程相关的数据结构)。

解决方法:这里使用重定向原则,让替换后的进程读取管道数据向标准输入读取,写入数据向标准输出写入即可。在进程替换前进行重定向。

dup2将oldfd指向的文件拷贝到newfd所指向的文件。


POST方法参数在正文中,传递参数时通过管道传参数。
GET方法参数在请求行上,这里采用环境变量进行传参。更高效,putenv函数与getenv函数
所以替换的进程还需要知道HTTP的方法才可以得知需要从哪里拿取参数。

环境变量具有全局属性,子进程可以看到。其次环境变量也不受进程替换的影响。

注意:在测试cgi时打印时需要使用cerr输出,因为标准输出与标准输入已经被重定向。

设计CGI代码

服务器将请求数据解析后导入环境变量中。

#pragma once //已经存在套接字,线程通过套接字处理任务#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.h"
#include<string>
#include<vector>
#include"Log.h"#include<sstream>
#include<unordered_map>#include<sys/stat.h>
#include<algorithm>#include<sys/sendfile.h>
#include<fcntl.h>#include<sys/wait.h>#define OK 200
#define NOTFOUND 404#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define VERSON_HTTP "HTTP/1.0"
#define LINE_END "\r\n" //行结尾标志static std::string CodeToInfo(int code){std::string Info;switch(code){case 200:Info="OK";break;case 404:Info="NotFound";break;default:break;}return Info;
}//HTTP响应报文
class HttpResponse{public:std::string StatusLine_HTTP;//状态行std::vector<std::string>ResponHeads;//首部字段std::string ResponBlank=LINE_END;//空行std::string ResponBody;//正文int status_code=OK;//响应状态码int fd=-1;//储存发送网页的文件描述符size_t size;//打开网页的大小};//HTTP请求报文
class HttpRequest{public:std::string RequestLine_HTTP;//请求行std::vector<std::string>RequestHeads;//首部字段std::string RequestBlank;//空行std::string RequestBody;//正文//解析完请求报文后的结果std::string Method;std::string URI;//Path?Pararmstd::string Version;//保存解析首部字段的mapstd::unordered_map<std::string,std::string>Head_KVS;int Content_Lenth=0;//访问资源的路径std::string Path;//如果是GET方法通过URL上传的参数std::string Param;bool CGI=false;std::string Type;//请求文件类型
};//读取请求,分析请求,构建响应,基本IO通信,实现基本业务逻辑
class EndPoint{private:int sock;HttpRequest http_request;//http请求HttpResponse http_response;//http响应private:void GetHttpRequestLine(){//读请求行Util::ReadLine(sock,http_request.RequestLine_HTTP);//读取HTTP请求第一行http_request.RequestLine_HTTP.pop_back();ERRORLOG(INFO,http_request.RequestLine_HTTP);}void GetHttpRequstHeads(){//读取首部字段std::string line;while(true){Util::ReadLine(sock,line);if(line=="\n"){//ERRORLOG(INFO,line);http_request.RequestBlank=line;break;}line.pop_back();//去掉每行的\nhttp_request.RequestHeads.push_back(line);ERRORLOG(INFO,line);line.clear();}}void AnalyQuestLine(){//解析请求行  方法 URL HTTP版本std::stringstream Str(http_request.RequestLine_HTTP);Str>>http_request.Method>>http_request.URI>>http_request.Version;//将方法统一转化成大写 Get->GETstd::string& strtmp=http_request.Method;std::transform(strtmp.begin(),strtmp.end(),strtmp.begin(),::toupper);//写回strtmp首部}void AnalyuestHeadS(){std::string key;std::string value;for(auto&line:http_request.RequestHeads){if(Util::CutString(line,key,value,": ")){http_request.Head_KVS.insert(std::make_pair(key,value)); }else{ERRORLOG(FATA,"AnalyuestHeadS error");}}}bool HaveHttpBody(){//判断是否是GET方法,GET方法没有正文std::string& Method=http_request.Method;if(Method=="POST"){std::unordered_map<std::string,std::string>::iterator iter=http_request.Head_KVS.find("Content-Length");if(iter!=http_request.Head_KVS.end()){http_request.Content_Lenth=atoi(iter->second.c_str());return true;}}return false;}void GetHttpBody(){if(HaveHttpBody()){//std::cout<<"需要读取正文"<<std::endl;int Content_Lenth=http_request.Content_Lenth;char ch=0;while(Content_Lenth>0){ssize_t size=recv(sock,&ch,1,0);if(size>0){http_request.RequestBody.push_back(ch);Content_Lenth--;}else{break;}}//std::cout<<http_request.RequestBody<<std::endl;}}int ProceNoCGI(size_t size){//构建HTTP响应网页//填充状态行http_response.fd=open(http_request.Path.c_str(),O_RDONLY);//只读方式打开if(http_response.fd>0){std::string& Str=http_response.StatusLine_HTTP;Str+=VERSON_HTTP;Str+=" ";Str+=std::to_string(http_response.status_code);Str+=" ";Str+=CodeToInfo(http_response.status_code);Str+=LINE_END;http_response.size=size;std::string Content_Lenth_str="Content-Length: ";Content_Lenth_str+=std::to_string(size);Content_Lenth_str+=LINE_END;http_response.ResponHeads.push_back(Content_Lenth_str);std::string Content_Type_str="Content-Type: ";Content_Type_str+=Util::SuffixToDesc(http_request.Type);Content_Type_str+=LINE_END;http_response.ResponHeads.push_back(Content_Type_str);return OK;}return NOTFOUND;}int ProceCGI(){//进程替换std::string&bin=http_request.Path;//父进程数据std::string& query=http_request.Param;//GET方法参数在请求行std::string& body=http_request.RequestBody;//POST方法参数在正文int PostReadSize=http_request.Content_Lenth;//POST方法需要导入正文大小的环境变量std::string query_env;std::string method_env;std::string PostReadSize_env;int exit_code=OK;//退出码int input[2];int output[2];//站在父进程角度if(pipe(input)<0){//创建管道失败->ERRORLOG(ERROR,"pipe input error");exit_code=NOTFOUND;return exit_code;}if(pipe(output)<0){ERRORLOG(ERROR,"pipe output error");exit_code=NOTFOUND;return exit_code;//->}pid_t pid=fork();if(pid==0){//子进程//需要替换的程序在PATH上,给程序传递的参数与方法有关。close(input[0]);close(output[1]);method_env="METHOD=";method_env+=http_request.Method;//把方法也传给子进程putenv((char*)method_env.c_str());//std::cerr<<"debug# "<<method_env<<std::endl;//如果是GET方法,通过环境变量传参if(http_request.Method=="GET"){query_env="QUERY_STRING=";query_env+=query;putenv((char*)query_env.c_str());ERRORLOG(INFO,"putenv Query_string!");}else if(http_request.Method=="POST"){PostReadSize_env="CONTENT_LENGTH=";PostReadSize_env+=std::to_string(PostReadSize);putenv((char*)PostReadSize_env.c_str());ERRORLOG(INFO,"putenv Content_Lenth!");}//向input[1]写入->1 向output[0]进行读取->0dup2(input[1],1);dup2(output[0],0);//std::cerr<<"debug# "<<bin.c_str()<<std::endl;execl(bin.c_str(),bin.c_str(),nullptr);//读0写1//cerr<<"替换失败"<<std::endl;exit(1);//替换失败}else if(pid<0){//创建进程失败->ERRORLOG(ERROR,"fork error");return NOTFOUND;}else{//父进程close(input[1]);close(output[0]);if(http_request.Method=="POST"){//将数据写入到管道中      //  std::cerr<<"写入开始"<<std::endl;const char*start=body.c_str();int total=0;//已经写了几个字符ssize_t size=0;//写了几个字符//保证全部数据都写入while(total<http_request.Content_Lenth&&(size=write(output[1],start+total,body.size()-total))>0){total+=size;}}//父进程获取子进程处理结果char ch=0;while(read(input[0],&ch,1)>0){http_response.ResponBody.push_back(ch);}int status=0;pid_t ret=waitpid(pid,&status,0);//阻塞等待if(ret==pid){if(WIFEXITED(status)){if(WEXITSTATUS(status)==0){//进程正常退出exit_code=OK;}else{exit_code=NOTFOUND;}}else{exit_code=NOTFOUND;}}close(input[0]);close(output[1]);}return exit_code;}public:EndPoint(int _sock):sock(_sock){}void RecvQuest_HTTP(){//读取请求GetHttpRequestLine();GetHttpRequstHeads();}void AnalyQuest_HTTP(){//解析请求AnalyQuestLine();AnalyuestHeadS();//std::cout<<"解析完毕"<<std::endl;GetHttpBody();}void MakeRespon_HTTP(){//构建响应//判断请求类型std::string tmpPath;//临时保存请求路径,方便修改size_t size =0;//记录文件大小struct stat stat_buff;//记录打开文件属性size_t suffix_pos=0;//找后缀if(http_request.Method!="GET"&&http_request.Method!="POST"){ERRORLOG(WARNING,"error request");http_response.status_code=NOTFOUND;goto END;}//如果是GET方法需要处理URL,看URL是否有参数if(http_request.Method=="GET"){size_t pos=http_request.URI.find('?');if(pos!=std::string::npos){Util::CutString(http_request.URI,http_request.Path,http_request.Param,"?");http_request.CGI=true;}else{//不是通过GET传参数http_request.Path=http_request.URI;}}else if(http_request.Method=="POST"){//POSThttp_request.CGI=true;//需要CGI技术http_request.Path=http_request.URI;}//std::cout<<"Debug# URI: "<<http_request.URI<<" Path:"<<http_request.Path<<" Param:"<<http_request.Param<<std::endl;//拼接web根目录;tmpPath=http_request.Path;http_request.Path=WEB_ROOT;http_request.Path+=tmpPath;//std::cout<<"Debug# "<<http_request.Path<<std::endl;if(http_request.Path[http_request.Path.size()-1]=='/'){//默认访问index.htmlhttp_request.Path+=HOME_PAGE;}//std::cout<<"Debug# "<<http_request.Path<<std::endl;//判断路径是否合法if(stat(http_request.Path.c_str(),&stat_buff)==0){//资源存在,需要判断这个路径是否访问了路径下的某个资源,如果没有,直接将路径的默认网页响应回去if(S_ISDIR(stat_buff.st_mode)){//是目录,添加首页信息后还需要重新获取文件stat状态http_request.Path+="/";http_request.Path+=HOME_PAGE;stat(http_request.Path.c_str(),&stat_buff);}if((stat_buff.st_mode &S_IXUSR)||(stat_buff.st_mode &S_IXGRP)||(stat_buff.st_mode& S_IXOTH)){//可执行文件,需要特殊处理http_request.CGI=true;}size=stat_buff.st_size;}else{//资源不存在状态码 404ERRORLOG(WARNING,http_request.Path+" Not Found!");http_response.status_code=NOTFOUND;goto END;}suffix_pos=http_request.Path.rfind(".");if(suffix_pos==std::string::npos){http_request.Type=".html";}else{http_request.Type=http_request.Path.substr(suffix_pos);}if(http_request.CGI==true){http_response.status_code=ProceCGI();}else{http_response.status_code=ProceNoCGI(size);//一定是GET方法,一定不带参,简单的文本网页返回}
END://进行响应if(http_response.status_code!=OK){//错误响应}return;}void SendRespon_HTTP(){//发送响应send(sock,http_response.StatusLine_HTTP.c_str(),http_response.StatusLine_HTTP.size(),0);//std::cout<<"DeBug# "<<http_response.StatusLine_HTTP<<std::endl;for(size_t size=0;size<http_response.ResponHeads.size();size++){         send(sock,http_response.ResponHeads[size].c_str(),http_response.ResponHeads[size].size(),0);//std::cout<<"send succeed"<<std::endl;}   //->send(sock,http_response.ResponBlank.c_str(),http_response.ResponBlank.size(),0);sendfile(sock,http_response.fd,nullptr,http_response.size);//    std::cout<<"close fd="<<http_response.fd<<std::endl;close(http_response.fd);}~EndPoint(){close(sock);}
};class Entry{//线程执行任务的入口public:static void*SolveQuest(void*_sock){ERRORLOG(INFO,"Processing Requests...");int sock=*(int*)_sock;delete(int*)_sock;//std::cout<<" Get a New Link: sock="<<sock<<std::endl;EndPoint* endpoint=new EndPoint(sock);endpoint->RecvQuest_HTTP();endpoint->AnalyQuest_HTTP();endpoint->MakeRespon_HTTP();endpoint->SendRespon_HTTP();delete endpoint;ERRORLOG(INFO,"Processing Request End!");return nullptr;}
};

CGI程序代码

工具头文件

#pragma once //提供编码过程中所需要的工具函数 #include<string>
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<unordered_map>class Util{public:static int ReadLine(int sock,std::string&outBuff){char ch='D';//ch先随机赋不为'\n'、'\r'的值while(ch!='\n'){//将三种行结束符统一转化为\n换行ssize_t size=recv(sock,&ch,1,0);if(size>0){if(ch=='\r'){//特殊处理 \r->\n   \r\n->\n//查看\r后的字符,不取走recv(sock,&ch,1,MSG_PEEK);if(ch=='\n'){//是\r\n换行格式,重复recv即可recv(sock,&ch,1,0);//用\n将\r覆盖}else{//是\r的格式,修改chch='\n';}}outBuff.push_back(ch);//换行格式一定是\n}else if(size==0){return 0;//对端关闭}else{return -1;//读取错误}}return outBuff.size();//返回读取一行字符的个数}static bool CutString(std::string&src,std::string& key,std::string&value,std::string gist){//根据gist(: )字符串切分HTTP首部字段,将切分的两个字符串放入到map中size_t pos=src.find(gist);if(pos!=std::string::npos){key=src.substr(0,pos);value=src.substr(pos+gist.size());//默认截取到字符串尾return true;}return false;}static std::string SuffixToDesc(const std::string&suffix){static std::unordered_map<std::string,std::string>Desc={{".html","text/html"},{".css","text/css"},{".js","application/javascript"},{".jpg","application/x-jpg"},{".xml","application/xml"},{".htm","text/html"},{".ttf","font/ttf"},{".woff","font/woff"},{".woff2","font/woff2"}};      auto pos=Desc.find(suffix);if(pos!=Desc.end()){return pos->second;}return "text/html";}
};
#include<unistd.h>
#include<stdlib.h>
#include<string>
#include<iostream>
#include"Util.h"bool GetQuerySrt(std::string& query_str){std::string method=getenv("METHOD");bool resault=true;if(method=="GET"){query_str=getenv("QUERY_STRING");std::cerr<<"Debug# "<<query_str<<std::endl;resault=true;}else if(method=="POST"){int Content_Lenth=atoi(getenv("CONTENT_LENGTH"));char ch=0;while(Content_Lenth>0){read(0,&ch,1);query_str.push_back(ch); Content_Lenth--;}resault=true;}else{resault=false;}return resault;
}int main(){std::string query_str;GetQuerySrt(query_str);// a=100&b=200std::string str1;std::string str2;Util::CutString(query_str,str1,str2,"&");std::string key1;std::string value1;Util::CutString(str1,key1,value1,"=");std::string key2;std::string value2;Util::CutString(str2,key2,value2,"=");//重定向标准输出,直接向标准输出打印字符,调用方可以通过read读取std::cout<<key1<<"->"<<value1<<std::endl;std::cout<<key2<<"->"<<value2<<std::endl;std::cerr<<"Debug:"<<key1<<"->"<<value1<<std::endl;std::cerr<<"Debug: "<<key2<<"->"<<value2<<std::endl;return 0;
}


Github项目代码位置
Gitee项目代码位置

Linux_网络项目_WEB服务器 设计CGI机制,与CGI处理数据相关推荐

  1. 网络教学资源平台设计与实现--公告发布系统数据表

    表4.0公告信息表 字段名称 数据类型 大小 必填字段 说明 noticeID int 10 是 公告ID 主键 AdminID Int 10 管理员ID外键 TchID Int 10 教师ID 外键 ...

  2. 网络编程_HTTP协议_Web服务器_(TCP3次握手4次挥手,长短连接,伪静态、静态和动态)

    Python高级语法--网络编程--进阶学习笔记 文中案例参考: https://github.com/FangbaiZhang/Python_advanced_learning/tree/maste ...

  3. 定时器 槽函数没执行_Web服务器项目详解 07 定时器处理非活动连接(上)

    点击"两猿社" 关注我们 Web服务器详解目录 00 项目概述 01 线程同步机制包装类 02 半同步/半反应堆线程池(上) 03 半同步/半反应堆线程池(下) 04 http连接 ...

  4. 高并发服务器开源项目,百万级高并发WebRTC流媒体服务器设计与开发(示例代码)...

    第1章 课程导学与准备工作 本章主要介绍为何要学习WebRTC流媒体服务器开发,以及本门课能为我们带来哪些收获.之后会为大家介绍本课程内容具体安排,最后给出如何学好这门课程的一些学习建议.希望大家都能 ...

  5. 阿里云域名解析网络和服务架构设计(四) 之阿里云ECS服务器Nginx代理实践

    一.回顾 阿里云域名解析网络和服务架构设计总概览(一)_飞鸽FlyGo的博客-CSDN博客云解析DNS.负载均衡SLB.阿里云ECS服务器.阿里云ECS服务器Nginx代理https://flygo. ...

  6. 网络安全技术与应用实验——SSL验证分析 基于DTLS的安全服务器设计

    1. SSL验证分析 实验目的:通过实验,掌握SSL的基本原理,掌握扫描器的基本原理和基本工具Wireshark的使用. 实验内容:使用Wireshark工具分析TCP连接中主机和服务器之间传输SSL ...

  7. 【Java 网络编程】UDP 服务器 客户端 通信 ( DatagramSocket | DatagramPacket | UDP 发送数据包 | UDP 接收数据包 | 端口号分配使用机制 )

    文章目录 I UDP 信息发送接收原理 II UDP 发送和接收端口相同 III UDP 发送信息代码示例 IV UDP 接收信息代码示例 V UDP 服务器端代码示例 VI UDP 客户端代码示例 ...

  8. 基于Java EE平台项目管理系统的设计与实现(论文+PPT+源码)

    分类号_______________ 密级________________ UDC _______________ 学号 毕业设计(论文) 论文题目 基于Java EE平台项目管理系统的设计与实现 T ...

  9. 计算机网络规划与建设,网络工程规划与设计

    <网络工程规划与设计>是2012年人民邮电出版社出版的图书,作者是李银玲. 书    名 网络工程规划与设计 作    者 李银玲 ISBN 978-7-115-27824-1 类     ...

最新文章

  1. win10 动态磁盘 linux,win10系统动态磁盘改为基本磁盘的方法
  2. 淘宝、美团、滴滴分别如何搭建大数据平台?
  3. 6.项目资源管理总结
  4. 简单介绍Python中的几种数据类型
  5. ASP.NET Core 管道再探
  6. Windows高级编程学习笔记(二)
  7. 从SVN资源库下载项目
  8. 爆火的Java面试题-kafka源码解析与实战豆瓣
  9. PHP 扩展 Mongo 与 MongoDB
  10. POJ 3237 Tree (树链拆分)
  11. 概率论与数理统计【一】- 随机事件与概率(1):古典概型与几何概型
  12. 陈洁kiki宠粉节狂欢夜观看破千万!四年磨一剑将宠粉做到极致
  13. 【回眸】牛客网刷刷刷!嵌入式软件中也会遇到的嵌入式硬件,通讯,通讯协议专题(一)
  14. c语言读取midi文件举例子,c# – 使用NAudio从MIDI文件中读取音符
  15. 微信小程序的父子组件传值
  16. 澳洲网:超8成雇主对澳大利亚高校毕业生表示满意
  17. 体育视野杂志体育视野杂志社体育视野编辑部2022年第5期目录
  18. 求无限循环小数的循环节
  19. git 撤销上次提交 commit
  20. python三大器,Python 入门之 Python三大器 之 生成器

热门文章

  1. 计算机导师名人,天哪,这些名人的老师竟然也这么牛!
  2. 如何通俗理解并快速写出麦克斯韦方程组?
  3. Excel 中用公式列出唯一值——模拟高级筛选功能
  4. 杭漂结束(end)|我从有赞离职啦|结束杭漂
  5. Version in “./docker-compose.yml“ is unsupported.
  6. “网购懊悔权”为何与“机动车撞人负全责”一样扯淡?
  7. vue----前端精选50道面试题---一个小白的孤芳自赏
  8. js实现图片的延迟加载
  9. CoreDNS篇1-简介和安装
  10. 17位行业影响力者的数字藏品2022年趋势研判!丨巴比特数字藏品高峰论坛金句实录...