cgi机制及实现(代码片段)

4nc414g0n 4nc414g0n     2022-11-28     848

关键词:

CGI机制及实现

CGI

概念部分摘自具体参考:Nginx运行FastCGI程序(ngx_http_fastcgi_module模块、fcgi库、spwan-fcgi进程管理器)
CGI:

  • 通用网关接口(Common Gateway Interface、CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。 CGI 独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只 要这个语言可以在这个系统上运行。Unix shell script、Python、Ruby、PHP、perl、Tcl、C/C++ 和 Visual Basic 都可以用来编写 CGI 程序

过程:

  1. web 服务器收到客户端(浏览器)的请求 Http Request,启动 CGI 程序( fork-and-execute),并通过环境变量、 标准输入传递数据
  2. CGI 进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、 逻辑处理等
  3. CGI 进程将处理结果通过标准输出、标准错误,传递给 web 服务器
  4. web 服务器收到 CGI 返回的结果,构建 Http Response 返回给客户端,并杀死 CGI 进程

常用环境变量:

  • AUTH_TYPE: 存取认证类型
  • CONTENT_LENGTH: 由标准输入传递给 CGI 程序的数据长度,以bytes或字元数来 计算
  • CONTENT_TYPE: 请求的 MIME 类型
  • GATEWAY_INTERFACE: 服务器的CGI版本编号
  • HTTP_ACCEPT: 浏览器能直接接收的Content-types, 可以有 HTTP Accept header 定义
  • HTTP_USER_AGENT: 递交表单的浏览器的名称、版本和其他平台性的附加信息
  • HTTP_REFERER: 递交表单的文本的 URL,不是所有的浏览器都发出这个信息, 不要依赖它
  • PATH_INFO: 传递给CGI程序的路径信息QUERY_STRING传递给 CGI 程序的请求参数,也就是用"?"隔开,添加在 URL 后面的字串
  • REMOTE_ADDR: client端的host 名称
  • REMOTE_HOST: client端的IP 位址
  • REMOTE_USER: client端送出来的使用者名称
  • REMOTE_METHOD: client端发出请求的方法(如 get、post)
  • SCRIPT_NAME: CGI程序所在的虚拟路径,如/cgi-bin/echoe
  • SERVER_NAME: server的host名称或IP地址
  • SERVER_PORT: 收到request的server端口
  • SERVER_PROTOCOL: 所使用的通讯协定和版本编号
  • SERVER_SOFTWARE: server程序的名称和版本

缺陷:

  • CGI 方式是客户端有多少个请求,就开辟多少个子进程,每个子进程都需要启动自己的 解释器、加载配置,连接其他服务器等初始化工作,这是 CGI 进程性能低下的主要原因。当用户请求非常多的时候,会占用大量的内存、cpu 等资源,造成性能低下
  • CGI 使外部程序与 Web 服务器之间交互成为可能。CGI 程序运行在独立的进程中,并对每 个 Web 请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请 求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限 制了资源重用

环境变量的大小是有一定的限制的,当需要传送的数据量大时,储存环境变量的空间可能会不足,造成数据接收不完全,甚至无法执行 CGI 程序,因此后来又发展出另外一种方法:POST,也就是利用 I/O 重新导向的技巧,让 CGI 程序可以由 stdin 和 stdout 直接跟浏览器沟通

  • 当我们指定用这种方法传递请求的数据时,web 服务器收到数据后会先放在一块输入缓冲区中,并且将数据的大小记录在 CONTENT_LENGTH 这个环境变量,然后调用 CGI 程序并将 CGI 程序的 stdin 指向这块缓冲区,于是我们就可以很顺利的通过 stdin 和环境变数 CONTENT_LENGTH 得到所有的信息,再没有信息大小的限制了

CGI实现


服务器是一个可执行程序,加载到内存中,变成了进程,CGI程序也是一个程序、进程,如何用一个进程,去执行另一个程序------程序替换: 使用exec系列函数


通过管道与重定向实现CGI:

站父进程角度:

  • input[2];用于读出(0读 1写)
  • output[2];用于写入

fork父子进程都能看到两个管道(部分代码共享)

  • 子进程:close(input[0]), close(output[1])
    execl(bin.c_str(),bin.c_str(),nullptr) (注意进程替换不会替换当前进程的内核数据结构(文件描述符表))
    子进程程序替换后数据两个fd释放,之后就找不到管道了,所以在exec*前使用dup2重定向(stdin->input[1], stdout->output[0])
  • 父进程:close(input[1]), close(output[0])
    waitpid()

Method:

  • POST:父进程将body通过output[1]给子进程
  • GET:父进程通过环境变量putevn给子进程(环境变量也不受exec*系列函数影响)

子进程判断是GET还是POST也要 通过环境变量拿到method
注意:

  • GET为什么参数不能太长,因为通过环境变量来导入给子进程,环境变量限制
  • POST是正文,通过正文传递,参数大小没有限制

httpserver

实现
ProcessCgi():

int ProcessCgi()

   int code = OK;
   //父进程数据
   auto &method = http_request.method;
   auto &query_string =  http_request.query_string; //GET
   auto &body_text = http_request.requestBody;     //POST
   auto &bin = http_request.path; //要让子进程执行的目标程序,一定存在
   int ContentLength = http_request.ContentLength;
   auto &response_body = http_response.response_body;
   //利用环境变量将值传递给程序替换后的进程
   std::string query_string_env;
   std::string method_env;
   std::string ContentLength_env;

   //站在父进程角度 创建两个管道(父子进程分别占有input和output的两个fd)
   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;
   
   //std::cerr<<"*****"<<code<<std::endl;
   //新线程,但是从头到尾都只有一个进程,就是httpserver!
   pid_t pid = fork();
   if(pid == 0 ) //child
       close(input[0]);
       close(output[1]);

       method_env = "METHOD=";
       method_env += method;

       putenv((char*)method_env.c_str());

       if(method == "GET")
           query_string_env = "QUERY_STRING=";
           query_string_env += query_string;
           putenv((char*)query_string_env.c_str());
           LOG(LOH_LEVEL_INFO, "Get Method, Add Query_String Env");
       
       else if(method == "POST")
           ContentLength_env = "CONTENT_LENGTH=";
           ContentLength_env += std::to_string(ContentLength);
           putenv((char*)ContentLength_env.c_str());
           LOG(LOH_LEVEL_INFO, "Post Method, Add ContentLength Env");
       
       else
           //Do Nothing
         
       //std::cout << "bin: " << bin << std::endl;
       dup2(output[0], 0);
       dup2(input[1], 1);
	   //程序替换
       execl(bin.c_str(), bin.c_str(), nullptr);
       exit(1);
   

替换成功之后,目标子进程如何得知,对应的读写文件描述符是多少?重定向到stdin,stdout,只要读0, 写1即可
站在子进程角度

  • input[1]: 写出 -> 1 -> input[1]
  • output[0]: 读入 -> 0 -> output[0]

出错:

else if(pid < 0) //error
       LOG(ERROR, "fork error!");
       return 404;

Father:

  • 写入stdin
	   else //parent
       close(input[1]);
       close(output[0]);
       if(method == "POST")//
           //std::cerr<<"=FATHER====METHOD"<<method<<std::endl;
           const char *start = body_text.c_str();
           int total = 0;
           int size = 0;
           while(total < ContentLength && (size= write(output[1], start+total, body_text.size()-total)) > 0)
               total += size;
           
       
       char ch = 0;
       while(read(input[0], &ch, 1) > 0)
           response_body.push_back(ch);
       
       int status = 0;
       pid_t ret = waitpid(pid, &status, 0);
       if(ret == pid)
           if(WIFEXITED(status))//WIFEXITED(status) 若此值为非0 表明进程正常结束
               if(WEXITSTATUS(status) == 0)
                   code = OK;
               
               else
                   code = BAD_REQUEST;
               
           
           else
               code = SERVER_ERROR;
           
       

       close(input[0]);
       close(output[1]);
   
   return code;

cgi程序

获取参数

std::string method = getenv("METHOD");
//std::cerr<<"====="<<method<<"========="<<std::endl;
if(method == "GET")
   //std::cerr<<"====="<<method<<"========="<<std::endl;
   query_string = getenv("QUERY_STRING");

else if(method == "POST")
   //std::cerr<<"====="<<method<<"========="<<std::endl;
   int content_length = atoi(getenv("CONTENT_LENGTH"));
   //std::cerr<<"=====Length"<<content_length<<std::endl;
   char c = 0;
   while(content_length)
       read(0, &c, 1);
       query_string.push_back(c);
       content_length--;
   

写回处理结果:(1->input[1])

std::cout << name1 << " : " << value1 << std::endl;
std::cout << name2 << " : " << value2 << std::endl;

Fast CGI

Fast CGI

  • 快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI 致力于减少 Web 服务器 与 CGI 程式之间互动的开销,从而使服务器可以同时处理更多的 Web 请求。与为每个请求 创建一个新的进程不同,FastCGI 使用持续的进程来处理一连串的请求。这些进程由 FastCGI 进程管理器管理 而不是 web 服务器
  • 以独立的进程池运行来cgi,单独一个进程死掉,系统可以很轻易的丢弃,然后重新分配新的进程来运行逻辑
  • 由于 Fast CGI 程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的 应用效率。它的速度效率最少要比 CGI 技术提高 5 倍以上。它还支持分布式的部署,即 Fast CGI 程序可以在 web 服务器以外的主机上执行
  • CGI 是所谓的短生存期应用程序,FastCGI 是所谓的长生存期应用程序。FastCGI 像是一个常驻(long-live)型的 CGI,它可以一直执行着,不会每次都要花费时间去 fork 一次(这 是 CGI 最为人诟病的 fork-and-execute 模式)

过程:

  • 1.Web 服务器启动时载入初始化 FastCGI 执行环境。 例如 IIS、ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi
  • 2.FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程并等待来自 Web 服务器 的连接。启动 FastCGI 进程时,可以配置以 ip 和 UNIX 域 socket 两种方式启动
  • 3.当客户端请求到达Web 服务器时,Web 服务器将请求采用socket方式转发FastCGI 主进程,FastCGI 主进程选择并连接到一个 CGI 解释器。Web 服务器将 CGI 环境变量和标准输入发送到 FastCGI 子进程
  • 4.FastCGI 子进程完成处理后将标准输出和错误信息从同一 socket 连接返回 Web 服务 器。当 FastCGI 子进程关闭连接时,请求便处理完成
  • 5.FastCGI 子进程接着等待并处理来自 Web 服务器的下一个连接

常用环境变量:

  • SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径
  • QUERY_STRING $query_string; #请求的参数;如?app=123
  • REQUEST_METHOD $request_method; #请求的动作(GET,POST)
  • CONTENT_TYPE $content_type; #请求头中的 Content-Type 字段
  • CONTENT_LENGTH $content_length; #请求头中的 Content-length 字段。
  • SCRIPT_NAME $fastcgi_script_name; #脚本名称
  • REQUEST_URI $request_uri; #请求的地址不带参数
  • DOCUMENT_URI $document_uri; #与$uri 相同。
  • DOCUMENT_ROOT $document_root; #网站的根目录。在 server 配置中 root 指令中指定的值
  • SERVER_PROTOCOL $server_protocol; #请求使用的协议,通常是 HTTP/1.0 或 HTTP/1.1。
  • GATEWAY_INTERFACE CGI/1.1;#cgi 版本
  • SERVER_SOFTWARE nginx/$nginx_version;#nginx 版本号,可修改、隐藏
  • REMOTE_ADDR $remote_addr; #客户端 IP
  • REMOTE_PORT $remote_port; #客户端端口
  • SERVER_ADDR $server_addr; #服务器 IP 地址
  • SERVER_PORT $server_port; #服务器端口
  • SERVER_NAME $server_name; #服务器名,域名在 server 配置中指定的 server_name
  • PATH_INFO $path_info;#可自定义变量

具体参考:Nginx运行FastCGI程序(ngx_http_fastcgi_module模块、fcgi库、spwan-fcgi进程管理器)

synchronized底层实现及优化机制(代码片段)

synchronized底层实现及优化机制前言首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。jdk1.6之前... 查看详情

synchronized底层实现及优化机制(代码片段)

synchronized底层实现及优化机制前言首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。jdk1.6之前... 查看详情

项目设计自主http服务器(代码片段)

...TTP的协议格式HTTP的请求方法HTTP的状态码HTTP常见的HeaderCGI机制介绍CGI机制的概念CGI机制的实现步骤CGI机制的意义日志编写套接字相关代码编写HTTP服务器主体逻辑HTTP请求结构设计HTTP响应结构设计EndPoint类编写EndPoint结构设计设 查看详情

基于jwt的token认证机制实现及安全问题(代码片段)

jsONWebToken(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。载荷(Payload)"iss":"OnlineJWTBuilder","... 查看详情

android浅析recyclerview回收复用机制及实战(仿探探效果)(代码片段)

android浅析RecyclerView回收复用机制及实战,仿探探效果浅析RecyclerView回收复用机制缓存机制-onTouchEvent()入口缓存机制-onLayout()入口复用机制探探效果实战还是老套路,先来看看实现的效果!浅析RecyclerView回收复用机制在写这个效果之前... 查看详情

const实现机制及与#define的区别(代码片段)

const与#define的区别(1)编译器处理方式不同  define宏是在预处理阶段展开。  const常量是编译运行阶段使用。(2)类型和安全检查不同  define宏没有类型,不做任何类型检查,仅仅是展开。  const常量有具体的类型... 查看详情

javascript中new运算符的实现机制及手写(代码片段)

官方解释:new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。总结:创建自定义对象或者构造函数的时候使用这里我通过几个小问题的方式来解释new运算符1、怎么创建一个自定义对象呢... 查看详情

spark源码系列之累加器实现机制及自定义累加器(代码片段)

一,基本概念累加器是Spark的一种变量,顾名思义该变量只能增加。有以下特点:1,累加器只能在Driver端构建及并只能是Driver读取结果,Task只能累加。2,累加器不会改变SparkLazy计算的特点。只会在Job触发的时候进行相关累加操... 查看详情

lamp理解搭建,wordpress.xcache,powerdns及poweradmin(代码片段)

一,概念CGI:CGI全称是通用网关接口(CommonGatewayInterface),是外部应用程序与与服务器之间的接口标准,是在CGI程序和web服务器之间传递信息的规程CGI是一段程序,运行在服务器上。CGI可以用任意语言编写,主要这种语言具有标... 查看详情

深度学习attention的原理分类及实现(代码片段)

...2.5相似度计算方式3Keras实现1原理1.1简介Attention是注意力机制,本质上对关注的多个对象有个不同的权重,从而关注重点不同。Attention是Transformer、Bert、GPT的基础。引入Attention机制的原因参数少:相对于RNN、CN 查看详情

redis集群故障监测及哨兵机制原理解析(代码片段)

...继续沉入,以及常见运行时的检测工具,及哨兵机制原理的解析,以及遇到常见问题的处理Redis客户端及监控集群Redis在集群中数据同步流程集成在spring中,实现就很简单,只要连接主节 查看详情

面试题:flink反压机制及与sparkstreaming的区别(代码片段)

文章目录一、背压1.1、背压机制产生的背景二、SparkStreaming的背压机制2.1、spark1.5之前2.1.1、receiver模式2.1.2、direct模式2.1.3、缺点2.2、spark1.5之后2.2.1、一些相关的参数三、Flink背压机制3.1、背压实现3.1.1、采样线程3.1.2、Sample3.1.3、... 查看详情

java反射机制快速入门及常见方法全归纳。(代码片段)

目录一、反射机制1、基本介绍2、原理示意图3、反射基本代码实现4、反射性能 二、Class类1、基本介绍2、获取Class类对象的方式3、有Class对象的类三、类加载1、基本介绍2、连接阶段四、常见方法取类的结构信息1、常用类的方法... 查看详情

uac实现原理及绕过方法-打洞专用(代码片段)

...x03触发UAC0x04UAC虚拟化0x05UAC逆向分析1x01无文件白名单提权机制1x02DLL文件注入提权1x030day[TOC]0x00UAC工作流程UAC是微软在WindowsVista以后版本引入的一种安全机制,通过UAC 查看详情

项目设计自主http服务器(代码片段)

...TTP的协议格式HTTP的请求方法HTTP的状态码HTTP常见的HeaderCGI机制介绍CGI机制的概念CGI机制的实现步骤CGI机制的意义日志编写套接字相关代码编写HTTP服务器主体逻辑HTTP请求结构设计HTTP响应结构设计EndPoint类编写EndPoint结构设计设计... 查看详情

flink保证exactly-once机制介绍:checkpoint及twophasecommitsinkfunction(代码片段)

文章目录一、前言1.1、Flink-1.4之前的exactly-once实现1.2、Flink-1.4之后的exactly-once实现二、Exactly-onceTowPhaseCommit2.1、预提交(preCommit)2.1.1、插入检查点2.1.2、触发将状态快照写入状态后端2.1.3、和外部系统,两步提交协议来保证数据... 查看详情

flink保证exactly-once机制介绍:checkpoint及twophasecommitsinkfunction(代码片段)

文章目录一、前言1.1、Flink-1.4之前的exactly-once实现1.2、Flink-1.4之后的exactly-once实现二、Exactly-onceTowPhaseCommit2.1、预提交(preCommit)2.2、提交阶段参考一、前言我们知道exactly-once,大多知道有checkpoint,但是在Flink1.4之后,... 查看详情

openwrt自定义cgi实现(代码片段)

此文已由作者吴志勐授权网易云社区发布。欢迎访问网易云社区,了解更多网易技术产品运营经验。安装uhttpd。在编译openwrt前,输入makememuconfig,查找Network->WebServers/Proxies->uhttpd,如果没勾选则勾选。然后编译固件。修改uhtt... 查看详情