FastCGI協議是在CGI協議的基礎上發展出來的,如果想了解CGI協議,可以看我另一篇文章:動態web技術(二) --- CGI,FastCGI程序本身監聽某個socket然後等待來自web服務器的連接,而不是像CGI程序是由web服務器 fork-exec,所以FastCGI本身是一個服務端程序,而web服務器對它來說則是客戶端。
FastCGI程序和web服務器之間通過可靠的流式傳輸(Unix Domain Socket或TCP)來通信,相對於傳統的CGI程序,有環境變量和標準輸入輸出,而FastCGI程序和web服務器之間則只有一條socket連接來傳輸數據,所以它把數據分成以下多種消息類型:
#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)
以上由web服務器向FastCGI程序傳輸的消息類型有以下幾種:
FCGI_BEGIN_REQUEST 表示一個請求的開始,
FCGI_ABORT_REQUEST 表示服務器希望終止一個請求
FCGI_PARAMS 對應於CGI程序的環境變量,php $_SERVER 數組中的數據絕大多數來自於此
FCGI_STDIN 對應CGI程序的標準輸入,FastCGI程序從此消息獲取 http請求的POST數據
此外 FCGI_DATA 和 FCGI_GET_VALUES 這裏不做介紹。
由FastCGI程序返回給web服務器的消息類型有以下幾種:
FCGI_STDOUT 對應CGI程序的標準輸出,web服務器會把此消息當作html返回給瀏覽器
FCGI_STDERR 對應CGI程序的標準錯誤輸出, web服務器會把此消息記錄到錯誤日誌中
FCGI_END_REQUEST 表示該請求處理完畢
FCGI_UNKNOWN_TYPE FastCGI程序無法解析該消息類型
此外還有 FCGI_GET_VALUES_RESULT 這裏不做介紹
web服務器和FastCGI程序每傳輸一個消息,首先會傳輸一個8字節固定長度的消息頭,這個消息頭記錄了隨後要傳輸的這個消息的 類型,長度等等屬性,消息頭的結構體如下:
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;
} ;
version 表示fastcgi協議版本
type 表示消息的類型,就是前面提到的多種消息類型之一,如 FCGI_BEGIN_REQUEST、FCGI_PARAMS 等等
requestIdB1 和 requestIdB0 這兩個字節組合來表示 requestId (對於每個請求web服務器會分配一個requestId), requestIdB1 是requestId的高八位,requestIdB0是低八位,所以最終的 requestId = (requestIdB1 << 8) + requestIdB0,因爲是兩個字節來表示,requestId最大取值爲65535, 同理 contentLengthB1 和 contentLengthB0 共同來表示消息體的長度,對於超過65535的消息體,可以切割成多個消息體來傳輸
paddingLength 爲了使消息8字節對齊,提高傳輸效率,可以在消息上添加一些字節數來達到消息對齊的目的,paddingLength 爲添加的字節數,這些字節是無用數據,讀出來可以直接丟棄。
reserved 保留字段,暫時無用
比如在傳輸 FCGI_BEGIN_REQUEST 消息之前,首先會傳輸一個消息頭類似如下:
0x0000080001000101 粗體 08 對應 requestIdB0 ,粗體00 對應 requestIdB1 ,所以後續要傳輸的這個消息的長度是八字節,粗體01指代該消息類型爲 FCGI_BEGIN_REQUEST
對於 FCGI_BEGIN_REQUEST 和 FCGI_END_REQUEST 消息類型,fastcgi協議分別定義了一個結構體如下,而對於其他類型的消息體,沒有專門結構體與之對應,消息體就是普通的二進制數據。
struct FCGI_BeginRequestBody {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} ;
struct FCGI_EndRequestBody {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
};
從這個結構體可以知道 FCGI_BEGIN_REQUEST 和 FCGI_END_REQUEST 消息體的長度都是固定的8個字節。
FCGI_BeginRequestBody 的 roleB1 和 roleB0 兩個字節組合指代 web服務器希望FastCGI程序充當的角色,目前FastCGI協議僅定義了三種角色:
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
常見的FastCGI程序基本都是作爲 FCGI_RESPONDER (響應器角色),所以roleB1的值總是0, roleB0的值可取1~3三個值,但 常見都是1,其他兩種角色這裏不做討論。
flags 是一個8位掩碼, web服務器可以利用該掩碼告知FastCGI程序在處理完一個請求後是否關閉socket連接 (最初協議的設計者可能還預留了該掩碼的其他作用,只是目前只有這一個作用)
flags & FCGI_KEEP_CONN 的值爲1,則FastCGI程序請求結束不關閉連接,爲0 則關鍵連接
其中 FCGI_KEEP_CONN 是一個宏,定義如下:
#define FCGI_KEEP_CONN 1
以下是協議對於響應器角色的解釋:
Responder FastCGI應用程序具有與CGI / 1.1程序相同的用途:它接收與HTTP請求相關的所有信息並生成HTTP響應。
以下解釋CGI / 1.1的每個元素和響應者角色的消息類型的對應關係:
- Responder應用程序通過FCGI_PARAMS從Web服務器接收CGI / 1.1環境變量。
- 接下來,Responder應用程序通過FCGI_STDIN從Web服務器接收CGI / 1.1 stdin數據。在接收流結束指示之前,應用程序最多從此流接收CONTENT_LENGTH個字節。(僅當HTTP客戶端無法提供時,應用程序纔會收到少於CONTENT_LENGTH的字節,例如,因爲客戶端崩潰了。)
- 響應者應用程序通過FCGI_STDOUT發送CGI / 1.1 標準輸出數據到Web服務器,通過FCGI_STDERR發送CGI / 1.1 標準錯誤數據。應用程序併發地發送這些,而不是一個接一個地發送。應用程序必須等待讀完FCGI_PARAMS數據之後纔可以寫FCGI_STDOUT和FCGI_STDERR,但它不需要等待讀完FCGI_STDIN纔可以寫這兩個流。
- 發送所有stdout和stderr數據後,Responder應用程序將發送FCGI_END_REQUEST記錄。應用程序將protocolStatus組件設置爲FCGI_REQUEST_COMPLETE,將appStatus組件設置爲CGI程序通過退出系統調用返回的狀態代碼。
處理POST請求的響應方應比較FCGI_STDIN上接收到的字節數和CONTENT_LENGTH,如果兩個數不相等,則中止請求。
FCGI_EndRequestBody 中 appStatus是應用級別的狀態碼。每個角色記錄其對appStatus的使用情況,不做深入討論
所述的protocolStatus 是協議級別的狀態碼,可能的protocolStatus值是:
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
FCGI_REQUEST_COMPLETE:請求的正常結束,典型的應該都是該個值。
FCGI_CANT_MPX_CONN:拒絕新的請求。當Web服務器通過一個連接將併發請求發送到每個連接一次處理一個請求的應用程序時,會發生這種情況。
FCGI_OVERLOADED:拒絕新的請求。當應用程序耗盡某些資源(例如數據庫連接)時會發生這種情況。
FCGI_UNKNOWN_ROLE:拒絕新的請求。當Web服務器指定了應用程序未知的角色時,會發生這種情況。
所以,當FastCGI程序從web服務器讀取數據時,總是先讀取一個8字節的消息頭,然後得到消息的類型和長度信息,然後再讀取消息體,一種消息過長可以切割成多個消息傳輸,當一個消息頭裏的 contentLength 爲0(也即 contentLengthB1和contentLengthB0 的值都爲0) 時,則表明這種消息傳輸完畢,然後我們可以把之前讀到的這種類型的多個消息合併得到最終完整的消息。反之,當我們要從FastCGI程序向web服務器返回數據時,總是每發送一個8字節消息頭,緊接發送一次消息體,循環往復,直到最後發送 FCGI_END_REQUEST類型的消息頭 和消息體結束請求。
總結一下web服務器和FastCGi程序之間大概的消息發送流程:
1、web服務器向FastCGI程序發送一個 8 字節 type=FCGI_BEGIN_REQUEST的消息頭和一個8字節 FCGI_BeginRequestBody 結構的 消息體,標誌一個新請求的開始
2、web服務器向FastCGI程序發送一個 8 字節 type=FCGI_PARAMS 的消息頭 和一個消息頭中指定長度的FCGI_PARAMS類型消息體
3、根據FCGI_PARAMS消息的長度可能重複步驟 2 多次,最終發送一個 8 字節 type=FCGI_PARAMS 並且 contentLengthB1 和 contentLengthB0 都爲 0 的消息頭 標誌 FCGI_PARAMS 消息發送結束
4、以和步驟2、3相同的方式 發送 FCGI_STDIN 消息
5、FastCGI程序處理完請求後 以和步驟2、3相同的方式 發送 FCGI_STDOUT消息 和 FCGI_STDERR 消息返回給服務器
6、FastCGI程序 發送一個 type= FCGI_END_REQUEST 的消息頭 和 一個8字節 FCGI_EndRequestBody 結構的消息體,標誌此次請求結束
FastCGI協議的完整規範請查看: https://fastcgi-archives.github.io/FastCGI_Specification.html
下面用c 編寫一個簡單的實現fastcgi協議的demo,這個demo主要是用來向大家更直觀的展示FastCGI協議,錯誤處理和內存泄漏檢測都不到位,專業的協議實現可以看官方提供的 Fastcgi Developer‘s kit : https://fastcgi-archives.github.io/FastCGI_Developers_Kit_FastCGI.html ,其中封裝的c庫在 libfcgi 文件中。
完整代碼託管在github上: https://github.com/zhyee/fastcgi-demo
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#define HEAD_LEN 8 //消息頭長度固定爲8個字節
#define BUFLEN 4096
#define FCGI_VERSION_1 1 //版本號
// 消息類型
enum fcgi_request_type {
FCGI_BEGIN_REQUEST = 1,
FCGI_ABORT_REQUEST = 2,
FCGI_END_REQUEST = 3,
FCGI_PARAMS = 4,
FCGI_STDIN = 5,
FCGI_STDOUT = 6,
FCGI_STDERR = 7,
FCGI_DATA = 8,
FCGI_GET_VALUES = 9,
FCGI_GET_VALUES_RESULT = 10,
FCGI_UNKOWN_TYPE = 11
};
// 服務器希望fastcgi程序充當的角色, 這裏只討論 FCGI_RESPONDER 響應器角色
enum fcgi_role {
FCGI_RESPONDER = 1,
FCGI_AUTHORIZER = 2,
FCGI_FILTER = 3
};
//消息頭
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;
};
//請求開始發送的消息體
struct FCGI_BeginRequestBody {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
};
//請求結束髮送的消息體
struct FCGI_EndRequestBody {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
};
// protocolStatus
enum protocolStatus {
FCGI_REQUEST_COMPLETE = 0,
FCGI_CANT_MPX_CONN = 1,
FCGI_OVERLOADED = 2,
FCGI_UNKNOWN_ROLE = 3
};
// 打印錯誤並退出
void haltError(char *type, int errnum)
{
fprintf(stderr, "%s: %s\n", type, strerror(errnum));
exit(EXIT_FAILURE);
}
// 存儲鍵值對的結構體
struct paramNameValue {
char **pname;
char **pvalue;
int maxLen;
int curLen;
};
// 初始化一個鍵值結構體
void init_paramNV(struct paramNameValue *nv)
{
nv->maxLen = 16;
nv->curLen = 0;
nv->pname = (char **)malloc(nv->maxLen * sizeof(char *));
nv->pvalue = (char **)malloc(nv->maxLen * sizeof(char *));
}
// 擴充一個結鍵值構體的容量爲之前的兩倍
void extend_paramNV(struct paramNameValue *nv)
{
nv->maxLen *= 2;
nv->pname = realloc(nv->pname, nv->maxLen * sizeof(char *));
nv->pvalue = realloc(nv->pvalue, nv->maxLen * sizeof(char *));
}
// 釋放一個鍵值結構體
void free_paramNV(struct paramNameValue *nv)
{
int i;
for(i = 0; i < nv->curLen; i++)
{
free(nv->pname[i]);
free(nv->pvalue[i]);
}
free(nv->pname);
free(nv->pvalue);
}
// 獲取指定 paramName 的值
char *getParamValue(struct paramNameValue *nv, char *paramName)
{
int i;
for(i = 0; i < nv->curLen; i++)
{
if (strncmp(paramName, nv->pname[i], strlen(paramName)) == 0)
{
return nv->pvalue[i];
}
}
return NULL;
}
int main(){
int servfd, connfd;
int ret, i;
struct sockaddr_in servaddr, cliaddr;
socklen_t slen, clen;
struct fcgi_header header, headerBuf;
struct FCGI_BeginRequestBody brBody;
struct paramNameValue paramNV;
struct FCGI_EndRequestBody erBody;
ssize_t rdlen;
int requestId, contentLen;
unsigned char paddingLen;
int paramNameLen, paramValueLen;
char buf[BUFLEN];
unsigned char c;
unsigned char lenbuf[3];
char *paramName, *paramValue;
char *htmlHead, *htmlBody;
/*socket bind listen*/
servfd = socket(AF_INET, SOCK_STREAM, 0);
if (servfd == -1)
{
haltError("socket", errno);
}
slen = clen = sizeof(struct sockaddr_in);
bzero(&servaddr, slen);
//這裏讓 fastcgi程序監聽 127.0.0.1:9000 和 php-fpm 監聽的地址相同, 方便我們用 nginx 來測試
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9000);
servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ret = bind(servfd, (struct sockaddr *)&servaddr, slen);
if (ret == -1)
{
haltError("bind", errno);
}
ret = listen(servfd, 16);
if (ret == -1)
{
haltError("listen", errno);
}
while (1)
{
bzero(&cliaddr, clen);
connfd = accept(servfd, (struct sockaddr *)&cliaddr, &clen);
if (connfd == -1)
{
haltError("accept", errno);
break;
}
fcntl(connfd, F_SETFL, O_NONBLOCK); // 設置socket爲非阻塞
init_paramNV(¶mNV);
while (1) {
//讀取消息頭
bzero(&header, HEAD_LEN);
rdlen = read(connfd, &header, HEAD_LEN);
if (rdlen == -1)
{
// 無數據可讀
if (errno == EAGAIN)
{
break;
}
else
{
haltError("read", errno);
}
}
if (rdlen == 0)
{
break; //消息讀取結束
}
headerBuf = header;
requestId = (header.requestIdB1 << 8) + header.requestIdB0;
contentLen = (header.contentLengthB1 << 8) + header.contentLengthB0;
paddingLen = header.paddingLength;
printf("version = %d, type = %d, requestId = %d, contentLen = %d, paddingLength = %d\n",
header.version, header.type, requestId, contentLen, paddingLen);
printf("%lx\n", header);
switch (header.type) {
case FCGI_BEGIN_REQUEST:
printf("******************************* begin request *******************************\n");
//讀取開始請求的請求體
bzero(&brBody, sizeof(brBody));
read(connfd, &brBody, sizeof(brBody));
printf("role = %d, flags = %d\n", (brBody.roleB1 << 8) + brBody.roleB0, brBody.flags);
break;
case FCGI_PARAMS:
printf("begin read params...\n");
// 消息頭中的contentLen = 0 表明此類消息已發送完畢
if (contentLen == 0)
{
printf("read params end...\n");
}
//循環讀取鍵值對
while (contentLen > 0)
{
/*
FCGI_PARAMS 以鍵值對的方式傳送,鍵和值之間沒有'=',每個鍵值對之前會分別用1或4個字節來標識鍵和值的長度 例如:
\x0B\x02SERVER_PORT80\x0B\x0ESERVER_ADDR199.170.183.42
上面的長度是用十六進制表示的 \x0B = 11 正好爲SERVER_PORT的長度, \x02 = 2 爲80的長度
*/
// 獲取paramName的長度
rdlen = read(connfd, &c, 1); //先讀取一個字節,這個字節標識 paramName 的長度
contentLen -= rdlen;
if ((c & 0x80) != 0) //如果 c 的值大於 128,則該 paramName 的長度用四個字節表示
{
rdlen = read(connfd, lenbuf, 3);
contentLen -= rdlen;
paramNameLen = ((c & 0x7f) << 24) + (lenbuf[0] << 16) + (lenbuf[1] << 8) + lenbuf[2];
} else
{
paramNameLen = c;
}
// 同樣的方式獲取paramValue的長度
rdlen = read(connfd, &c, 1);
contentLen -= rdlen;
if ((c & 0x80) != 0)
{
rdlen = read(connfd, lenbuf, 3);
contentLen -= rdlen;
paramValueLen = ((c & 0x7f) << 24) + (lenbuf[0] << 16) + (lenbuf[1] << 8) + lenbuf[2];
}
else
{
paramValueLen = c;
}
//讀取paramName
paramName = (char *)calloc(paramNameLen + 1, sizeof(char));
rdlen = read(connfd, paramName, paramNameLen);
contentLen -= rdlen;
//讀取paramValue
paramValue = (char *)calloc(paramValueLen + 1, sizeof(char));
rdlen = read(connfd, paramValue, paramValueLen);
contentLen -= rdlen;
printf("read param: %s=%s\n", paramName, paramValue);
if (paramNV.curLen == paramNV.maxLen)
{
// 如果鍵值結構體已滿則把容量擴充一倍
extend_paramNV(¶mNV);
}
paramNV.pname[paramNV.curLen] = paramName;
paramNV.pvalue[paramNV.curLen] = paramValue;
paramNV.curLen++;
}
if (paddingLen > 0)
{
rdlen = read(connfd, buf, paddingLen);
contentLen -= rdlen;
}
break;
case FCGI_STDIN:
printf("begin read post...\n");
if(contentLen == 0)
{
printf("read post end....\n");
}
if (contentLen > 0)
{
while (contentLen > 0)
{
if (contentLen > BUFLEN)
{
rdlen = read(connfd, buf, BUFLEN);
}
else
{
rdlen = read(connfd, buf, contentLen);
}
contentLen -= rdlen;
fwrite(buf, sizeof(char), rdlen, stdout);
}
printf("\n");
}
if (paddingLen > 0)
{
rdlen = read(connfd, buf, paddingLen);
contentLen -= rdlen;
}
break;
case FCGI_DATA:
printf("begin read data....\n");
if (contentLen > 0)
{
while (contentLen > 0)
{
if (contentLen > BUFLEN)
{
rdlen = read(connfd, buf, BUFLEN);
}
else
{
rdlen = read(connfd, buf, contentLen);
}
contentLen -= rdlen;
fwrite(buf, sizeof(char), rdlen, stdout);
}
printf("\n");
}
if (paddingLen > 0)
{
rdlen = read(connfd, buf, paddingLen);
contentLen -= rdlen;
}
break;
}
}
/* 以上是從web服務器讀取數據,下面向web服務器返回數據 */
headerBuf.version = FCGI_VERSION_1;
headerBuf.type = FCGI_STDOUT;
htmlHead = "Content-type: text/html\r\n\r\n"; //響應頭
htmlBody = getParamValue(¶mNV, "SCRIPT_FILENAME"); // 把請求文件路徑作爲響應體返回
printf("html: %s%s\n",htmlHead, htmlBody);
contentLen = strlen(htmlHead) + strlen(htmlBody);
headerBuf.contentLengthB1 = (contentLen >> 8) & 0xff;
headerBuf.contentLengthB0 = contentLen & 0xff;
headerBuf.paddingLength = (contentLen % 8) > 0 ? 8 - (contentLen % 8) : 0; // 讓數據 8 字節對齊
write(connfd, &headerBuf, HEAD_LEN);
write(connfd, htmlHead, strlen(htmlHead));
write(connfd, htmlBody, strlen(htmlBody));
if (headerBuf.paddingLength > 0)
{
write(connfd, buf, headerBuf.paddingLength); //填充數據隨便寫什麼,數據會被服務器忽略
}
free_paramNV(¶mNV);
//回寫一個空的 FCGI_STDOUT 表明 該類型消息已發送結束
headerBuf.type = FCGI_STDOUT;
headerBuf.contentLengthB1 = 0;
headerBuf.contentLengthB0 = 0;
headerBuf.paddingLength = 0;
write(connfd, &headerBuf, HEAD_LEN);
// 發送結束請求消息頭
headerBuf.type = FCGI_END_REQUEST;
headerBuf.contentLengthB1 = 0;
headerBuf.contentLengthB0 = 8;
headerBuf.paddingLength = 0;
bzero(&erBody, sizeof(erBody));
erBody.protocolStatus = FCGI_REQUEST_COMPLETE;
write(connfd, &headerBuf, HEAD_LEN);
write(connfd, &erBody, sizeof(erBody));
close(connfd);
printf("******************************* end request *******************************\n");
}
close(servfd);
return 0;
}
運行結果:
先運行FastCGI程序,爲了方便測試程序監聽和php-fpm相同的端口,所以啓程序前先關閉php-fpm
[root@localhost fastcgi-demo]# make
gcc fastcgi.c -o fastcgi
[root@localhost fastcgi-demo]# ls
fastcgi fastcgi.c Makefile README.md
[root@localhost fastcgi-demo]# ./fastcgi
然後請求一個php頁面:
[root@localhost hello]# curl -i '127.0.0.1/test.php?user=Tom&password=123456' -d 'gender=male&weight=60kg'
HTTP/1.1 200 OK
Server: nginx/1.11.3
Date: Wed, 27 Dec 2017 12:16:38 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
/usr/share/nginx/html/test.php[root@localhost hello]#
然後可以看到FastCGI程序的打印結果:
[root@localhost fastcgi-demo]# ./fastcgi
version = 1, type = 1, requestId = 1, contentLen = 8, paddingLength = 0
80001000101
******************************* begin request *******************************
role = 1, flags = 0
version = 1, type = 4, requestId = 1, contentLen = 717, paddingLength = 3
3cd0201000401
begin read params...
read param: SCRIPT_FILENAME=/usr/share/nginx/html/test.php
read param: QUERY_STRING=user=Tom&password=123456
read param: REQUEST_METHOD=POST
read param: CONTENT_TYPE=application/x-www-form-urlencoded
read param: CONTENT_LENGTH=23
read param: SCRIPT_NAME=/test.php
read param: REQUEST_URI=/test.php?user=Tom&password=123456
read param: DOCUMENT_URI=/test.php
read param: DOCUMENT_ROOT=/usr/share/nginx/html
read param: SERVER_PROTOCOL=HTTP/1.1
read param: GATEWAY_INTERFACE=CGI/1.1
read param: SERVER_SOFTWARE=nginx/1.11.3
read param: REMOTE_ADDR=127.0.0.1
read param: REMOTE_PORT=56896
read param: SERVER_ADDR=127.0.0.1
read param: SERVER_PORT=80
read param: SERVER_NAME=_
read param: REDIRECT_STATUS=200
read param: HTTP_USER_AGENT=curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
read param: HTTP_HOST=127.0.0.1
read param: HTTP_ACCEPT=*/*
read param: HTTP_CONTENT_LENGTH=23
read param: HTTP_CONTENT_TYPE=application/x-www-form-urlencoded
version = 1, type = 4, requestId = 1, contentLen = 0, paddingLength = 0
1000401
begin read params...
read params end...
version = 1, type = 5, requestId = 1, contentLen = 23, paddingLength = 1
1170001000501
begin read post...
gender=male&weight=60kg
version = 1, type = 5, requestId = 1, contentLen = 0, paddingLength = 0
1000501
begin read post...
read post end....
html: Content-type: text/html
/usr/share/nginx/html/test.php
******************************* end request *******************************
寫在後面:大家都知道 php-fpm 實現了fastcgi協議,但php-fpm所做的事遠不止於此,他還負責 進程管理(fastcgi進程數控制,重啓down調的fastcgi子進程等等),初始化 php 運行環境,以及執行 php 腳本,php-fpm的實現原理值得另寫一篇文章來介紹它。
That’s all!