第 B-1 章 CGI 动态请求

作者: Brinnatt 分类: ARM64 Linux 补充知识 发布时间: 2022-01-21 20:57

B1、CGI 是什么

CGI 是 common gateway interface 的缩写,大家都译作通用网关接口。

我们知道,web 服务器所处理的内容都是静态的,要想处理动态内容,需要依赖于 web 应用程序,如 php、jsp、python、perl 等。

但是 web server 如何将动态的请求传递给这些应用程序?它所依赖的就是 cgi 协议。没错,是协议,也就是 web server 和 web 应用程序交流时的规范。

换句话说,通过 cgi 协议,再结合已搭建好的 web 应用程序,就可以让 web server 也能 "处理" 动态请求(或者说,当用户访问某个特定资源时,可以触发执行某个 web 应用程序来实现特定功能),你肯定知道处理两字为什么要加上双引号。

简单版的 cgi 工作方式如下:

cgi simple

例如,在谷歌搜索栏中搜索一个关键词 "http",对应的 URL 为:

https://www.google.com/search?q=http&oq=http&aqs=chrome..69i57j69i60l4j0.1136j0j8&sourceid=chrome&ie=UTF-8
  1. 当谷歌的 web server 收到该请求后,先分析该 url,从中知道了要执行 search 程序,并且还知道了一系列要传递给 search 的参数及其对应的 value。
  2. web server 会将这些程序参数和其它一些环境变量根据 cgi 协议通过 TCP 或套接字等方式传递给已启动的 cgi 程序(可能是 cgi 进程,或者是已加载的模块 cgi 模块)。
  3. 当 cgi 进程接收到 web server 的请求后,调用 search 程序并执行,同时还会传递参数给 search 程序。
  4. search 执行结束后,cgi 进程/线程将处理结果返回给 web server,web server 再返回给浏览器。

有多种方式可以执行 cgi 程序,但对 http 的请求方法来说,只有 get 和 post 两种方法允许执行 cgi 脚本(即上面的 search 程序)。实际上 post 方法的内部本质还是 get 方法,只不过在发送 http 请求时,get 和 post 方法对 url 中的参数处理方式不一样而已。

任何一种语言都能编写 CGI,只不过有些语言比较擅长,有些语言则非常繁琐,例如用 bash shell 开发,那么需要用 echo 等打印语句将执行结果放在巨多无比的 html 的标签中输出给客户端。常用于编写 CGI 的语言有 perl、php、python 等,java 也一样能写,但 java 的 servlet 完全能实现 CGI 的功能,且更优化、更利于开发。

B2、专业术语

以 php 为例,我将一次动态请求相关的概念大致都简单解释一遍。

  1. cgi:它是一种协议。通过 cgi 协议,web server 可以将动态请求和相关参数发送给专门处理动态内容的应用程序。
  2. fastcgi:也是一种协议,只不过是 cgi 的优化版。
  3. php-cgi:fastcgi 是一种协议,而 php-cgi 实现了这种协议。不过这种实现是单进程的,一个进程处理一个请求,处理结束后进程就销毁。
  4. php-fmp:是对 php-cgi 的改进版,它直接管理多个 php-cgi 进程/线程。也就是说,php-fpm 是 php-cgi 的进程管理器,因此它也算是 fastcgi 协议的实现。在一定程度上讲,php-fpm 与 php 的关系,和 tomcat 对 java 的关系是类似的。
  5. cgi 进程/线程:在 php 上,就是 php-cgi 进程/线程。专门用于接收 web server 的动态请求,调用并初始化 zend 虚拟机
  6. cgi 脚本:被执行的 php 源代码文件。
  7. zend 虚拟机:对 php 文件做词法分析、语法分析、编译成 opcode,并执行。最后关闭 zend 虚拟机。
  8. cgi 进程/线程和 zend 虚拟机的关系:cgi 进程调用并初始化 zend 虚拟机的各种环境。

以 php-fpm 为例,web server 从转发动态请求到结束的过程大致如下:

php-fpm

  1. Web Browser 用户把请求交给 Apache 服务器。
  2. Apache 通过一个叫作 FastCGI 的高速二进制接口把 code 送给 PHP-FPM 主进程。
  3. PHP-FPM 主进程从线程池中选择一个 worker 线程,然后把 code 交给该线程。
  4. worker 线程执行 code,然后把执行结果返回给 Apache,由 Apache 转交给 Web Browser 用户。
  5. worker 线程一旦完成工作,就返回到线程池中等待下一个请求。

B3、web server 和 CGI 的交互模式

web server 对 cgi 进程/线程来说,它的作用就是发起动态处理请求,传递一些参数和环境变量,最后接收 cgi 的返回结果。通过下面 httpd.conf 中的转发配置应该很容易理解(httpd 和 php-fpm 的交互):

ProxyRequests off
ProxyPassMatch ^/(.*\.php)$ fcgi://127.0.0.1:9000/usr/local/apache/htdocs/$1

