首页vns威尼斯城官网登入 › CGI就是WEB服务器的API,一些与服务相关的操作都是通过SAPI接口实现

CGI就是WEB服务器的API,一些与服务相关的操作都是通过SAPI接口实现

在商酌 法斯特CGI 之前,不能不说古板的 CGI
的行事规律,同一时间应当大致精通 CGI
1.1 协议

目录

商量WEB编制程序的时候常说任何时候在写CGI,那么CGI是什么样呢?大概过多时候并不会去索求这么些底工概念,再比方说除了CGI还大概有法斯特CGI,
wsgi,
uwsgi等,那那些又有啥样界别吧?为了计算这几个那么些WEB编制程序幼功知识,于是写了此文,如有错误,恳请指正,示例代码见
web-basis

 本文链接:

金钱观 CGI 职业原理分析

客商端访谈某些 U宝马7系L 地址然后,通过 GET/POST/PUT 等方法提交数据,并经过
HTTP 合同向 Web 服务器发出央浼,服务器端的 HTTP Daemon(守护进度)将
HTTP 央浼里描述的消息通过标准输入 stdin 和境遇变量(environment
variableState of Qatar传递给主页钦定的 CGI
程序,并运营此应用程序进行管理(包蕴对数据库的拍卖),管理结果通过正式输出
stdout 再次回到给 HTTP Daemon 守护进度,再由 HTTP Daemon 进程经过 HTTP
协议再次回到给顾客端。

地点的这段话精通可能依旧相比较抽象,上边大家就透过贰次GET央浼为例举办详尽表达。

图片 1

上面用代码来促成图中表明的遵从。Web 服务器运行多少个 socket
监听服务,然后在地头实施 CGI 程序。后边有相比详细的代码解读。

  • 介绍
  • 深入CGI协议
    • CGI的运作规律
    • CGI共商的久治不愈的病痛
  • 深入FastCGI协议
    • 法斯特CGI左券运营规律
    • 为啥是 法斯特CGI 而非 CGI 合同
    • CGI 与 FastCGI 架构
    • 再看 FastCGI 协议
    • Web 服务器和 法斯特CGI 人机联作进程
    • 干什么必要在新闻头发送 RequestID 这些标志?
  • PHP-FPM

1 CGI

1、在PHP生命周期的各样阶段,一些与服务相关的操作都以通过SAPI接口达成。那一个内置完结的大要地点在PHP源码的SAPI目录。这一个目录寄放了PHP对风流倜傥意气风发服务器抽象层的代码,举个例子命令路程序的落实,Apache的mod_php模块完结以致fastcgi的落到实处等等

Web 服务器代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

#define SERV_PORT 9003

char* str_join(char *str1, char *str2);
char* html_response(char *res, char *buf);

int main(void)
{
    int lfd, cfd;
    struct sockaddr_in serv_addr,clin_addr;
    socklen_t clin_len;
    char buf[1024],web_result[1024];
    int len;
    FILE *cin;

    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("create socket failed");
        exit(1);
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);

    if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind error");
        exit(1);
    }

    if(listen(lfd, 128) == -1)
    {
        perror("listen error");
        exit(1);
    }

    signal(SIGCLD,SIG_IGN);

    while(1)
    {
        clin_len = sizeof(clin_addr);
        if ((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)
        {
            perror("接收错误\n");
            continue;
        }

        cin = fdopen(cfd, "r");
        setbuf(cin, (char *)0);
        fgets(buf,1024,cin); //读取第一行
        printf("\n%s", buf);

        //============================ cgi 环境变量设置演示 ============================

        // 例如 "GET /user.cgi?id=1 HTTP/1.1";

        char *delim = " ";
        char *p;
        char *method, *filename, *query_string;
        char *query_string_pre = "QUERY_STRING=";

        method = strtok(buf,delim);         // GET
        p = strtok(NULL,delim);             // /user.cgi?id=1 
        filename = strtok(p,"?");           // /user.cgi

        if (strcmp(filename,"/favicon.ico") == 0)
        {
            continue;
        }

        query_string = strtok(NULL,"?");    // id=1
        putenv(str_join(query_string_pre,query_string));

        //============================ cgi 环境变量设置演示 ============================

        int pid = fork();

        if (pid > 0)
        {
            close(cfd);
        }
        else if (pid == 0)
        {
            close(lfd);
            FILE *stream = popen(str_join(".",filename),"r");
            fread(buf,sizeof(char),sizeof(buf),stream);
            html_response(web_result,buf);
            write(cfd,web_result,sizeof(web_result));
            pclose(stream);
            close(cfd);
            exit(0);
        }
        else
        {
            perror("fork error");
            exit(1);
        }
    }

    close(lfd);

    return 0;
}

char* str_join(char *str1, char *str2)
{
    char *result = malloc(strlen(str1)+strlen(str2)+1);
    if (result == NULL) exit (1);
    strcpy(result, str1);
    strcat(result, str2);

    return result;
}

char* html_response(char *res, char *buf)
{
    char *html_response_template = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\nContent-Length: %d\r\nServer: mengkang\r\n\r\n%s";

    sprintf(res,html_response_template,strlen(buf),buf);

    return res;
}

介绍

在用PHP开拓的进度中,大家平常使用Nginx或许Apache作为大家的Web服务器。但是PHP是何许与这个Web服务器通信的吧?

  • Apache把PHP作为二个模块集成到Apache进度运营,这种mod_php的运行情势与PHP-CGI未有其余关系。

  • Nginx是通过FastCGI来贯彻与PHP的通讯。

要谈法斯特CGI就必须先说说CGI。那怎样是CGI?

