FastCGI 协议规范中文版
三、 协议基础
3.1、表达约定
本规范基于 C 语言的表示法来描述协议中涉及到的数据结构,这些数据结构的成员都是基于无符号字符型(unsigned char, 即 uint8 ) 的,且成员紧密排列无内存对齐处理。这样的好处是,这些结构在代码中看到的样子,和经过标准C编译器处理后在内存中的样子,是一模一样的。这些结构在传输时,第一个成员的第一个字节会被第一个传输,然后是第二个字节,然后是第二个成员,依次类推。
这里约定两种特殊的 C 语言简化表达方式:
第一种,当数据结构中两个相邻的成员,它们的名字除了结尾的 “B1″、”B0” 不同,其他部分都相同的时候,这意味着,这两个成员应该被当做一个两字节整数读取,其整数值为 B1 << 8 + B0,整数名字为成员的原始名字去除了 B1、B0 后缀的样子。这是一种多字节整数的简化表达方法。
第二种,允许struct(C 语言结构)含有变长数组成员。
1struct {
2 unsigned char mumbleLengthB1;
3 unsigned char mumbleLengthB0;
4 ... /* other stuff */
5 unsigned char mumbleData[mumbleLength];
6};
上例中 mumbleData 成员的长度就依赖于前面的成员 mumbleLength(双字节整数) 的值。
3.2、接受连接
一个 FastCGI 应用会监听 FCGI_LISTENSOCK_FILENO 对应的 socket,然后调用 accept() 来完成建立连接。当 accept() 调用成功时,应用可能会被绑定一个环境变量 FCGI_WEB_SERVER_ADDRS, 这个环境变量会存储一个逗号分隔的 Web 服务器的 IP 地址列表白名单。如果这个环境变量被绑定了,应用会校验当前发起连接的 Web 服务器的 IP 地址是否在这个列表里面,如果不在,或者这个连接的传输模式不是 TCP/ IP 模式,那么应用会直接关闭这个连接。
FCGI_WEB_SERVER_ADDRS 的值是逗号分隔的 IP 列表,其中每个 IP 表示为英文句号分隔的、范围为 [0, 255] 的十进制数。比如 FCGI_WEB_SERVER_ADDRS 一个合法的绑定值为 199.170.183.28,199.170.183.71。
在应用的实现中,可以支持接受 Web 服务器并发的建立连接,但这个特性不是强制实现的。
3.3、记录
3.3.1
基本结构
Web 服务器和 FastCGI 应用之间的通信采用了一个简单的协议,这个协议的细节依赖于应用当前的角色类型。整个流程大体上说,是 Web 服务器先发送参数和数据给应用,然后应用产生并发送结果数据给服务器,当结果数据发送完成的时候,应用需要发送一个请求处理完成标志给服务器。
数据在传输中,会被统一地编制成一个个记录 (FCGI_Record) 的形式。这种做法有两种好处:一是,多个请求的数据可以复用同一个连接进行传输,这样应用的实现就可以采用事件驱动的编程模型或者多线程编程模型;二是,同一个请求中的多个数据流的数据可以通过封装成不同记录的形式在同一个连接上传输,例如 stdout 和 stderr 两个输出流的数据可以通过同一个连接发送给 Web 服务器,而不是不得不使用两个连接。
1// FCGI_Record 结构定义
2typedef struct {
3 unsigned char version;
4 unsigned char type;
5 unsigned char requestIdB1;
6 unsigned char requestIdB0;
7 unsigned char contentLengthB1;
8 unsigned char contentLengthB0;
9 unsigned char paddingLength;
10 unsigned char reserved;
11 unsigned char contentData[contentLength];
12 unsigned char paddingData[paddingLength];
13} FCGI_Record;
一个记录的标准结构如上所示,前面包含一些定长的成员,尾部则是变长的内容成员和对齐用字节成员。下面分别介绍各个成员的含义:
-
version :表示 FastCGI 协议的版本号,本规范基于 FCGI_VERSION_1。
-
type :表示此记录的类型,预示着此记录的主要功能。记录的各种类型和功能会在后文表述。
-
requestId :表示此记录属于哪个 FastCGI 请求。
-
contentLength :表示后面成员 contentData 的长度。
-
paddingLength :表示后面成员 paddingData 的长度。
-
contentData :字节数组,长度范围在 [0, 65535],根据记录类型不同需要采用不同的解读方法。
-
paddingData :字节数组,长度范围在 [0, 255],处理时忽略该内容。
这里使用一种简化的 C 语言 struct 初始化语法来表达一个记录的值:忽略 version、padding相关成员,把 requestIdB1、 requestIdB0 当做 requestId 处理。
这样,一个 {FCGI_END_REQUEST, 1, { FCGI_REQUEST_COMPLETE, 0}} 语句表达了这样一个记录:它的 type值为 FCGI_END_REQUEST,requestId 值为 1,contentData 值为 {FCGI_REQUEST_COMPLETE, 0}。
3.3.2
字节对齐
本规范允许数据发送方发送字节对齐处理后的记录。这种情况下,数据接收方必须理解记录的 paddingLength 字段,且忽略paddingData 的内容。字节对齐的手段可以提升数据在内存中处理的效率, X window system 协议中也使用了这种手段。
本规范建议在内存中处理记录时,尽量保持每个记录的起始指针地址为8字节的整数倍。一个记录的定长部分长度也是8字节。
3.3.3
管理请求ID
Web 服务器在运行中会重复分配并使用请求 ID,在每一个 FastCGI 应用与 Web 服务器的连接上,应用都需要自行管理当前连接上的所有使用中的请求 ID。当应用收到记录 {FCGI_BEGIN_REQUEST, R, …} 时,说明要把请求 ID R 设置为使用中了,当收到记录 {FCGI_END_REQUEST, R, …} 时,说明要把请求ID R 设置成空闲状态了。当一个 ID R 处于空闲状态,则应用只需要响应属于请求 R 的类型为上面提到的 FCGI_BEGIN_REQUEST的记录,而忽略其他类型的记录。
Web 服务器会尽量保证请求 ID 局限在一个较小的范围,这样,应用就可以使用 short 数组管理请求 ID,而不是不得不使用 long 数组或者哈希表。应用的实现中,可以选择一次只接受一个请求的方案,在这种情况下,应用管理请求 ID 会比较简单,只需要比较新接收记录的请求 ID 和当前处理中的请求的 ID 是否一致即可。
3.3.4
记录的分类
这里有两种分类方法。
第一种分类方法是把记录分为管理类记录和应用类记录。一个管理类记录不和任何一个具体的请求相关联,比如此记录可能是 Web 服务器在查询应用的容量。一个应用类记录则是与某一个请求相关联的,它的 requestId 成员表明了本记录需要关联到哪一个请求。
管理类记录的 requestId 成员的值都是 0,或者叫 ID 为 null。应用类记录的 requestId 都不为 0。
第二种分类方法是把记录分为自描述型记录和流数据型记录。一个自描述型记录本身自己就可以表达有完整意义的内容,而一个流数据型记录则是一份完整的流数据的其中一部分。一份完整的流数据由若干内容长度为 0 或非 0 的流数据型记录和一个内容长度为0的流数据型记录结尾构成。把这些记录的 contentData 成员的值拼接成一个字节序,这个字节序就是这份流数据的完整值。这种组织方式意味着,一份完整的流数据的值,不会因为拆分记录的粒度不同而不同。
这两种分类方法是相互独立的,在本规范对应的版本下,所有的管理类记录都是自描述型记录,几乎所有的应用类记录都是流数据型记录。有三种应用类记录是自描述型的,且在以后的规范版本中,可能有的管理类记录也会是流数据型的。
3.4、键值对
当 FastCGI 应用处于若干角色类型中,都需要读写一些键值对数据。因此有必要定义一份键值对数据的编码标准。
当应用传输键值对时,本规范的做法是先传送键的长度,再传递值的长度,然后是键的值,最后是值的值。如果键或值的内容长度不大于 127 字节,那么长度就可以使用 1 字节进行编码,如果长度大于 127 字节,那么统一使用 4 字节进行编码。
下面是各种不同长度的内容下,键值对编码的格式:
// name 长度不大于 127, value 长度不大于 127 typedef struct { unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */ unsigned char nameData[nameLength]; unsigned char valueData[valueLength]; } FCGI_NameValuePair11; // name 长度不大于 127, value 长度大于 127 typedef struct { unsigned char nameLengthB0; /* nameLengthB0 >> 7 == 0 */ unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */ unsigned char valueLengthB2; unsigned char valueLengthB1; unsigned char valueLengthB0; unsigned char nameData[nameLength]; unsigned char valueData[valueLength ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 <> 7 == 1 */ unsigned char nameLengthB2; unsigned char nameLengthB1; unsigned char nameLengthB0; unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */ unsigned char nameData[nameLength ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 <> 7 == 1 */ unsigned char nameLengthB2; unsigned char nameLengthB1; unsigned char nameLengthB0; unsigned char valueLengthB3; /* valueLengthB3 >> 7 == 1 */ unsigned char valueLengthB2; unsigned char valueLengthB1; unsigned char valueLengthB0; unsigned char nameData[nameLength ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0]; unsigned char valueData[valueLength ((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0]; } FCGI_NameValuePair44;
长度成员的第一个字节的最高位为标志位,为 0 则表示本长度编码为 1 字节,为 1 则表示编码为 4 字节。
这种编码方式允许数据发送者在不附加额外信息的条件下进行安全的二进制传输,同时数据接受者也能在处理初期就能确定此键值对需求的空间大小。
3.5、关闭连接
正常情况下,Web 服务器管理着连接的生命期。当没有待处理的请求时,Web 服务器就可以关闭此连接。Web 服务器也可以授权 FastCGI 应用代为关闭某个连接(见 FCGI_BEGIN_REQUEST 记录),在这种情况下,应用会在处理完成那个请求之后,自行关闭对应连接。
上述机制让应用在实现时可以选择不同的风格。一个简单的 FastCGI 的应用实现可能是,对同一个连接只处理一个请求,每个新请求都需要建立新连接传递过来。一种更加复杂的实现是,应用可以在同一个连接上并发处理多个请求,这样这个连接就会作为长连接存在。
应用在输出响应之后自行关闭连接,然后继续进行后续逻辑的处理,可以显著地降低响应时间。注意此时长连接的生命期只能由 Web 服务器自己管理。
当应用自行关闭了某个连接或者发现某连接已经被关闭了,在必要的时候,需要建立新连接来进行后续处理。
四、 管理类记录
4.1
FCGI_GET_VALUES, FCGI_GET_VALUES_RESULT
Web 服务器可以查询 FastCGI 应用的内部变量,一个 Web 服务器的典型实现是,在启动时查询应用的一些配置,然后根据这些数据调整自身的某些系统设置。
当应用收到一个{FCGI_GET_VALUES, 0, …} 记录时,表明 Web 服务器在请求查询。这个记录的 contentData 成员内容为,一串值均为空的键值对编码数据。
应用会解析这些键,填充上它们对应的值,最后构建一串新的键值对编码数据,放入 {FCGI_GET_VALUES_RESULT, 0, …} 记录中,返回给 Web 服务器。如果应用不理解某个键指向的数据,就不会在返回记录中包含这个键值对。
FCGI_GET_VALUES 记录可以查询的变量范围是允许扩展的,这里定义了首批 Web 服务器关心的变量名单。
-
FCGI_MAX_CONNS :这个应用支持的最高并发连接数。比如 “1” 或 “10”。
-
FCGI_MAX_REQS :这个应用支持的最高并发请求数。比如 “1” 或 “50”。
-
FCGI_MPXS_CONNS :“0” 表示这个应用不支持连接复用,一个请求使用一个连接,1表示支持。
应用可能在任何时间收到 Web 服务器的查询请求,对此生成响应的应该是 FastCGI 底层库,不需要应用的实现层来发出响应。
4.2、FCGI_UNKNOWN_TYPE
在 FastCGI 协议的未来版本中,管理类记录的种类可能会扩充,这里引入 FCGI_UNKNOWN_TYPE 类型来兼容处理这种情况。当旧版应用收到一个新版的类型为 T 的管理类记录时,可以使用一个 {FCGI_UNKNOWN_TYPE, 0, {T}} 记录进行响应。
一个 FCGI_UNKNOWN_TYPE 记录的 contentData 成员结构如下:
typedef struct {
unsigned char type;
unsigned char reserved[7];
} FCGI_UnknownTypeBody;
其中 type 成员表示的就是未知的请求记录类型 T。
五、 应用类记录
5.1、FCGI_BEGIN_REQUEST
Web 服务器发送一个 FCGI_BEGIN_REQUEST 记录给 FastCGI 应用来开始一个请求的处理。
一个 FCGI_BEGIN_REQUEST 记录的 contentData 成员结构如下:
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
其中 role 成员指明了 Web服务器希望应用处理此请求所使用的角色类型,当前有三种定义好的角色类型:
-
FCGI_RESPONDER 响应器
-
FCGI_AUTHORIZER 鉴权器
-
FCGI_FILTER 过滤器
在第六章会详细介绍这些角色类型的细节。
其中 flags 成员含有一个控制连接关闭方式的标志位。
当 flags & FCGI_KEEP_CONN 的值为 0 时,表明需要应用在响应此请求之后,主动关闭对应的连接。如果非 0,则不需要,这时,此连接的生命期由 Web 服务器控制。
5.2、键值对数据记录:FCGI_PARAMS
FCGI_PARAMS 记录被 Web 服务器用来向应用传递键值对数据,键值对数据会被封装成若干这类记录,形成一个数据流,依次传递。键值对间不区分顺序。
5.3、字节流类记录
FCGI_STDIN,FCGI_DATA
FCGI_STDOUT FCGI_STDERR
FCGI_STDIN 记录被 Web 服务器用来向应用传输任意格式的数据,FCGI_DATA 记录是类似的用法。
FCGI_STDOUT 记录被应用当做标准输出,用来向 Web 服务器传输任意类型的数据。
FCGI_STDERR 记录被应用当做标准错误输出,用来向 Web 服务器传输任意类型的数据。
5.4、FCGI_ABORT_REQUET
Web 服务器通过向 FastCGI 应用发送一个 FCGI_ABORT_REQUEST 记录来终止一个请求的处理。当应用收到一个
{FCGI_ABORT_REQUEST, R} 记录时, 需要立即以
{FCGI_END_REQUEST, R, {FCGI_REQUEST_COMPLETE, appStatus}}
记录进行回复, 这是一个应用实现级别的响应,并非 FastCGI 底层库自行发出的响应。
当一个请求经由 Web 服务器进入到 FastCGI 应用中,处理暂未结束时,如果此时 HTTP 客户端断开与 Web 服务器的连接,那么 Web 服务器就会触发此请求的终止操作。这种情况发生比例较低,因为大多数 FastCGI 请求在应用中都会在较短的时间内完成并返回,甚至在 HTTP 客户端传输速度较慢的时候,Web 服务器会把应用的响应数据缓存下来,慢慢的发送给客户端。在某些情况下,应用可能会因为外部依赖或者执行某个推送任务而耗时过长。
如果 Web 服务器和应用之间的通信没有复用连接,那么 Web 服务器可以通过关闭这个请求对应的连接来终止这个请求的处理。但是在连接复用的情况下,关闭这个连接会导致此连接上所有正在进行请求的执行终止。
5.5、FCGI_END_REQUEST
FastCGI 应用会通过发送一个 FCGI_END_REQUEST 记录给 Web 服务器,来通知它某个请求被终止执行了。这种情况发生在请求已经被处理完成,或者请求被拒绝执行时。
一个 FCGI_END_REQUEST 记录的 contentData 成员的结构如下:
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
appStatus 成员是应用实现级别的状态码。这个值在不同的角色类型下代表的含义会在介绍角色类型时详细描述。
protocolStatus 成员是 FastCGI 协议级别的状态码,可能的取值如下:
-
FCGI_REQUEST_COMPLETE :表明正常结束一个请求。
-
FCGI_CANT_MPX_CONN :表明拒绝新请求进入。这种情况发生在 Web服务器在不允许复用的连接上发送了第二个请求过来时。
-
FCGI_OVERLOADED :表明拒绝新请求进入。这种情况发生在应用的资源不足时,比如数据库连接不足。
-
FCGI_UNKNOWN_ROLE :表明拒绝新请求进入。这种情况发生在 Web 服务器指定了一个应用不理解的角色类型时。
六、 角色
6.1、角色下的协议
应用作为任一角色类型处理请求时,传输中使用到的记录,都是应用类的记录,所以它们也都是流数据型记录。
综合考虑协议的可靠性和 FastCGI 应用实现的难易度,本规范采用了近似顺序传输模式。在严格顺序传输模式下,应用会顺序接收第一个输入、第二个输入等等直到输入全部接收完成,类似地,输出响应数据时,也会先输出第一个输出,其次第二个输出这样依次输出,直到输出全部完成。严格顺序模式下,在时间上,所有输入数据之间互不交叉,所有输出数据之间也互不交叉。
对于一些 FastCGI 应用的角色类型而言,严格顺序模式太过苛刻了。CGI 规范允许在交叉时间上分别输出数据到 stdout 和 stderr,所以 FastCGI 的协议也需要兼容这种特性,即允许交叉使用 FCGI_STDOUT 记录和 FCGI_STDERR 记录。
在任意角色下,应用都像使用 stderr 一样使用 FCGI_STDERR 记录,通过它可以输出应用级别的易于理解的错误报告信息。发送 FCGI_STDERR 记录是可选的,如果没有错误需要报告,可以不发送 FCGI_STDERR 记录,或者发送一个数据长度为0的记录过去。
在任意角色下,应用使用 FCGI_STDERR 对应之外的数据流时,必须至少发送一个记录过去,就算待传输数据长度为 0,也需要发送一个数据长度为 0 的记录。
同样的,综合考虑协议的可靠性和 FastCGI 应用实现的难易度,本规范采用了近似请求-响应模式。在严格请求-响应模式下,应用必须接收完成全部的请求数据之后,才允许输出响应,不允许边收边发式的流水线作业方式。
对一些 FastCGI 应用的角色类型而言,严格请求-响应模式太过苛刻了,毕竟 CGI 协议并没有要求应用一定要在全部输入读取完成之后才可以输出响应,所以 FastCGI 协议也需要兼容这个特性。本规范设定为这样:应用首先需要读取完成除最后一个流类型数据外的所有输入数据,然后在开始读取最后一个流类型数据的时候,就可以开始输出响应数据了。
在使用 FCGI_PARAMS 记录传输文本数据时,例如 CGI 应用从环境变量中读取到的那些数据,记录中的长度字段不包含文本结尾处额外的 null 字节(”\0″),且文本尾部也没有额外设置 null 字节。如果应用自己需要依赖这些数据生成 environ 格式的数据,必须自己在这些键值对之间添加等于号,且在值的尾部添加 null 字节。
FastCGI 协议并没有使用与 CGI 协议不兼容的 header 字段,FastCGI 应用通过使用 CGI 协议中同样的 Status 和 Location 字段来返回状态码。
6.2、响应器
响应器角色下的 FastCGI 应用和 CGI/1.1 的应用表现得基本一致:接收 HTTP 请求的输入数据,产生一个 HTTP 响应数据。
这里介绍下该角色下的 FastCGI 协议如何兼容 CGI/1.1协议。
-
应用通过读取 Web 服务器传送的 FCGI_PARAMS 记录数据来实现 CGI/1.1 协议下获取环境变量的模块。
-
然后应用通过读取 Web 服务器传送的 FCGI_STDIN 记录数据来实现 CGI/1.1 协议下读取 stdin 数据的模块。应用最多读取长度为 CONTENT_LENGTH 的数据,且在收到数据结束标记时停止接收。(应用接收数据的长度少于 CONTENT_LENGTH 的情况,应该仅仅发生于发送者发送的数据不足时,例如发送者宕机了 )
-
应用通过发送 FCGI_STDOUT 记录来发送数据来实现 CGI/1.1 协议的 stdout 输出模块,通过发送 FCGI_STDERR 记录实现 CGI/1.1 协议的 stderr 输出模块。这两个输出流是支持交叉写入的,不限制必须发送完一个再发送另一个。发送数据前要求必须已经读取 FCGI_PARAMS 记录的流数据完成,但是不要求读取 FCGI_STDIN 记录的流数据完成。
-
当应用已经发送完成 stdout 和 stderr 的数据时,需要发送一个FCGI_END_REQUEST记录给 Web 服务器。
此记录中:
protocolStatus 成员值设置为 FCGI_REQUEST_COMPLETE
appStatus 成员值设置成 CGI 协议中 status 字段约定的有效范围内的值
这个 appStatus 会被 exit() 系统调用返回给上层。
此角色下,如果应用处理一个写入请求时,发现接收的数据长度和 CONTENT_LENGTH 不符,应该立即终止处理这个请求。
6.3、鉴权器
一个鉴权器角色下的 FastCGI 应用会在获取 HTTP 请求的所有数据之后,生成一个鉴权通过或者不通过的结果。在通过的情况下,应用可以向这个请求中添加键值对数据,并让 Web 服务器对此请求采取下一步处理前添加这些额外信息给下游。鉴权不通过的情况下,应用会直接返回一个终结用的响应,这个响应最终会被给到 HTTP 客户端。
CGI/1.1 协议已经定义了此角色下请求所需的各个属性了。FastCGI 协议会使用这些既有字段进行处理。
-
应用通过读取 Web 服务器传送的 FCGI_PARAMS 记录来获取请求的各项数据,FCGI_PARAMS 记录中的内容成员的格式类似于响应器模式下应用涉及到的那样。 只是:
CONTENT_LENGTH, PATH_INFO,
P ATH_TRANSLATED, SCRIPT_NAME
这些 header 字段不会被传送过来。
-
应用发送 stdout 和 stderr 数据的方式,和响应器角色下基本一样,使用 CGI/1.1协议中的响应状态码表明请求的结果,如果应用传递了一个状态码为 200 的响应给 Web 服务器,那么 Web 服务器认为本鉴权处理通过。根据 Web 服务器设置的不同,Web 服务器可能还需要进行进一步的鉴权校验,或者向另一个鉴权器应用发起再次鉴权请求。
鉴权器应用发送状态码为 200 的响应到 Web 服务器时,可能会在 header 中添加一些 “Variable-” 开头的字段,这些字段是应用传送给 Web 服务器的额外数据,比如下面这个 header 字段传递了名为 “AUTH_METHOD”、值为 “database lookup” 的数据给 Web 服务器。在下游 CGI 应用或者 FastCGI 应用接手处理这个请求之前,Web 服务器需要把这个数据添加到该请求中。
Variable-AUTH_METHOD: database lookup
鉴权器应用发送状态码为 200 的响应到 Web 服务器时,Web 服务器应该忽略该响应 header 中非 “Variable-” 开头的字段,也应该忽略响应中任何的非 header 数据。
鉴权器应用发送状态码为非 200 的响应到 Web 服务器时,Web 服务器应该终止执行这个请求,同时使用响应中的 status、headers 、content 组成响应发送给 HTTP 客户端。
6.4、过滤器
一个过滤器角色下的 FastCGI 应用,会从 Web 服务器获取到请求的所有相关数据,外加一个位于 Web 服务器的文件的内容数据流,然后对这个文件内容数据流进行过滤,把过滤得到的结果作为一个响应发送给 Web 服务器。
一个过滤器角色下的应用,表现上很像一个接受一个文件作为参数的响应器应用。它们两个的区别是,过滤器角色下,应用本身和输入文件数据流都会受限于 Web 服务器的权限控制,在响应器角色中,应用只能使用自身的权限校验机制。
一个过滤器应用的处理过程类似于响应器应用,Web 服务器首先发送环境变量给应用,然后是 stdin 数据(通常是 post 请求的表单数据),最后是待过滤的文件数据流。
-
应用通过 Web 服务器传送的 FCGI_PARAMS 记录数据来获取请求的各项数据。在此角色下,应用还会获取到两个特有字段:
FCGI_DATA_LAST_MOD 、 FCGI_DATA_LENGTH。
-
然后应用读取 FCGI_STDIN 记录数据,就像 CGI/1.1 应用读取 stdin 数据那样。读取数据的长度不会超过CONTENT_LENGTH,且在遇到数据结束标志时停止。(应用接收数据的长度少于 CONTENT_LENGTH 的情况,应该仅仅发生于发送者发送的数据不足时,例如发送者宕机了 )
-
应用通过读取 FCGI_DATA 记录数据获得待过滤的文件内容。读取 FCGI_DATA_LAST_MOD 的值可以获取该文件的最后更改时间,其格式为一个整数,表示自 UTC 时间 1970-01-01 00:00:00 至今的秒数。应用可以根据此字段判断是否读可以取缓存文件,来代替读取记录内容。应用在读取数据时,最多读取 FCGI_DATA_LENGTH 长度的数据,且遇到流数据结束标志时停止。
-
应用通过发送 FCGI_STDOUT 记录来实现 CGI/1.1 的 stdout 输出模块,通过发送 FCGI_STDERR 记录实现 CGI/1.1 的 stderr 输出模块。这两个输出流是支持交叉发送的,不要求必须发送完一个再发送另一个。输出数据要求当前必须已经读取完成 FCGI_PARAMS 记录数据,但是不要求读取完成 FCGI_STDIN 记录的数据。
-
当应用已经发送完成 stdout 和 stderr 数据时,需要发送一个
FCGI_END_REQUEST 记录给 Web 服务器。 此记录中:
protocolStatus 成员值设为: FCGI_REQUEST_COMPLETE
appStatus 成员值设置成 CGI 协议中 status 字段有效范围内的值
这个 appStatus 会被 exit() 系统调用返回给上层。
过滤器应用应该校验 FCGI_STDIN 记录中读取到的数据长度,是否等于 CONTENT_LENGTH 的值, 从 FCGI_DATA 记录中读取的数据长度,是否等于 FCGI_DATA_LENGTH 的值。在处理只读请求时,如果出现了不一致的情况,应用应该返回一个标志给 Web 服务器,表示该请求的数据有丢失情况。如果是处理写入请求,应用则应该立即终止此请求的处理。
七、 错误处理
如果一个 FastCGI 应用进程的退出码为 0,则表示这个进程是正常终止的,比如系统底层进行垃圾回收操作时回收了此进程。如果退出码不为 0,则表示此进程宕机了,Web 服务器怎么处理应用进程的退出码不在本规范的叙述范围之内。
Web 服务器可以通过发送 SIGTERM 信号给应用来让应用退出,如果应用忽略了 SIGTERM信号,Web 服务器可以尝试发送 SIGKILL 信号来让应用退出。
应用会通过发送 FCGI_STDERR 记录来报告应用级别的错误,也可能会通过设置 FCGI_END_REQUEST 记录中的appStatus 字段来报告错误。在很多时候下,应用也会选择通过发送 FCGI_STDOUT 记录来发送错误报告,这样可以直接向 HTTP 客户端展示错误报告。
在 Unix 系统上,应用会把底层错误,包括 FastCGI 底层库级别错误、环境变量语法错误等写入到系统 syslog 目录中。根据错误的严重程度不同,应用可能会继续运行,也有可能直接使用非 0 退出码退出。
八、 数据结构和常量定义
/*
* Listening socket file number
*/
#define FCGI_LISTENSOCK_FILENO 0
typedef struct {
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;
/*
* Number of bytes in a FCGI_Header. Future versions of the protocol
* will not reduce this number.
*/
#define FCGI_HEADER_LEN 8
/*
* Value for version component of FCGI_Header
*/
#define FCGI_VERSION_1 1
/*
* Values for type component of FCGI_Header
*/
#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)
/*
* Value for requestId component of FCGI_Header
*/
#define FCGI_NULL_REQUEST_ID 0
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
typedef struct {
FCGI_Header header;
FCGI_BeginRequestBody body;
} FCGI_BeginRequestRecord;
/*
* Mask for flags component of FCGI_BeginRequestBody
*/
#define FCGI_KEEP_CONN 1
/*
* Values for role component of FCGI_BeginRequestBody
*/
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
typedef struct {
FCGI_Header header;
FCGI_EndRequestBody body;
} FCGI_EndRequestRecord;
/*
* Values for protocolStatus component of FCGI_EndRequestBody
*/
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/*
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
*/
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
typedef struct {
unsigned char type;
unsigned char reserved[7];
} FCGI_UnknownTypeBody;
typedef struct {
FCGI_Header header;
FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;
九、 参考资料
The WWW Common Gateway Interface at W3C
(https://www.w3.org/CGI/ )
附表A:各种类型记录的特性
下表列出了各类型记录所支持的特性情况(x表示勾选)
-
WS->App :这个特性勾选表示此类型记录只能被 Web 服务器发送给应用。非勾选类型的记录只能被应用发送给 Web 服务器。
-
management :这个特性勾选表示此类型记录是非关联到某个请求的记录,记录的 requestId 成员值为 null。非勾选类型的记录则有关联到某个请求,记录的 requestId 成员值不为 null。
-
stream :这个特性勾选表示此类型记录的内容被用来组成一个数据流,这个数据流会使用一个 contentData 成员长度为 0 的记录作为终止标志。这种类型的记录都是非自描述的,每个记录都是整个流数据中的一部分。
记录类型 / 特性 | WS->App | management | stream |
---|---|---|---|
FCGI_GET_VALUES | x | x | |
FCGI_GET_VALUES_RESULT | x | ||
FCGI_UNKNOWN_TYPE | x | ||
FCGI_BEGIN_REQUEST | x | ||
FCGI_ABORT_REQUEST | x | ||
FCGI_END_REQUEST | |||
FCGI_PARAMS | x | x | |
FCGI_STDIN | x | x | |
FCGI_DATA | x | x | |
FCGI_STDOUT | x | ||
FCGI_STDERR | x |
附表B:典型数据流示例
这里引入一些简化表达方式来描述数据流:
流数据式记录 (FCGI_PARAMS,FCGI_STDIN,FCGI_STDOUT, FCGI_STDERR) 中的 contentData 成员值使用字符串表示。一个以 “…” 结尾的字符串表示此值过长,需要截断显示。
为了区别显示 Web 服务器发送和接收的数据,Web 服务器接收的数据会缩进显示。 数据流会按照实际的处理时间顺序排列显示。
0
1
一个简单的请求,其 stdin 数据为空,应用成功处理了这个请求。
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR ... "}
{FCGI_PARAMS, 1, ""}
{FCGI_STDIN, 1, ""}
{FCGI_STDOUT, 1, "Content-type: text/html\r\n\r\n\n ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
0
2
类似例子1中的请求,只是本例的请求中 stdin 数据不为空,Web 服务器同时也使用 FCGI_PARAMS 记录来发送更多的参数。
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SER"}
{FCGI_PARAMS, 1, "VER_ADDR199.170.183.42 ... "}
{FCGI_PARAMS, 1, ""}
{FCGI_STDIN, 1, "quantity=100&item=3047936"}
{FCGI_STDIN, 1, ""}
{FCGI_STDOUT, 1, "Content-type: text/html\r\n\r\n\n ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
0
3
类似例子1中的请求,但是本例中,应用处理请求时,触发了一个错误。应用会把错误报告发送到 stderr 数据流,同时以非0的退出码退出。在退出之前,应用还使用 FCGI_STDOUT记录返回了一份数据作为响应。
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR ... "}
{FCGI_PARAMS, 1, ""}
{FCGI_STDIN, 1, ""}
{FCGI_STDOUT, 1, "Content-type: text/html\r\n\r\n<ht"}
{FCGI_STDERR, 1, "config error: missing SI_UID\n"}
{FCGI_STDOUT, 1, "ml>\n ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_STDERR, 1, ""}
{FCGI_END_REQUEST, 1, {938, FCGI_REQUEST_COMPLETE}}
0
4
本例由两个例子1中的请求构成,它们复用同一个连接进行处理。因为第一个请求比较复杂导致耗时较长,从而应用会首先完成并响应第二个请求,后续才响应第一个请求。
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, FCGI_KEEP_CONN}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR ... "}
{FCGI_PARAMS, 1, ""}
{FCGI_BEGIN_REQUEST, 2, {FCGI_RESPONDER, FCGI_KEEP_CONN}}
{FCGI_PARAMS, 2, "\013\002SERVER_PORT80\013\016SERVER_ADDR ... "}
{FCGI_STDIN, 1, ""}
{FCGI_STDOUT, 1, "Content-type: text/html\r\n\r\n"}
{FCGI_PARAMS, 2, ""}
{FCGI_STDIN, 2, ""}
{FCGI_STDOUT, 2, "Content-type: text/html\r\n\r\n\n ... "}
{FCGI_STDOUT, 2, ""}
{FCGI_END_REQUEST, 2, {0, FCGI_REQUEST_COMPLETE}}
{FCGI_STDOUT, 1, "\n ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
附:原文地址和版权声明
原文地址:
https://fastcgi-archives.github.io/FastCGI_Specification.html
版权声明:
Mark R. Brown
Open Market, Inc.
Document Version: 1.0
29 April 1996
Copyright © 1996 Open Market, Inc. 245 First Street, Cambridge, MA 02142 U.S.A.
作 者: 比克(企业代号名)
出品人: 五花肉、克虏伯 (企业代号名)
———- END ———-