以最典型的 apache httpd 和 php 为例,对于 httpd 来说,web server 和 php-cgi 有 3 种交互模式:

  • cgi 模式:httpd 接收到一个动态请求就 fork 一个 cgi 进程,cgi 进程返回结果给 httpd 进程后自我销毁。
  • 动态模块模式:将 php-cgi 的模块(例如 php5_module)编译进 httpd。在 httpd 启动时会加载模块,加载时也将对应的模块激活,php-cgi 也就启动了。
    • 注意:很多人以为动态编译的模块是可以在需要的时候随时加载调用,不需要的时候它们就停止了,实际上不是这样的。和静态编译的模块一样,动态加载的模块在被加载时就被加入到激活链表中,无论是否使用它,它都已经运行在 apache httpd 的内部。
  • php-fpm 模式:使用 php-fpm 管理 php-cgi,此时 httpd 不再控制 php-cgi 进程的启动。可以将 php-fpm 独立运行在非 web 服务器上,实现所谓的动静分离。

实际上,借助模块 mod_fastcgi 还可以实现 fastcgi 模式。同 cgi 一样,管理模式的先天缺陷决定了这并不是一种好方法。

B3.1、CGI 模式

使用 CGI 模式时,当动态请求到达,httpd 临时启动一个 cgi 解释器,并通过 cgi 协议转发要运行的内容。当 cgi 脚本运行结束后,将结果返回给 httpd,然后 cgi 解释器进程自我销毁。当多个动态请求到达时,将先后启动多个 cgi 解释器。因此,这种方法效率极低。

在注释掉 php5_module 的 LoadModule 相关行后,使用 action 指令指定要使用 cgi 运行的类型。但注意,action 指令是 mod_action 提供的,所以必须已经加载该模块。

例如:指定 MIME 类型为 image/gif 的请求使用 images.cgi 运行。显然,images.cgi 脚本你必须先写好。

Action image/gif /cgi-bin/images.cgi

还可以通过添加 handler 来复合文件类型,再使用某个 cgi 脚本去运行这个 handler 中的任意类型。

AddHandler my-file-type .xyz
Action my-file-type "/cgi-bin/program.cgi"

对于 php 来说,则可以使用安装 php 时 bin 目录下提供的 php-cgi 程序作为 cgi 程序。

[root@arm64v8 ~]# ls /usr/local/php/bin/
pear  peardev  pecl  phar  phar.phar  php  php-cgi  php-config  phpize
[root@arm64v8 ~]#

# 复制到apache默认的cgi-bin目录下,方便管理
[root@arm64v8 ~]# cp /usr/local/php/bin/php-cgi /usr/local/apache/cgi-bin/

# 在httpd.conf中添加以下行
Action application/x-httpd-php /usr/local/php/bin/cgi-bin/php-cgi

B3.2、模块方式

在编译 php 时,将 php5_module 模块编译到 apache 中,例如在编译 php 时在 ./configure 配置中加上 "--with-apxs2=/usr/local/apache/bin/apxs"。

这种交互模式下,httpd 在启动时加载并激活 php_module。也就是说,php-cgi 常驻在 httpd 进程内部。当动态请求到达时,httpd 不用再生成 cgi 解释器,而是直接将动态请求转发给它内部 php-cgi。

配置实用这种交互模式非常简单,只需使用 LoadModule 加载 php_module,再添加对应的 MIME 处理器即可。

LoadModule php5_module modules/libphp5.so

# 在mime模块中添加对应的类型
<IfModule mime_module>
AddType application/x-httpd-php .php
AddType applicaiton/x-httpd-php-source .phps
</IfModule>

B3.3、php-fpm 方式

前面说了,php-fpm 是 php-cgi 的进程管理器。这种交互方式实际上是让 php-cgi 以独立于 httpd 的方式存在,目前基本使用 php-fpm 的方式管理 php-cgi 进程。

也就是说,这种模式下,php-cgi 和 httpd 已经分离了,它们的分离意味着请求的动静分离变为可能:httpd 和 php-fpm 分别运行在不同服务器上。动静分离后,压力也分散到各自的服务器上。

要让 php-fpm 以这种方式运行,需要在编译的 ./configure 配置选项中添加 "--enable-fpm" 选项。当然,还得启动 php-fpm 服务。例如:

service php-fpm start

这样 php-cgi 进程就开放着端口(默认9000)等待 httpd 转发动态请求。要让 httpd 能够转发请求到 php-cgi 上,需要在 httpd.conf 中关闭正向代理,并设置 fastcgi 协议代理参数。例如,转发到 192.168.0.88 主机上的 php-fpm。

# 加载代理模块
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

# 添加MIME类型
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

# 在需要转发的虚拟主机中配置转发代理
ProxyRequests off
ProxyPassMatch ^/(.*\.php)$ fcgi://192.168.0.88:9000/usr/local/apache/htdocs/$1
标签云