CGI(Common Gateway Interface:通用网关接口卡塔尔是Web
服务器运维时外部程序的正经,按CGI 编写的前后相继能够扩张服务器成效。CGI
应用程序能与浏览器实行相互影响,还可经过数据库API
与数据库服务器等外部数据源实行通讯,从数据库服务器中获取数据。--百度百科

CGI协议同 HTTP 左券一样是三个「应用层」公约,它的 功效 是为了然决 Web
服务器与 PHP 应用(或任何 Web 应用)之间的通讯难题。

既然它是三个「左券」,换言之它与语言无关,即只假若兑现类 CGI
左券的使用就能够完毕相互作用的通讯。

1.1 CGI原理

在说明CGI是什么从前,咱们先来讲说CGI不是怎样。

  • CGI不是一门编制程序语言。它的完毕对编制程序语言未有节制,你能够用python,php,perl,shell,C语言等。
  • CGI不是一个编制程序方式。你能够行使此外你熟稔的方法完毕它。
  • CGI也不复杂,无需你是多少个编制程序老司机,生手同样能够欢娱的写自身的CGI。

那么CGI到底是怎样?CGI全称是Common Gateway Interface,即通用网关接口。大家恐怕对API(Application
Programming
Interface卡塔尔会很熟习,CGI正是WEB服务器的API。WEB服务器看名就会知道意思,就是发送网页给浏览器的软件,浏览器称之为web
client,WEB服务器是web
server。浏览器作为客商端,它做的干活就是向WEB服务器央求文件(比如HTML文书档案,图片,样式文件以至任何此外文件等),日常的WEB服务器的职能就是出殡和下葬存储在服务器上的静态文件给发送央求的客户端。

那么难题来了,有个别时候,我们要求发送动态的多寡给客户端,那就供给大家写程序来动态变化数据并重返,那正是CGI用场地在。须要强调的是,WEB服务器和客商端之间是不能够相互的,CGI程序无法须求顾客输入一些参数,管理并再次来到输出,然后需要客户继续输入,那也是CGI能够保证简单的来由之生机勃勃。CGI程序每趟只好最多获得三遍客商输入,然后管理并赶回一次输出。那么CGI怎么样获取客户输入呢?

CGI程序拿到客户输入信任浏览器发送乞求的措施。日常的话,浏览器的HTTP诉求会以GET只怕POST的艺术发送。浏览器选取HTML表格获取客户输入,HTML表格能够钦定浏览器发送央求的格局是GET依旧POST,它们差别在于GET方法会将客商输入参数作为U昂科雷L大器晚成部分,而POST的优势在于:

  • 你能够发送越来越多的多寡(ULX570L长度是有节制的卡塔尔国
  • 发送数据不会在UCRUISERL中被记录(举个例子你要发送密码放到URAV4L中是不太安全的State of Qatar,也不会产出在浏览器之处栏中。

那么CGI程序如何精晓客商端央求是哪个种类方式吗?在WEB服务器加载你的CGI程序前,会设置有个别意况变量让CGI程序知道去哪个地方获得客商输入数据甚至数额大小。举例
REQUEST_METHOD以此情况变量会安装为客商端的乞请方法如GET/POST/HEAD等。而CONTENT_LENGTH景况变量会报告您应该从stdin中读取多少字节数据。CONTENT_TYPE则是报告你顾客端数据类型,是缘于表单依然别的来源。

当CGI程序读取到了客户输入数据后,能够拍卖数量并将响应发送到stdout。CGI程序能够回到HTML数据依旧别的品种数据如GIF图片等。那也是怎么你在再次来到数据前要先在率先行表明您回去数据的等级次序,如Content-type: text/html,然后加八个CPRADOLF后(HTTP合同的规定卡塔尔,再回去真正的输出数据。

在逐黄金年代服务器抽象层之间坚决守护着同大器晚成的预约,这里大家称之为SAPI接口。每种SAPI达成都是二个_sapi_module_struct构造体变量。(SAPI接口卡塔尔(قطر‎。在PHP的源码中,当供给调用服务器相关新闻时,全体因此SAPI接口中对应的艺术调用完毕,而这一个方式在挨门挨户服务器抽象层落成时都会有些的落到实处。由于过多操作的通用性,有相当的大学一年级些接口方法运用的是默许方法。下图为SPAI的大致暗暗表示图

如上代码中的珍视:

  • 66~81行找到CGI程序的相对路线(大家为了简单,直接将其根目录定义为Web程序的当前目录),那样就能够在子进度中执行CGI 程序了;同时设置意况变量,方便CGI程序运转时读取;
  • 94~95行将 CGI 程序的专门的工作输出结果写入 Web 服务器守护进度的缓存中;
  • 97行则将打包后的 html 结果写入客户端 socket
    描述符,重返给连接Web服务器的客商端。

深入CGI协议

咱们曾经知道了 CGI 协议是为着变成 Web
服务器和利用之间实行数量通讯这几个主题素材。那么,那生龙活虎节大家就来拜见毕竟它们之间是什么开展通讯的。

简易来讲 CGI 左券它汇报了 Web
服务器和应用程序之间展开数据传输的格式,并且只要大家的编制程序语言扶助标准输入、规范输出以至意况变量等管理,你就可以动用它来编排三个CGI 程序。

1.2 CGI实现

在切实应用中,WEB服务器常用的有nginx和apache。apache提供了众多模块,能够一向加载CGI程序,和上生龙活虎章提到的点子基本黄金时代致。而nginx是不可能加载CGI程序的,必得其余单独运转二个CGI程序微型机来拍卖CGI乞请,先来看下CGI实现,WEB服务器代码cgi.c。编写翻译并运营:

$ gcc -o cgi cgi.c
$ ./cgi

CGI程序如下,可以为C语言编写,如
cgi_hello.c,也得以是shell,python等其他语言,如
cgi_hello.sh。编译cgi_hello.c,放到cgi.c同三个目录上边。

$ gcc -o cgi_hello cgi_hello.c

利用C达成叁个cgi服务器,其实便是WEB服务器并顺便调用cgi程序功用。依照URL中的路线获取cgi程序名,并奉行该cgi程序得到再次来到结果并重临给客商端。注意,是在WEB服务器程序中装置的遭受变量,通过execl实践cgi程序,cgi程序因为是fork+exec施行的,子进程是会复制父进度景况变量表到温馨的长河空间的,所以能够读取碰到变量QUERY_STRING。在浏览器输入
http://192.168.56.18:6006/cgi_hello?name=ssj(测试机ip为192.168.56.18)
能够观望再次回到 Hello: ssj

图片 2

CGI 程序(user.c)

#include <stdio.h>
#include <stdlib.h>
// 通过获取的 id 查询用户的信息
int main(void){

    //============================ 模拟数据库 ============================
    typedef struct 
    {
        int  id;
        char *username;
        int  age;
    } user;

    user users[] = {
        {},
        {
            1,
            "mengkang.zhou",
            18
        }
    };
    //============================ 模拟数据库 ============================

    char *query_string;
    int id;

    query_string = getenv("QUERY_STRING");

    if (query_string == NULL)
    {
        printf("没有输入数据");
    } else if (sscanf(query_string,"id=%d",&id) != 1)
    {
        printf("没有输入id");
    } else
    {
        printf("用户信息查询<br>学号: %d<br>姓名: %s<br>年龄: %d",id,users[id].username,users[id].age);
    }

    return 0;
}

将地点的 CGI
程序编写翻译成gcc user.c -o user.cgi,放在上面web程序的同级目录。

代码中的第28行,从境况变量中读取后边在Web服务器守护进度中安装的情状变量,是我们演示的机要。

CGI的运作规律

  • 当客商访谈大家的 Web 应用时,会发起三个 HTTP 乞请。最后 Web
    服务器收到到这一个央浼。

  • Web 服务器创造叁个新的 CGI 进度。在这些历程中,将 HTTP
    央求数据已确实无疑格式深入分析出来,并经过规范输入和情况变量传入到 U本田UR-VL
    钦定的 CGI 程序(PHP 应用 $_SERVER)。

  • Web 应用程序管理完毕后将回到数据写入到职业输出中,Web
    服务器进程则从专门的学问输出流中读取到响应,并应用 HTTP
    协议重临给客商响应。

一句话正是 Web 服务器中的 CGI 进度将选拔到的 HTTP
央浼数据读取到景况变量中,通过专门的学业输入转载给 PHP 的 CGI 程序;当 PHP
程序管理实现后,Web 服务器中的 CGI
进程从规范输出中读取重返数据,并更改回 HTTP
响应新闻格式,最后将页面呈献给顾客。然后 Web 服务器关闭掉这几个 CGI 进程。

能够说 CGI 左券非常擅长管理 Web 服务器和 Web
应用的通讯难题。但是,它有三个严重缺欠,对于每一个要求都亟需再行 fork
出四个 CGI 进程,管理到位后立时关闭。

2 FastCGI协议

以cgi情势和apache2服务器为例,它们的启航方法如下:

法斯特CGI 工作原理剖析

相持于 CGI/1.1 标准在 Web 服务器在地点 fork 二个子进程实践 CGI
程序,填充 CGI 预约义的情况变量,归入系统意况变量,把 HTTP body 体的
content 通过规范输入传入子进度,管理达成之后通过专门的工作输出再次来到给 Web
服务器。法斯特CGI 的主导则是明确命令幸免古板的 fork-and-execute
方式,减弱每一回运转的皇皇开支(前面以 PHP
为例表达),以常驻的法子来拍卖乞请。

法斯特CGI 专门的学业流程如下:

  1. 法斯特CGI 进度微电脑自个儿开始化,运营多少个 CGI 解释器进度,并等候来自
    Web Server 的连年。
  2. Web 服务器与 法斯特CGI 进度微处理机进行 Socket 通讯,通过 法斯特CGI
    合计算与发放送 CGI 处境变量和正规输入数据给 CGI 解释器进度。
  3. CGI 解释器进度完毕处理后将标准输出和错误新闻从同三翻四次接重临 Web
    Server。
  4. CGI 解释器进度接着等待并管理来自 Web Server 的下叁个老是。

图片 3

法斯特CGI 与历史观 CGI 格局的区分之一则是 Web 服务器不是向来施行 CGI
程序了,而是经过 socket 与 法斯特CGI 响应器(法斯特CGI
进程微型机)举办相互,Web 服务器须求将 CGI 接口数据封装在依据 FastCGI
左券包中发送给 法斯特CGI 响应器程序。就是由于 法斯特CGI 进度微处理机是依据socket 通信的,所以也是布满式的,Web服务器和CGI响应器服务器分开安排。

再啰嗦一句,法斯特CGI
是大器晚成种左券,它是白手立室在CGI/1.1底蕴之上的,把CGI/1.1中间的要传递的多寡经过法斯特CGI合同定义的逐一、格式进行传递。

CGI磋商的久治不愈的病痛

  • 历次处理客商央浼,都急需再行 fork CGI 子进度、销毁 CGI 子进度。

  • 风度翩翩二种的 I/O
    花销减弱了互联网的吞吐量,形成了能源的荒凉,在大并发时会时有产生严重的性指摘题。

2.1 FastCGI原理

如前方提到的,nginx是不可能直接加载CGI程序的,由此需求二个特别的CGI程序微电脑,nginx通过unix-socket或tcp-socket与CGI程序微电脑通讯。如php常用php-fpm,python常用uWSGI等,可是它们的协商差异,php-fpm用的是fastcgi左券,而uWSGI用的是uwsgi协议。nginx对这两种协议都支持,nginx配置文件/etc/nginx/fastcgi_params/etc/nginx/uwsgi_params哪怕个别指向那二种合同的。

先来走访法斯特CGI契约。望文生义,法斯特CGI公约只是是CGI钻探的变种,区别之处仅仅在于WEB服务器和CGI程序的人机联作情势。CGI共商业中学WEB服务器和CGI程序是通过景况变量来传递新闻,WEB服务器fork+exec来实施CGI程序,CGI程序将出口打字与印刷到正式输出,实践到位后即退出。而法斯特CGI做的作业差十分的少和CGI同样,分化点在于法斯特CGI是由此进程间通讯来传递新闻,举个例子unix
socket或tcp
socket。那么,假诺只是那样小的不等,法斯特CGI协议的含义何在呢?法斯特CGI的意义在于能够让WEB应用程序结构完全成形,CGI研商下,应用程序的生命周期是一遍http诉求,而在法斯特CGI合计里面,应用程序能够一贯留存,管理七个http央求再脱离,小幅升高了WEB应用程序品质。

法斯特CGI左券是贰个彼此公约,尽管底层传输体制是面向连接的,但是它自身不是面向连接的。WEB服务器和CGI程序微型机之间通过法斯特CGI的音信通讯,音信由header和body两有些组成。个中header包括的字段如下:

Version: FastCGI协议版本号,目前一般是1.
Type: 标识消息类型。后面会有提到。
Request ID: 标识消息数据包所属的请求。
Content Length: 该数据包中body长度

法斯特CGI首要的新闻类型如下:

  • BEGIN_REQUEST:WEB服务器 => 应用程序,要求伊始时发送。
  • ABORT_REQUEST:WEB服务器 =>
    应用程序,筹算截止正在周转的号召时发送。家常便饭景色是客商点击了浏览器的告黄金年代段落开关。
  • END_REQUEST:应用程序 =>
    WEB服务器,诉求管理到位后发送。这种音讯的body会包括三个return
    code,标志央浼成功可能失利。
  • PARAMS:WEB服务器 => 应用程序,称之为“stream
    packet”,叁个恳求里面或然发送八个PARAMS类型的音讯。最终叁个body长度为0的音信标记那类音信停止。PARAMS类型音讯里面包含的数据便是CGI里面安装到蒙受变量里面包车型地铁这个变量。
  • STDIN: WEB服务器 => 应用程序,那也是多少个“stream
    packet”,POST相关数据会在STDIN音信中发送。在出殡和安葬完POST数据后,会发送四个空的STDIN音信以标记STDIN类型新闻截至。
  • STDOUT: 应用程序 => WEB服务器,那也是二个“stream
    packet”,是应用程序发送给WEB服务器的带有客商央浼对应的响应数据。响应数据发送完结后,也会发送二个空的STDOUT音信以标记STDOUT类型消息结束。

WEB服务器和法斯特CGI应用程序之间互相流程平日是那样的:

  • WEB服务器收到到三个亟待法斯特CGI应用程序管理的顾客端央浼。因而,WEB服务器通过unix-socket恐怕TCP-socket连选取法斯特CGI程序。
  • 法斯特CGI程序看见了过来的接二连三,它能够采纳谢绝或许摄取该连接。若选用接二连三,则法斯特CGI程序初叶从一而再的多寡流中读取数据包。
  • 即使法斯特CGI程序未有在预料时间内选拔一而再,则倡议失利。不然,WEB服务器会发送三个BEGIN_REQUEST
    的新闻给法斯特CGI程序,该新闻有三个唯风流倜傥的央求ID。接下来的音信都用那几个在header中宣称的同样的ID。接着,WEB服务器会发送一定数量的PARAMS信息给法斯特CGI程序,当变量都发送达成时,WEB服务器再发送一个空的PARAMS新闻关闭PARAMS数据流。并且,WEB服务器会将吸取的源头顾客端的POST数据经过STDIN音信传给法斯特CGI程序,当全数POST数据传输完成,一样也会发送三个空的STDIN类型的新闻以标志停止。
  • 再者,当FastCGI程序接收到BEGIN_REQUEST包后,它能够复苏二个END_REQUEST包推却该哀告,也足以接过并拍卖该诉求。即便采用央浼,则它会等到PARAMSSTDIN包都接纳实现再一起管理,响应结果会经过STDOUT包发送回WEB服务器,最终会发送END_REQUEST包给WEB服务器让其知晓央浼是打响照旧诉讼失败了。

有人只怕会微微意外,为啥新闻头中要求叁个Request ID,假若贰个呼吁多个接连,那那一个字段是多余的。大概你猜到了,叁个连连大概包蕴多少个供给,那样就供给标记音讯数据包是归于哪个诉求,这也是FastCGI为啥要选择面向数据包的合计,实际不是面向数据流的合计。一个三回九转中恐怕夹杂三个央求,在软件工程里面也称之为多路传输。由于各种数据包都有三个须要ID,所以WEB服务器能够在三个连连中还要传输任意个数据包给法斯特CGI应用程序。况兼,FastCGI程序能够同时选用大量的连年,种种连接能够并且包含三个央求。

除此以外,下边描述的通讯流程并非逐大器晚成的。也正是说,WEB服务器能够首发送19个BEGIN_REQUEST包,然后再发送一些PARAMS包,接着发送一些STDIN包,然后又发送一些PARAMS包等等。

cgi_sapi_module.startup(&cgi_sapi_module) // cgi模式 cgi/cgi_main.c文件

apache_sapi_module.startup(&apache_sapi_module); // apache服务器  apache2handler/sapi_apache2.c文件

筹算专门的学问

莫不上边的从头到尾的经过掌握起来依然很虚幻,那是出于第生机勃勃对法斯特CGI左券还尚未叁个大致的认识,第二不曾实际代码的读书。所以须要事情发生前学习下
FastCGI
左券的内容,不自然要求完全看懂,可差不离掌握之后,看完本篇再结合着学习掌握消食。

http://www.fastcgi.com/devkit… (英文原版)
http://andylin02.iteye.com/bl… (中文版)

深入FastCGI协议

从效率上来说,CGI 合同已经完全可以消灭 Web 服务器与 Web
应用之间的数额通讯难题。但是出于每一个央浼都供给再行 fork 出 CGI
子进度招致品质堪忧,所以依附 CGI 公约的底工上做了改过便有了 FastCGI
左券,它是后生可畏种常驻型的 CGI 公约。

本质上来将 法斯特CGI 和 CGI 左券大约全盘等同,它们都足以从 Web
服务器里收到到同样的数额,差异之处在于接纳了差异的通讯格局。

再来回想一下 CGI 公约每趟收到到 HTTP 央求时,都亟待涉世 fork 出 CGI
子进度、实行拍卖并销毁 CGI 子进程那生龙活虎多级工作。

FastCGI 左券利用 进程间通讯
来处理顾客的倡议,上面大家就来拜见它的运维规律。

2.2 法斯特CGI实例深入分析

这里的cgi_sapi_module是sapi_module_struct布局体的静态变量。它的startup方法指向php_cgi_startup函数指针。在此个布局体中除去startup函数指针,还也许有超级多此外艺术或字段,那么些布局在服务器的接口达成中都有定义

法斯特CGI 契约解析

上面结合 PHP 的 法斯特CGI 的代码进行深入分析,不作特殊表明以下代码均来自于 PHP
源码。

FastCGI公约运转规律

  • 法斯特CGI 进程微电脑运转时会创立多个 主 进程和多少个 CGI
    解释器进度(Worker 进度),然后等待 Web 服务器的连接。

  • Web 服务器收到 HTTP 央求后,将 CGI 报文通过 套接字(UNIX 或 TCP
    Socket)实行通信,将意况变量和倡议数据写入规范输入,转载到 CGI
    解释器进度。

  • CGI 解释器进程实现管理后将行业内部输出和错误信息从同一而再连续接再次回到给 Web
    服务器。

  • CGI 解释器进度等待下四个 HTTP 央求的光顾。

测量检验情状配置和抓包

法斯特CGI落成情势比超级多,如PHP的php-fpm,大概比较轻便的fcgiwrap,在那,我用fcgiwrap那个比较轻便的贯彻来解析法斯特CGI合同,验证上焕发青新春说的规律。

先安装fcgiwrap,能够源码安装,假诺是ubuntu/debian系统也得以间接apt-get安装。通过/etc/init.d/fcgiwrap start启航fcgiwrap暗中认可会以unix-socket格局运转,假设要改成tcp-socket运营,能够fcgiwrap -f -s tcp:ip:port与上述同类运维。

# sudo apt-get install fcgiwrap

在测量检验的nginx配置的server段里面增加风流倜傥行

include /etc/nginx/fcgi.conf;

内部fcgi.conf文件内容见
fcgi.conf。

测量检验用的cgi程序都放在 /usr/share/nginx/cgi-bin目录下边。测验cgi程序为
fcgi_hello.sh:

在浏览器输入http://192.168.56.18/cgi-bin/fcgi_hello.sh?foo=bar能够见见再次来到结果。

为了幸免任何困扰,小编没用tcp-socket运营fcgiwrap,这样为了抓unix-socket的包,供给选拔socat本条工具。为了抓包,须求轻易改下nginx的陈设,将
/etc/nginx/fcgi.conf中的fastcgi_pass那大器晚成行改良下,如下所示。

# fastcgi_pass  unix:/var/run/fcgiwrap.socket;
fastcgi_pass  unix:/var/run/fcgiwrap.socket.socat;

reload nginx并在命令行张开socat命令

socat -t100 -x -v UNIX-LISTEN:/var/run/fcgiwrap.socket.socat,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/fcgiwrap.socket

此刻,在浏览器输入http://192.168.56.18/cgi-bin/fcgi_hello.sh?foo=bar能够看见socat命令会有出口如下:

> 2018/01/30 06:16:42.309659  length=960 from=0 to=959
01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00  ................
01 04 00 01 03 92 06 00 0c 07 51 55 45 52 59 5f  ..........QUERY_
53 54 52 49 4e 47 66 6f 6f 3d 62 61 72 0e 03 52  STRINGfoo=bar..R
45 51 55 45 53 54 5f 4d 45 54 48 4f 44 47 45 54  EQUEST_METHODGET
......
66 72 3b 71 3d 30 2e 36 00 00 00 00 00 00 01 04  fr;q=0.6........
00 01 00 00 00 00 01 05 00 01 00 00 00 00        ..............
--
< 2018/01/30 06:16:42.312909  length=136 from=0 to=135
01 06 00 01 00 61 07 00 53 74 61 74 75 73 3a 20  .....a..Status: 
32 30 30 0d 0a                                   200..
43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65  Content-Type: te
78 74 2f 70 6c 61 69 6e 0d 0a                    xt/plain..
0d 0a                                            ..
52 45 51 55 45 53 54 20 4d 45 54 48 4f 44 3a 20  REQUEST METHOD: 
20 47 45 54 0a                                    GET.
50 41 54 48 5f 49 4e 46 4f 3a 20 0a              PATH_INFO: .
51 55 45 52 59 5f 53 54 52 49 4e 47 3a 20 20 66  QUERY_STRING:  f
6f 6f 3d 62 61 72 0a                             oo=bar.
00 00 00 00 00 00 00 01 06 00 01 00 00 00 00 01  ................
03 00 01 00 08 00 00 00 00 00 00 00 00 00 00     ...............

在ubuntu/debian上通过
sudo apt-get install libfcgi-dev后,可以在/usr/local/fastcgi.h中找到各种项指标消息的概念,接下去我们相比上意气风发节说的法斯特CGI类型每一种拆解解析下。

 

法斯特CGI 消息类型

法斯特CGI 将传输的音讯做了超多门类的撤销合并,其结构体定义如下:

typedef enum _fcgi_request_type {
    FCGI_BEGIN_REQUEST      =  1, /* [in]                              */
    FCGI_ABORT_REQUEST      =  2, /* [in]  (not supported)             */
    FCGI_END_REQUEST        =  3, /* [out]                             */
    FCGI_PARAMS             =  4, /* [in]  environment variables       */
    FCGI_STDIN              =  5, /* [in]  post data                   */
    FCGI_STDOUT             =  6, /* [out] response                    */
    FCGI_STDERR             =  7, /* [out] errors                      */
    FCGI_DATA               =  8, /* [in]  filter data (not supported) */
    FCGI_GET_VALUES         =  9, /* [in]                              */
    FCGI_GET_VALUES_RESULT  = 10  /* [out]                             */
} fcgi_request_type;

干什么是 法斯特CGI 而非 CGI 左券

假诺单纯因为做事方式的两样,就如并从未怎么大不断的。并没到非要选取法斯特CGI 合同不可的境地。

然则,对于那么些近乎细小的歧异,但意义非同日常,最后的结果是落到实处出来的 Web
应用布局上的间距。

分析

WEB服务器和法斯特CGI之间常常的彼此流程是如此的,下边会因此抓包详细解析。

{FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS,          1, "\013\007QUERY_STRINGfoo=bar"}
{FCGI_PARAMS,          1, ""}
{FCGI_STDIN,           1, "id=1&name=ssj"}
{FCGI_STDIN,           1, ""}

    {FCGI_STDOUT,      1, "Content-type: text/html\r\n\r\n<html>\n<head> ... "}
    {FCGI_STDOUT,      1, ""}
    {FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}

整套SAPI雷同于七个面向对象中的模板方法格局的施用。SAPI.c和SAPI.h文件所蕴含的风度翩翩部分函数正是模板方法情势中的抽象模板,各样服务器对于sapi_module的概念及相关贯彻则是三个个绘声绘色的沙盘模拟经营

音讯的出殡顺序

下图是二个简洁明了的音信传递流程

图片 4

第一发送的是FCGI_BEGIN_REQUEST,然后是FCGI_PARAMSFCGI_STDIN,由于各类新闻头(下边将详细表明)里面能够世襲的最大尺寸是65535,所以那三种类型的消息不自然只发送叁回,有异常的大希望总是发送数十次。

法斯特CGI
响应体管理实现之后,将发送FCGI_STDOUTFCGI_STDERR,同理也说不许多次再而三发送。最终以FCGI_END_REQUEST意味着诉求的利落。

亟待小心的一些,FCGI_BEGIN_REQUESTFCGI_END_REQUEST独家标志着乞求的起来和终结,与总体公约皮之不存毛将焉附,所以她们的音信体的原委也是协商的风华正茂某个,因而也可能有相应的构造体与之对应(前面会详细表明)。而境遇变量、标准输入、规范输出、错误输出,那几个都以工作有关,与和睦非亲非故,所以他们的音讯体的剧情则无构造体对应。

由于全体新闻是二进制接二连三传递的,所以必需定义叁个集合的布局的新闻头,那样以便读取每一个音信的新闻体,方便新闻的切割。那在互连网通信中是非平常见的后生可畏种花招。

CGI 与 FastCGI 架构

在 CGI 左券中,Web 应用的生命周期完全依赖于 HTTP 诉求的宣示周期。

对各类接受到的 HTTP 诉求,都亟需重启八个 CGI
进度来实行拍卖,管理完毕后必须关闭 CGI 进度,才具达到规定的标准布告 Web 服务器本次HTTP 恳求管理到位的目标。

而是在 法斯特CGI 中全然不相符。

法斯特CGI 进度是常驻型的,风流倜傥旦运转就足以管理全体的 HTTP
需要,而没有必要直接退出。

WEB服务器发送给法斯特CGI程序的数量包:

  • 率先个音讯是
    BEGIN_REQUEST,能够看看第4个字节为01,也正是version为1,第4个字节为01,即消息类型是
    BEGIN_REQUEST,接着3-4字节0001是requestId为1。再跟着5-6字节0008是音信体长度为8。然后7-8字节0000是保存字段和填充字段。接着8个字节就是音讯体了,9-10字节0001为role值,表示FCGI_RESPONDER,也正是那是叁个索要响应的音讯。11字节00为flag,表示应用在本次诉求后关门连接。然后12-16的5个字节0000000000为保存字段。

  • 其次个新闻的第三个字节是01,也是version为1,第3个字节为04,表示音讯类型为PARAMS。接着3-4字节为0001是requestId也是1。5-6字节0x0392新闻体长度为914字节。前面7-8是0600位填充字段6字节。后边的为音讯体内容,也正是QUERY_STRING, REQUEST_METHOD这几个在CGI中安装到境遇变量中的变量和值。接下来是PARAMS音讯体。PARAMS音讯用的是Name-Value对这种方式组织的数据构造,先是变量名称长度,然后是变量值长度,接着才是名字和值的切实数据。注意,名和值的长度假如超越1字节,则用4个字节来囤积,具体是1字节如故4字节依附长度值的第两个字节的最高位来区分,借使为1则是4字节,假诺为0则是1字节。如此能够剖判PARAMS音讯体了,头八个字节0c07意味名字长度为12,值长度为7,然后就是11个字节的变量名QUERY_STRING,7字节的值foo=bar,就那样类推,接着的2个字节0e03便是名字长度为14,值长度为3,变量名是REQUEST_METHOD,值为GET...后续数据就是多余的任何变量。最前边的6个字节000000000000是填充字节。

  • 其多少个消息也是PARAMS,那是三个空的PARAMS音讯。第1字节为01,第2字节为04意味着PARAMS,3-4字节0001是requestId为1,5-6字节0000代表音信体长度为0,7-8字节0000表示填充和保留字节为0。

  • 第八个消息为STDIN,第二个字节01是version,第4个字节05表示项目为STDIN,接下去是3-4字节0001是requestId为1,5-6字节表示信息体长度为0,因为我们未有POST数据。前边7-8字节为0。(倘使有POST数据,则STDIN这里新闻体长度不为0,而它的音讯体正是POST的数码,注意STDIN不是Name-Value对,它是直接将POST的数额字段连在一同的,如那样id=1&name=ssj)。到此,WEB服务器发送给法斯特CGI程序的数目包结束。

 

FastCGI 消息头

如上,法斯特CGI
音信分10种音讯类型,有的是输入过多输出。而颇负的新闻都是二个信息头开头。其构造体定义如下:

typedef struct _fcgi_header {
    unsigned char version;
    unsigned char type;
    unsigned char requestIdB1;
    unsigned char requestIdB0;
    unsigned char contentLengthB1;
    unsigned char contentLengthB0;
    unsigned char paddingLength;
    unsigned char reserved;
} fcgi_header;

字段解释下:

  • version标志法斯特CGI公约版本。
  • type 标记法斯特CGI记录类型,也正是记录推行的雷同意义。
  • requestId标志记录所属的法斯特CGI要求。
  • contentLength笔录的contentData组件的字节数。

至于地点的xxB1xxB0的磋商表达:当八个相邻的构造组件除了后缀“B1”和“B0”之外命名相符期,它代表那三个零器件可说是价值评估为B1<<8
+
B0的单个数字。该单个数字的名字是那个零件减去后缀的名字。那一个约定归纳了贰个由抢先多少个字节表示的数字的管理方式。

比方公约头中requestIdcontentLength表示的最大值便是65535

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int main()
{
   unsigned char requestIdB1 = UCHAR_MAX;
   unsigned char requestIdB0 = UCHAR_MAX;
   printf("%d\n", (requestIdB1 << 8) + requestIdB0); // 65535
}

你大概会想到借使一个新闻体长度超过65535如何做,则分割为多少个生龙活虎律类其余消息发送就可以。

再看 FastCGI 协议

通过前边的讲课,我们相比较已经得以很确切的说出去 法斯特CGI
是豆蔻梢头种通讯公约

那样的结论。未来,大家就将关爱的纽带挪到协商本身,来拜候这么些合同的概念。

同 HTTP 左券相符,法斯特CGI 协议也有信息头和音讯体组成。

法斯特CGI程序发送给WEB服务器的多寡包:

  • 率先个消息是 STDOUT
    。第三个字节依旧01为version,第3个字节06表示项目为STDOUT,接着3-4字节0001仍然requestId,5-6字节0061为消息体长度97,7-8字节0700表示填充字段为7字节。接下来新闻体正是回来的剧情Status: 200\r\n...

  • 第三个音信还是
    STDOUT,可是是空的STDOUT音讯,用来标志STDOUT音信截止。

  • 其三个音讯是
    END_REQUEST。第4个字节01照旧version,第3个字节03标记类型
    END_REQUEST,3-4字节为requestId为1,5-6字节为新闻体大小为8,7-8字节0000为填充字节长度。前面消息体内容为8个0字节。也正是说appStatus为0,protocolStatus也为0.当中protocalStatus是协议级的状态码,为0表示
    REQUEST_COMPLETE,即央求平常完毕。

// 消息类型定义
#define FCGI_BEGIN_REQUEST       1
#define FCGI_ABORT_REQUEST       2
#define FCGI_END_REQUEST         3
#define FCGI_PARAMS              4
#define FCGI_STDIN               5
#define FCGI_STDOUT              6
#define FCGI_STDERR              7
#define FCGI_DATA                8
#define FCGI_GET_VALUES          9
#define FCGI_GET_VALUES_RESULT  10
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

2、Apache模块

FCGI_BEGIN_REQUEST 的定义

typedef struct _fcgi_begin_request {
    unsigned char roleB1;
    unsigned char roleB0;
    unsigned char flags;
    unsigned char reserved[5];
} fcgi_begin_request;

字段解释

role表示Web服务器期待利用扮演的角色。分为四个剧中人物(而大家这里研讨的情况平时都以响应器角色)

typedef enum _fcgi_role {
    FCGI_RESPONDER    = 1,
    FCGI_AUTHORIZER    = 2,
    FCGI_FILTER        = 3
} fcgi_role;

FCGI_BEGIN_REQUEST中的flags零部件包罗一个决定线路关闭的位:flags & FCGI_KEEP_CONN:假使为0,则应用在对这次诉求响应后关门线路。若是非0,应用在对这一次诉求响应后不会停业线路;Web服务器为线路保持响应性。

音信头消息

主要的消息头消息如下:

  • Version: 用于表示 法斯特CGI 左券版本号。

  • Type: 用于标记 法斯特CGI 消息的门类 - 用于钦命管理这一个音信的不二法门。

  • RequestID: 标记出当下所属的 法斯特CGI 央求。

  • Content Length: 数据包包体所占字节数。

2.3 fcgiwrap分析

fcgiwrap用到了libfcgi库,libfcgi库提供了有的函数封装,以福利达成fastcgi管理器。fcgiwrap运转参数如下:

fcgiwrap -f -s unix:/var/run/fcgiwrap.socket -c 2

其中-s指定socket类型,若要用tcp-socket则用
-s tcp:ip:port。-c参数钦命子进度数目,这里为2个。

fcgiwrap的中坚代码如下,即先创制叁个listen
socket,然后将该socket通过dup2复制到文件陈说符0,因为libfcgiCurry面固定从fd
0来监听网络数据。prefork是创办参数钦点数量的子进程数目,然后父进度经过pause()调用甘休运作,接着每种子进度继续往下实践fcgiwrap_main()函数。

int main(int argc, char **argv) {
    fd = setup_socket(socket_url);
    prefork(nchildren);
    fcgiwrap_main();
}

fcgiwrap_main()着力代码如下,即不停的经过
FCGI_Accept()函数监听连接并拍卖央浼。当中FCGI_Accept()函数是libfcgi库提供的,重要效用就是监听listen
socket上的号令,然后依据fastcgi合同读取数据并分析为方便管理的协会,设置景况变量environ等,那样handle_fcgi_request()就能够跟cgi程序一样通过读取意况变量还获得cgi文件名等剧情。

static void fcgiwrap_main(void)
{
   ...... //略去了一些信号处理代码
   inherited_environ = environ;

    while (FCGI_Accept() >= 0 && !sigint_received) {
        handle_fcgi_request();
    }
}

handle_fcgi_request()不畏管理诉求的函数了,先是fork出子进程去奉行CGI程序,将举行理并了结果写入到管道中,而父进度则读取管道中的数据并回到给WEB服务器。这里有几点注意下:

  • 子进度中代码dup2(pipe_in[0], 0)实施后,子进度从pipe_in[0]用作标准输入,而父进度设置了
    fc.fd_stdin = pipe_in[1],在函数fcgi_pass()中,会先调用子函数fcgi_pass_request()读取FCGI_stdin中的数据(相当于前风流倜傥节提到的STDIN类型的新闻,也正是POST中的表单数据)并写入fc.fd_stdin,约等于写入到了pipe_in管道中,则子进度那时就足以从行业内部输入中(因为前边的dup2卡塔尔(قطر‎读取到数量。同理,子进度中代码dup2(pipe_out[1], 1)即表达子进度的专门的学问输出会输出到管道pipe_out中,父进度在fcgi_pass()中同理能够经过管道读取到子进度的运营输出结果(这里fcgi_pass()使用了select()方法来轮询fd_stdout和fd_stderr文件叙述符)。父进度读取到输出结果后,再次来到STDOUTFCGI_END_REQUEST新闻给nginx服务器,完费用次央求。

static void handle_fcgi_request(void)
{
    int pipe_in[2];
    int pipe_out[2];
    int pipe_err[2];
    char *filename;
    char *last_slash;
    char *p;
    pid_t pid;

    struct fcgi_context fc;

    switch((pid = fork())) {
        case -1:
            goto err_fork;

        case 0: /* child */
            close(pipe_in[1]);
            close(pipe_out[0]);
            close(pipe_err[0]);

            dup2(pipe_in[0], 0);
            dup2(pipe_out[1], 1);
            dup2(pipe_err[1], 2);

            close(pipe_in[0]);
            close(pipe_out[1]);
            close(pipe_err[1]);

            close(FCGI_fileno(FCGI_stdout));

            signal(SIGCHLD, SIG_DFL);
            signal(SIGPIPE, SIG_DFL);

            filename = get_cgi_filename();
            inherit_environment();
            ...... //省略了检查文件是否存在和文件权限的代码

            execl(filename, filename, (void *)NULL);
            cgi_error("502 Bad Gateway", "Cannot execute script", filename);

        default: /* parent */
            close(pipe_in[0]);
            close(pipe_out[1]);
            close(pipe_err[1]);

            fc.fd_stdin = pipe_in[1];
            fc.fd_stdout = pipe_out[0];
            fc.fd_stderr = pipe_err[0];
            fc.reply_state = REPLY_STATE_INIT;
            fc.cgi_pid = pid;

            fcgi_pass(&fc);
    }
    return;

   ...... // 省略部分错误处理代码
    FCGI_puts("Status: 502 Bad Gateway\nContent-type: text/plain\n");
    FCGI_puts("System error");
}

实质上运用中,像php-fpm(fpm是fastcgi process
manager的意味)这种法斯特cgi进度管理器,它会有master进度和worker进度,然后统风流浪漫由master进度来散发须要保管worker,可是用的都是fastcgi契约,与本文深入分析的相像。

(1State of Qatar当PHP要求在Apache服务器下运作时,平时的话,它能够mod_php5模块的款式集成,那个时候mod_php5模块的功用是选择Aapche传递过来的PHP文件央浼,并管理这个伏乞,然后将拍卖后的结果重回给Apache。要是我们在Apache运转前在其铺排文件中安插了PHP模块,PHP模块通过挂号apache2的ap_hook_post_config挂钩,在Apache运行的时候运转此模块以接到PHP文件的乞请。

FCGI_END_REQUEST 的定义

typedef struct _fcgi_end_request {
    unsigned char appStatusB3;
    unsigned char appStatusB2;
    unsigned char appStatusB1;
    unsigned char appStatusB0;
    unsigned char protocolStatus;
    unsigned char reserved[3];
} fcgi_end_request;

字段解释

appStatus组件是接收级其余状态码。
protocolStatus组件是说道级其余状态码;protocolStatus的值或者是:

FCGI_REQUEST_COMPLETE:诉求的平常化甘休。
FCGI_CANT_MPX_CONN:拒却新乞请。那发生在Web服务器通过一条路径向利用发送并发的乞求时,前面一个被设计为每条路线每一趟处理三个号召。
FCGI_OVE凯雷德LOADED:谢绝新央求。那发生在运用用完有些财富时,举例数据库连接。
FCGI_UNKNOWN_ROLE:回绝新央浼。这爆发在Web服务器钦点了多少个施用无法识其他剧中人物时。

protocolStatus在 PHP 中的定义如下

typedef enum _fcgi_protocol_status {
    FCGI_REQUEST_COMPLETE    = 0,
    FCGI_CANT_MPX_CONN        = 1,
    FCGI_OVERLOADED            = 2,
    FCGI_UNKNOWN_ROLE        = 3
} dcgi_protocol_status;

急需小心dcgi_protocol_statusfcgi_role逐生机勃勃要素的值都是 FastCGI
合同里定义好的,而非 PHP 自定义的。

音讯类型定义

  • BEGIN_REQUEST: 从 Web 服务器发送到 Web
    应用,表示最初拍卖新的央求。

  • ABORT_REQUEST: 从 Web 服务器发送到 Web
    应用,表示暂停二个甩卖中的央浼。举个例子,顾客在浏览器发起号召后按下浏览器上的「甘休开关」时,会触发那么些音信。

  • END_REQUEST: 从 Web 应用发送给 Web
    服务器,表示该伏乞管理完了。再次回到数据包里包罗「重返的代码」,它调整乞求是不是中标拍卖。

  • PARAMS: 「流数据包」,从 Web 服务器发送到 Web
    应用。那时候可以发送四个数据包。发送截至标志为从 Web
    服务器发出一个尺寸为 0 的空包。且 PARAMS 中的数据类型和 CGI
    协议黄金时代致。即大家接纳 $_SE传祺VEHaval 获取到的系统情状等。

  • STDIN: 「流数据包」,用于 Web 应用从正规输入中读抽出客户提交的
    POST 数据。

  • STDOUT: 「流数据报」,从 Web
    应用写入到标准输出中,包涵再次回到给客商的数目。

3 WSGI

而外这种运维时的加载格局,Apache的模块能够在运维的时候动态装载,那意味着对服务器能够展开功用扩张而无需再次对源代码举办编写翻译,乃至无需重启服务器。大家所须求做的单独是给服务器发送实信号HUP也许AP_SIG_GEACEFUL通告服务重视新载入模块。然而在动态装载早前大家需求将模块编写翻译成为动态链接库。此时的动态加载就是加载动态链接库。Apache中对动态链接库的拍卖是通过模块mod_so来达成的,因而mod_so模块无法被动态加载,它只好本静态编写翻译进Apache的主导。那象征它和Apache一齐运营的。

新闻电视发表样例

为了轻松的表示,音讯头只体现新闻的档案的次序和音讯的
id,其余字段都不给与呈现。上边包车型大巴例子来自于官方网址

{FCGI_BEGIN_REQUEST,   1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS,          1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{FCGI_STDIN,           1, "quantity=100&item=3047936"}
{FCGI_STDOUT,          1, "Content-type: text/html\r\n\r\n<html>\n<head> ... "}
{FCGI_END_REQUEST,     1, {0, FCGI_REQUEST_COMPLETE}}

协作地点各种布局体,则足以大意想到 法斯特CGI 响应器的深入分析和响应流程:

第少年老成读取新闻头,获得其品种为FCGI_BEGIN_REQUEST,然后解析其音信体,得到消息其需求的剧中人物正是FCGI_RESPONDERflag为0,表示诉求截至后关门线路。然后解析第二段新闻,获知其音讯类型为FCGI_PARAMS,然后径直将音讯体里的剧情以回车符切割后存入遭遇变量。与之临近,管理达成之后,则赶回了FCGI_STDOUT音信体和FCGI_END_REQUEST音信体供
Web 服务器深入分析。

Web 服务器和 法斯特CGI 交互过程

  • Web 服务器收到顾客诉求,但聊到底管理央浼由 Web 应用完毕。那个时候,Web
    服务器尝试通过套接字(UNIX 或 TCP 套接字,具体使用哪个由 Web
    服务器配置决定)连接到 法斯特CGI 进程。

  • 法斯特CGI
    进度查看接收到的连天。选用「选择」或「拒却」连接。纵然是「选择」连接,则从正式输入流中读取数据包。

  • 万生龙活虎 FastCGI
    进度在指准时间内并未有水到渠成接纳到一连,则该央浼退步。否则,Web
    服务器发送叁个包蕴唯生机勃勃的RequestID 的 BEGIN_REQUEST 类型音信给到
    法斯特CGI 进度。后续全部数据包发送都包罗那几个 RequestID。 然后,Web
    服务器发送任性数量的 PARAMS 类型新闻到 法斯特CGI
    进度。黄金年代旦发送实现,Web 服务器通过发送贰个空PARAMS
    音讯包,然后停业那些流。 别的,假如客户发送了 POST 数据 Web
    服务器会将其写入到 规范输入 发送给 法斯特CGI 进度。当全体 POST
    数据发送完毕,会发送多少个空的 规范输入 来关闭那一个流。

  • 再者,法斯特CGI 进度选拔到 BEGINREQUEST 类型数据包。它能够因而响应
    ENDREQUEST
    来谢绝这一个央浼。只怕收受并管理那几个诉求。假若接受诉求,法斯特CGI
    进度会等待选择全部的 PARAMS 和 规范输入数据包。
    然后,在管理诉求并将回到结果写入 规范输出
    流。管理达成后,发送多少个空的数据包到正式输出来关闭那么些流,而且会发送二个END_REQUEST 类型新闻文告 Web 服务器,告知它是还是不是产生错误极度。

3.1 WSGI规范

WSGI是Web服务器网关接口(Python Web Server Gateway
Interface,缩写为WSGI)是为Python语言定义的WEB服务器和WEB应用程序或框架之间的生机勃勃种轻松而通用的接口,它与CGI相仿,它不是黄金年代种框架,亦不是模块,而是风流罗曼蒂克种服务器(Web
Server卡塔尔(قطر‎和应用程序(Web
Application卡塔尔之间标准。WSGI切磋实际上是概念了少年老成种WEB服务器与WEB框架解耦的正经,开荒者能够采用随机的WEB
服务器和WEB应用组合实现本人的web应用。比如常用的uWSGI和Gunicorn都以兑现了WSGI
Server公约的服务器(uWSGI还兼有进度微处理器,监察和控制,日志,插件,网关等作用卡塔尔(قطر‎,Flask是贯彻了WSGI
Application合同的施用框架(当然Flask也自带有四个粗略的WEB服务器,固然大家平日是用nginx来拍卖静态文件卡塔尔(قطر‎,能够依照项目境况搭配使用。

WSGI分为两端:服务器/网关端 和
应用/框架端,服务器端调用应用端提供的可调用的对象。可调用对象能够是函数、方法、类还是实现了__call__格局的实例,那取决服务器和利用选择哪类达成手艺。除了尊重的服务器和采取,也足以运用中间件才具来落到实处该专门的学问。

 

PHP 中的 FastCGI 的实现

下面前遭受代码的解读笔记只是自己个人知识的一个梳理提炼,如有核对,请大家建议。对不熟谙该代码的同室来讲大概是三个辅导,起头认识,如若以为很模糊不清楚,那么照旧须求和谐逐行去读书。

php-src/sapi/cgi/cgi_main.c为例进行深入分析表明,假诺开采遭逢为 unix
意况。main 函数中有些变量的概念,以致 sapi
的开首化,我们就不钻探在这里边钻探了,只表达有关 法斯特CGI 相关的内容。

干什么要求在音信头发送 RequestID 这一个标志?

要是是各类连接仅管理七个呼吁,发送 RequestID 则略显多余。

只是大家的 Web 服务器和 法斯特CGI
进度之间的接二连三或许管理三个央浼,即三个连连能够处理七个要求。所以才须要使用数据中国包装技合同实际不是一向利用单个数据流的原故:以促成「多路复用」。

于是,由于每一个数据包都包涵唯风度翩翩的 RequestID,所以 Web
服务器技术在叁个总是上发送任性数量的伏乞,而且 法斯特CGI
进度也能够从四个三番四遍上选拔到大肆数量的央求数据包。

其他大家还索要分明一点正是 Web 服务器 与 法斯特CGI 进度间通讯是
冬天的。尽管大家在竞相进程中看起来三个要求是不改变的,不过大家的 Web
服务器也许有比相当的大可能率在同有时候发出几12个 BEGIN_REQUEST
类型的数据包,依此类推。

应用/框架

选拔对象正是叁个选取八个参数的可调用对象,它能够是函数,方法,类等。应用对象必需能够被每每调用。就算大家誉为应用对象,但那并不意味应用开拓者要用WSGI作为WEB编制程序API。应用开采者能够三番三次运用已经存在的、高档框架服务去支付他们的利用。WSGI
是多少个为框架开垦者和服务器开采者计划的工具,应用开垦者不需求直接采用WSGI。

app.py是带有多少个应用对象的身体力行,此中一个是用函数完成,另贰个是用类已毕。

Apache是什么样加载模块的呢?以mod_php5为例,首先在httpd.conf中增加生龙活虎行:

1.开启多个 socket 监听服务

fcgi_fd = fcgi_listen(bindpath, 128);

从那边伊始监听,而fcgi_listen函数里面则完结 socket
服务前三步socket,bind,listen

PHP-FPM

PHP-FPM即PHP-FastCGI Process Manager.

PHP-FPM是法斯特CGI的落实,并提供了经过处理的效应。

进程包罗 master 进度和 worker 过程三种进度。

master 进度独有二个,担任监听端口,选择来自 Web Server 的央求,而 worker
进程则相近有多个(具体数额依照实际供给安顿卡塔尔,每一种进度之中都置于了八个 PHP
解释器,是 PHP 代码真正实行之处。

PHP-FPM 是 法斯特CGI 进程微机(PHP 法斯特CGI Process
Manager)( PHP 内核的
法斯特CGI 的大部分叠合成效(也许说意气风发种代替的 PHP 法斯特CGI
落成),对于高负载网址是特别有效的。

服务器/网关

服务器/网关每趟从 HTTP
客商端收到叁个伸手,就调用叁遍使用对象。为了方便表明,这里有个简单的CGI网关的例子
server.py,接纳央浼并调用应用对象app管理诉求,实际担当管理须要的地点在handles.py中。

LoadModule php5_module modules/mod_php5.so

2.最早化央浼对象

fcgi_request目的分配内部存款和储蓄器,绑定监听的 socket 套接字。

fcgi_init_request(&request, fcgi_fd);

全体伏乞从输入到再次回到,都围绕着fcgi_request构造体对象在扩充。

typedef struct _fcgi_request {
    int            listen_socket;
    int            fd;
    int            id;
    int            keep;
    int            closed;

    int            in_len;
    int            in_pad;

    fcgi_header   *out_hdr;
    unsigned char *out_pos;
    unsigned char  out_buf[1024*8];
    unsigned char  reserved[sizeof(fcgi_end_request_rec)];

    HashTable     *env;
} fcgi_request;

PHP-FPM怎么着行事的?

PHP-FPM 进程微处理机有二种进度组成,多个 Master 进度和七个 Worker
进程。Master 进度肩负监听端口,接纳来自 Web 服务器的倡议,然后指使具体的
Worker 进程管理乞请;worker 进度则雷同有多少个(依据配置决定进度数卡塔尔国,每种进程之中都放置了多个 PHP 解释器,用来执行 PHP
代码。

中间件:能够扮演三种剧中人物

中间件是这样意气风发种对象,它不仅可以够当做劳动器端跟应用端交互作用,也能够充作利用端跟服务器端交互作用。中间件组件平时具备上面多少个效果与利益:

  • 在重写了景况变量后,遵照指标UPAJEROL将呼吁路由到差异的使用对象。
  • 允许三个应用或框架在同叁个历程中逐个施行。
  • 透过转载呼吁和响应,帮助负载均衡和长间距处理。
  • 支撑对剧情张开一而再管理。

中间件的存在对于接口的“服务器/网关”和“应用/框架”这两端是晶莹的,并无需非常的帮忙。大许多景色下,中间件必需相符WSGI的服务器和应用程序端的约束和须要。

在安顿文件中增添了所示的通令后,Apache在加载模块时会依据模块名查找模块并加载。Apache的每一个模块都以以module布局体的花样存在,module结构的name属性在最后是透过宏STANDA奥德赛D20_MODULE_STUFF以__FILE__展示。通过事情发生早前的授命中钦定的不二秘籍找到有关的动态链接库文件后,Apache通过中间的函数获取动态链接库中的内容,并将模块的剧情加载到内部存款和储蓄器中钦赐变量中。

3.开立八个 CGI 拆解剖判器子进度

此地子进度的个数暗中认可是0,从计划文件中读取设置随处境变量,然后在程序中读取,然后创立钦命数量的子进度来等待处理Web 服务器的伏乞。

if (getenv("PHP_FCGI_CHILDREN")) {
    char * children_str = getenv("PHP_FCGI_CHILDREN");
    children = atoi(children_str);
    ...
}

do {
    pid = fork();
    switch (pid) {
    case 0:
        parent = 0; // 将子进程中的父进程标识改为0,防止循环 fork

        /* don't catch our signals */
        sigaction(SIGTERM, &old_term, 0);
        sigaction(SIGQUIT, &old_quit, 0);
        sigaction(SIGINT,  &old_int,  0);
        break;
    case -1:
        perror("php (pre-forking)");
        exit(1);
        break;
    default:
        /* Fine */
        running++;
        break;
    }
} while (parent && (running < children));

Nginx 服务器如何与 法斯特CGI 协同职业

Nginx 服务器不可能直接与 法斯特CGI 服务器实行通讯,须要启用
ngx_http_fastcgi_module 模块举行代理配置,才具将号召发送给 法斯特CGI
服务。

转载:

PHP和Apache是何等通讯的?

Nginx+PHP-FPM运维规律安详严整

操纵CGI和法斯特CGI合同的运维规律

3.2 WSGI细节

在真的激活模块以前,Apache会检查有着加载的模块是还是不是为真正的Apache模块。最终Apache会调用相关的函数(ap_add_loaded_module卡塔尔将模块激活,此处的激活正是将模块归入相应的链表中(ap_top_modules链表)

4.在子进程中接到央求

到此地全体都照旧 socket
的服务的覆辙。接纳央浼,然后调用了fcgi_read_request

fcgi_accept_request(&request)

int fcgi_accept_request(fcgi_request *req)
{
    int listen_socket = req->listen_socket;
    sa_t sa;
    socklen_t len = sizeof(sa);
    req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);

    ...

    if (req->fd >= 0) {
        // 采用多路复用的机制
        struct pollfd fds;
        int ret;

        fds.fd = req->fd;
        fds.events = POLLIN;
        fds.revents = 0;
        do {
            errno = 0;
            ret = poll(&fds, 1, 5000);
        } while (ret < 0 && errno == EINTR);
        if (ret > 0 && (fds.revents & POLLIN)) {
            break;
        }
        // 仅仅是关闭 socket 连接,不清空 req->env
        fcgi_close(req, 1, 0);
    }

    ...

    if (fcgi_read_request(req)) {
        return req->fd;
    }
}

并且把request归入全局变量sapi_globals.server_context,那点相当的重大,方便了在其他地点对须要的调用。

SG(server_context) = (void *) &request;

规范细节

行使对象必得承当三个岗位参数。为了便于表明,大家将参数命名称叫environ和start_response,当然你也得以用其余的称谓。服务器/网关必得运用地点参数(非关键字参数)调用应用对象,如result = application(environ, start_response)

environ参数是叁个词典对象,包括CGI风格的遭受变量。那个目的必需是二个放到的Python词典(不是子类、UserDict等),并允许应用程序校正字典。词典还必需含有有些WSGI必得的变量(在背后的章节中介绍),还或然带有特定的服务器的恢宏变量,根据预订格局打开命名。

start_response参数是二个可调用的指标,它承当五个必填的地点参数和叁个可选参数。那四个参数平日命名称叫status,response_headers和exc_info。应用程序平常经过start_response(status,response_headers)方式调用它。

status参数是方式为“200 OK”如此的情状字符串,response_headers是描述HTTP响应头的(header_name,header_value)元组列表。可选的exc_info参数仅在应用程序捕获错误并尝试向浏览器彰显错误消息时行使。start_response必需重回贰个write(body_data)的可调用对象,它负责一个使命参数,该参数作为HTTP响应大旨的意气风发部分。

当棉被和衣服务器调用时,应用对象必需回到多个生出零个或两个字节串的迭代,比方一个Python列表。倘诺应用程序重返的迭代对象具有close(State of Qatar方法,则服务器/网关在收尾近些日子呼吁前必得调用该格局,无论央求是寻常达成可能由于迭代以内的因为浏览器断开连接产生了应用程序错误而提早终止。调用close(卡塔尔方法是为着释放应用程序的能源。

Apache加载的是PHP模块,那么这些模块时怎么落到实处的吗?Apache2的mod_php5模块包含sapi/apache2handler和sapi/apache2filter三个目录,在apache2_handle/mod_php5.c文件中,模块定义的相关代码如下:

转载本站文章请注明出处:vns威尼斯城官网登入 http://www.tiec-ccpittj.com/?p=4308

上一篇:

下一篇:

相关文章