?
http://fuzhong1983.blog.163.com/blog/static/1684705201051002951763/
.?介紹
FastCGI是對CGI的開放的擴展,它為所有因特網應用提供高性能,且沒有Web服務器API的缺點(penalty)。
本規范具有有限的(narrow)目標:從應用的視角規定FastCGI應用和支持FastCGI的Web服務器之間的接口。Web服務器的很多特性涉及FastCGI,舉例來說,應用管理設施與應用到Web服務器的接口無關,因此不在這兒描述。
本規范適用于Unix(更確切地說,適用于支持伯克利socket的POSIX系統)。本規范大半是簡單的通信協議,與字節序無關,并且將擴展到其他系統。
我們將通過與CGI/1.1的常規Unix實現的比較來介紹FastCGI。FastCGI被設計用來支持常駐(long-lived)應用進程,也就是應用服務器。那是與CGI/1.1的常規Unix實現的主要區別,后者構造應用進程,用它響應一個請求,以及讓它退出。
FastCGI進程的初始狀態比CGI/1.1進程的初始狀態更簡潔,因為FastCGI進程開始不會連接任何東西。它沒有常規的打開的文件stdin、stdout和stderr,而且它不會通過環境變量接收大量的信息。FastCGI進程的初始狀態的關鍵部分是個正在監聽的socket,通過它來接收來自Web服務器的連接。
FastCGI進程在其正在監聽的socket上收到一個連接之后,進程執行簡單的協議來接收和發送數據。協議服務于兩個目的。首先,協議在多個獨立的?FastCGI請求間多路復用單個傳輸線路。這可支持能夠利用事件驅動或多線程編程技術處理并發請求的應用。第二,在每個請求內部,協議在每個方向上提供若干獨立的數據流。這種方式,例如,stdout和stderr數據通過從應用到Web服務器的單個傳輸線路傳遞,而不是像CGI/1.1那樣需要獨立的管道。
一個FastCGI應用扮演幾個明確定義的角色中的一個。最常用的是響應器(Responder)角色,其中應用接收所有與HTTP請求相關的信息,并產生一個HTTP響應;那是CGI/1.1程序扮演的角色。第二個角色是認證器(Authorizer),其中應用接收所有與HTTP請求相關的信息,并產生一個認可/未經認可的判定。第三個角色是過濾器(Filter),其中應用接收所有與HTTP請求相關的信息,以及額外的來自存儲在Web服務器上的文件的數據流,并產生"已過濾"版的數據流作為HTTP響應。框架是易擴展的,因而以后可定義更多的FastCGI。
在本規范的其余部分,只要不致引起混淆,術語"FastCGI應用"、"應用進程"或"應用服務器"簡寫為"應用"。
2.?初始進程狀態?2.1?參數表
Web服務器缺省創建一個含有單個元素的參數表,該元素是應用的名字,用作可執行路徑名的最后一部分。Web服務器可提供某種方式來指定不同的應用名,或更詳細的參數表。
注意,被Web服務器執行的文件可能是解釋程序文件(以字符#!開頭的文本文件),此情形中的應用參數表的構造在execve man頁中描述。
2.2?文件描述符
當應用開始執行時,Web服務器留下一個打開的文件描述符,FCGI_LISTENSOCK_FILENO。該描述符引用Web服務器創建的一個正在監聽的socket。
FCGI_LISTENSOCK_FILENO等于STDIN_FILENO。當應用開始執行時,標準的描述符STDOUT_FILENO和STDERR_FILENO被關閉。一個用于應用確定它是用CGI調用的還是用FastCGI調用的可靠方法是調用getpeername(FCGI_LISTENSOCK_FILENO),對于FastCGI應用,它返回-1,并設置errno為ENOTCONN。
Web服務器對于可靠傳輸的選擇,Unix流式管道(AF_UNIX)或TCP/IP(AF_INET),是內含于FCGI_LISTENSOCK_FILENO socket的內部狀態中的。
2.3?環境變量
Web服務器可用環境變量向應用傳參數。本規范定義了一個這樣的變量,FCGI_WEB_SERVER_ADDRS;我們期望隨著規范的發展定義更多。Web服務器可提供某種方式綁定其他環境變量,例如PATH變量。
2.4?其他狀態
Web服務器可提供某種方式指定應用的初始進程狀態的其他組件,例如進程的優先級、用戶ID、組ID、根目錄和工作目錄。
3.?協議基礎?3.1?符號(Notation)
我們用C語言符號來定義協議消息格式。所有的結構元素按照unsigned char類型定義和排列,這樣ISO C編譯器以明確的方式將它們展開,不帶填充。結構中定義的第一字節第一個被傳送,第二字節排第二個,依次類推。
我們用兩個約定來簡化我們的定義。
首先,當兩個相鄰的結構組件除了后綴“B1”和“B0”之外命名相同時,它表示這兩個組件可視為估值為B1<<8 + B0的單個數字。該單個數字的名字是這些組件減去后綴的名字。這個約定歸納了一個由超過兩個字節表示的數字的處理方式。
第二,我們擴展C結構(struct)來允許形式
struct {
unsigned char mumbleLengthB1;
unsigned char mumbleLengthB0;
... /*?其他東西?*/
unsigned char mumbleData[mumbleLength];
};
表示一個變長結構,此處組件的長度由較早的一個或多個組件指示的值確定。
3.2?接受傳輸線路
FastCGI應用在文件描述符FCGI_LISTENSOCK_FILENO引用的socket上調用accept()來接收新的傳輸線路。如果accept()成功,而且也綁定了FCGI_WEB_SERVER_ADDRS環境變量,則應用立刻執行下列特殊處理:
FCGI_WEB_SERVER_ADDRS:值是一列有效的用于Web服務器的IP地址。
如果綁定了FCGI_WEB_SERVER_ADDRS,應用校驗新線路的同級IP地址是否列表中的成員。如果校驗失敗(包括線路不是用TCP/IP傳輸的可能性),應用關閉線路作為響應。
FCGI_WEB_SERVER_ADDRS被表示成逗號分隔的IP地址列表。每個IP地址寫成四個由小數點分隔的在區間[0..255]中的十進制數。所以該變量的一個合法綁定是FCGI_WEB_SERVER_ADDRS=199.170.183.28,199.170.183.71。
?
應用可接受若干個并行傳輸線路,但不是必須的。
3.3?記錄
應用利用簡單的協議執行來自Web服務器的請求。協議細節依賴應用的角色,但是大致說來,Web服務器首先發送參數和其他數據到應用,然后應用發送結果數據到Web服務器,最后應用向Web服務器發送一個請求完成的指示。
通過傳輸線路流動的所有數據在FastCGI記錄中運載。FastCGI記錄實現兩件事。首先,記錄在多個獨立的FastCGI請求間多路復用傳輸線路。該多路復用技術支持能夠利用事件驅動或多線程編程技術處理并發請求的應用。第二,在單個請求內部,記錄在每個方向上提供若干獨立的數據流。這種方式,例如,stdout和stderr數據能通過從應用到Web服務器的單個傳輸線路傳遞,而不需要獨立的管道。
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;
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;
FastCGI記錄由一個定長前綴后跟可變數量的內容和填充字節組成。記錄包含七個組件:
version:?標識FastCGI協議版本。本規范評述(document)FCGI_VERSION_1。?
type:?標識FastCGI記錄類型,也就是記錄執行的一般職能。特定記錄類型和它們的功能在后面部分詳細說明。?
requestId:?標識記錄所屬的FastCGI請求。?
contentLength:?記錄的contentData組件的字節數。?
paddingLength:?記錄的paddingData組件的字節數。?
contentData:?在0和65535字節之間的數據,依據記錄類型進行解釋。?
paddingData:?在0和255字節之間的數據,被忽略。
我們用不嚴格的C結構初始化語法來指定常量FastCGI記錄。我們省略version組件,忽略填充(Padding),并且把requestId視為數字。因而{FCGI_END_REQUEST, 1, {FCGI_REQUEST_COMPLETE,0}}是個type == FCGI_END_REQUEST、requestId == 1且contentData == {FCGI_REQUEST_COMPLETE,0}的記錄。
填充(Padding)
協議允許發送者填充它們發送的記錄,并且要求接受者解釋paddingLength并跳過paddingData。填充允許發送者為更有效地處理保持對齊的數據。X窗口系統協議上的經驗顯示了這種對齊方式的性能優勢。
我們建議記錄被放置在八字節倍數的邊界上。FCGI_Record的定長部分是八字節。
管理請求ID
Web服務器重用FastCGI請求ID;應用明了給定傳輸線路上的每個請求ID的當前狀態。當應用收到一個記錄{FCGI_BEGIN_REQUEST, R, ...}時,請求ID R變成有效的,而且當應用向Web服務器發送記錄{FCGI_END_REQUEST, R, ...}時變成無效的。
當請求ID R無效時,應用會忽略requestId == R的記錄,除了剛才描述的FCGI_BEGIN_REQUEST記錄。
Web服務器嘗試保持小的FastCGI請求ID。那種方式下應用能利用短數組而不是長數組或哈希表來明了請求ID的狀態。應用也有每次接受一個請求的選項。這種情形下,應用只是針對當前的請求ID檢查輸入的requestId值。
記錄類型的類型
有兩種有用的分類FastCGI記錄類型的方式。
第一個區別在管理(management)記錄和應用(application)記錄之間。管理記錄包含不特定于任何Web服務器請求的信息,例如關于應用的協議容量的信息。應用記錄包含關于特定請求的信息,由requestId組件標識。
管理記錄有0值的requestId,也稱為null請求ID。應用記錄有非0的requestId。
第二個區別在離散和連續記錄之間。一個離散記錄包含一個自己的所有數據的有意義的單元。一個流記錄是stream的部分,也就是一連串流類型的0或更多非空記錄(length != 0),后跟一個流類型的空記錄(length == 0)。當連接流記錄的多個contentData組件時,形成一個字節序列;該字節序列是流的值。因此流的值獨立于它包含多少個記錄或它的字節如何在非空記錄間分配。
這兩種分類是獨立的。在本版的FastCGI協議定義的記錄類型中,所有管理記錄類型也是離散記錄類型,而且幾乎所有應用記錄類型都是流記錄類型。但是三種應用記錄類型是離散的,而且沒有什么能防止在某些以后的協議版本中定義一個流式的管理記錄類型。
3.4?名-值對
FastCGI應用的很多角色需要讀寫可變數量的可變長度的值。所以為編碼名-值對提供標準格式很有用。
FastCGI以名字長度,后跟值的長度,后跟名字,后跟值的形式傳送名-值對。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;
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 << 8) + B0];
} FCGI_NameValuePair14;
typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 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 << 8) + B0];
unsigned char valueData[valueLength];
} FCGI_NameValuePair41;
typedef struct {
unsigned char nameLengthB3; /* nameLengthB3 >> 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意味著四字節的編碼方式。
名-值對格式允許發送者不用額外的編碼方式就能傳輸二進制值,并且允許接收者立刻分配正確數量的內存,即使對于巨大的值。
3.5?關閉傳輸線路
Web服務器控制傳輸線路的生存期。當沒有活動的請求時Web服務器能關閉線路。或者Web服務器也能把關閉的職權委托給應用(見FCGI_BEGIN_REQUEST)。該情形下,應用在指定的請求結束時關閉線路。
這種靈活性提供了多種應用風格。簡單的應用會一次處理一個請求,并且為每個請求接受一個新的傳輸線路。更復雜的應用會通過一個或多個傳輸線路處理并發的請求,而且會長期保持傳輸線路為打開狀態。
簡單的應用通過在寫入響應結束后關閉傳輸線路可得到重大的性能提升。Web服務器需要控制常駐線路的生命期。
當應用關閉一個線路或發現一個線路關閉了,它就初始化一個新線路。
4.?管理(Management)記錄類型?4.1 FCGI_GET_VALUES, FCGI_GET_VALUES_RESULT
Web服務器能查詢應用內部的具體的變量。典型地,服務器會在應用啟動上執行查詢以使系統配置的某些方面自動化。
應用把收到的查詢作為記錄{FCGI_GET_VALUES, 0, ...}。FCGI_GET_VALUES記錄的contentData部分包含一系列值為空的名-值對。
應用通過發送補充了值的{FCGI_GET_VALUES_RESULT, 0, ...}記錄來響應。如果應用不理解查詢中包含的一個變量名,它從響應中忽略那個名字。
FCGI_GET_VALUES被設計為允許可擴充的變量集。初始集提供信息來幫助服務器執行應用和線路的管理:
FCGI_MAX_CONNS:該應用將接受的并發傳輸線路的最大值,例如"1"或"10"。?
FCGI_MAX_REQS:該應用將接受的并發請求的最大值,例如"1"或"50"。?
FCGI_MPXS_CONNS:如果應用不多路復用線路(也就是通過每個線路處理并發請求)則為?"0",其他則為"1"。
應用可在任何時候收到FCGI_GET_VALUES記錄。除了FastCGI庫,應用的響應不能涉及應用固有的庫。
4.2 FCGI_UNKNOWN_TYPE
在本協議的未來版本中,管理記錄類型集可能會增長。為了這種演變作準備,協議包含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組件是無法識別的管理記錄的類型。
5.?應用(Application)記錄類型?5.1 FCGI_BEGIN_REQUEST
Web服務器發送FCGI_BEGIN_REQUEST記錄開始一個請求。
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
角色在下面的第6章中作更詳細地描述。
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和FCGI_STDERR都是流記錄類型,分別用于從應用向Web服務器發送任意數據和錯誤數據。
5.4 FCGI_ABORT_REQUEST
Web服務器發送FCGI_ABORT_REQUEST記錄來中止請求。收到{FCGI_ABORT_REQUEST, R}后,應用盡快用{FCGI_END_REQUEST, R, {FCGI_REQUEST_COMPLETE, appStatus}}響應。這是真實的來自應用的響應,而不是來自FastCGI庫的低級確認。
當HTTP客戶端關閉了它的傳輸線路,可是受客戶端委托的FastCGI請求仍在運行時,Web服務器中止該FastCGI請求。這種情況看似不太可能;多數FastCGI請求具有很短的響應時間,同時如果客戶端很慢則Web服務器提供輸出緩沖。但是FastCGI應用與其他系統的通信或執行服務器端進棧可能被延期。
當不是通過一個傳輸線路多路復用請求時,Web服務器能通過關閉請求的傳輸線路來中止請求。但使用多路復用請求時,關閉傳輸線路具有不幸的結果,中止線路上的所有請求。
5.5 FCGI_END_REQUEST
不論已經處理了請求,還是已經拒絕了請求,應用發送FCGI_END_REQUEST記錄來終止請求。
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組件是應用級別的狀態碼。每種角色說明其appStatus的用法。
protocolStatus組件是協議級別的狀態碼;可能的protocolStatus值是:
FCGI_REQUEST_COMPLETE:請求的正常結束。?
FCGI_CANT_MPX_CONN:拒絕新請求。這發生在Web服務器通過一條線路向應用發送并發的請求時,后者被設計為每條線路每次處理一個請求。?
FCGI_OVERLOADED:拒絕新請求。這發生在應用用完某些資源時,例如數據庫連接。?
FCGI_UNKNOWN_ROLE:拒絕新請求。這發生在Web服務器指定了一個應用不能識別的角色時。
6.?角色?6.1?角色協議
角色協議只包括帶應用記錄類型的記錄。它們本質上利用流傳輸所有數據。
為了讓協議可靠以及簡化應用編程,角色協議被設計使用近似順序編組(nearly sequential marshalling)。在嚴格順序編組的協議中,應用接收其第一個輸入,然后是第二個,依次類推。直到收到全部。同樣地,應用發送其第一個輸出,然后是第二個,依次類推。直到發出全部。輸入不是相互交叉的,輸出也不是。
對于某些FastCGI角色,順序編組規則有太多限制,因為CGI程序能不受時限地(timing restriction)寫入stdout和stderr。所以用到了FCGI_STDOUT和FCGI_STDERR的角色協議允許交叉這兩個流。
所有角色協議使用FCGI_STDERR流的方式恰是stderr在傳統的應用編程中的使用方式:以易理解的方式報告應用級錯誤。FCGI_STDERR流的使用總是可選的。如果沒有錯誤要報告,應用要么不發送FCGI_STDERR記錄,要么發送一個0長度的FCGI_STDERR記錄。
當角色協議要求傳輸不同于FCGI_STDERR的流時,總是至少傳輸一個流類型的記錄,即使流是空的。
再次關注可靠的協議和簡化的應用編程技術,角色協議被設計為近似請求-響應。在真正的請求-響應協議中,應用在發送其輸出記錄前接收其所有的輸入記錄。請求-響應協議不允許流水線技術(pipelining)。
對于某些FastCGI角色,請求響應規則約束太強;畢竟,CGI程序不限于在開始寫stdout前讀取全部stdin。所以某些角色協議允許特定的可能性。首先,除了結尾的流輸入,應用接收其所有輸入。當開始接收結尾的流輸入時,應用開始寫其輸出。
當角色協議用FCGI_PARAMS傳輸文本值時,例如CGI程序從環境變量得到的值,其長度不包括結尾的null字節,而且它本身不包含null字節。需要提供environ(7)格式的名-值對的應用必須在名和值間插入等號,并在值后添加null字節。
角色協議不支持CGI的未解析的(non-parsed)報頭特性。FastCGI應用使用CGI報頭Status和Location設置響應狀態。
6.2?響應器(Responder)
作為響應器的FastCGI應用具有同CGI/1.1一樣的目的:它接收與HTTP請求關聯的所有信息并產生HTTP響應。
它足以解釋怎樣用響應器模擬CGI/1.1的每個元素:
響應器應用通過FCGI_PARAMS接收來自Web服務器的CGI/1.1環境變量。?
接下來響應器應用通過FCGI_STDIN接收來自Web服務器的CGI/1.1 stdin數據。在收到流尾指示前,應用從該流接收最多CONTENT_LENGTH字節。(只當HTTP客戶端未能提供時,例如因為客戶端崩潰了,應用才收到少于CONTENT_LENGTH的字節。)?
響應器應用通過FCGI_STDOUT向Web服務器發送CGI/1.1 stdout數據,以及通過FCGI_STDERR發送CGI/1.1 stderr數據。應用同時發送這些,而非一個接一個。在開始寫FCGI_STDOUT和FCGI_STDERR前,應用必須等待讀取FCGI_PARAMS完成,但是不需要在開始寫這兩個流前完成從FCGI_STDIN讀取。?
在發送其所有stdout和stderr數據后,響應器應用發送FCGI_END_REQUEST記錄。應用設置protocolStatus組件為FCGI_REQUEST_COMPLETE,并設置appStatus組件為CGI程序通過exit系統調用返回的狀態碼。
響應器執行更新,例如實現POST方法,應該比較在FCGI_STDIN上收到的字節數和CONTENT_LENGTH,并且如果兩數不等則中止更新。
6.3?認證器(Authorizer)
作為認證器的FastCGI應用接收所有與HTTP請求相關的信息,并產生一個認可/未經認可的判定。對于認可的判定,認證器也能把名-值對同HTTP請求相關聯;當給出未經認可的判定時,認證器向HTTP客戶端發送結束響應。
由于CGI/1.1定義了與HTTP請求相關聯的信息的極好的表示方式,認證器使用同樣的表示法:
認證器應用在FCGI_PARAMS流上接收來自Web服務器的HTTP信息,格式同響應器一樣。Web服務器不會發送報頭CONTENT_LENGTH、PATH_INFO、PATH_TRANSLATED和SCRIPT_NAME。?
認證器應用以同響應器一樣的方式發送stdout和stderr數據。CGI/1.1響應狀態指定對結果的處理。如果應用發送狀態200(OK),Web服務器允許訪問。 依賴于其配置,Web服務器可繼續進行其他的訪問檢查,包括對其他認證器的請求。
認證器應用的200響應可包含以Variable-為名字前綴的報頭。這些報頭從應用向Web服務器傳送名-值對。例如,響應報頭
Variable-AUTH_METHOD: database lookup
傳輸名為AUTH-METHOD的值"database lookup"。服務器把這樣的名-值對同HTTP請求相關聯,并且把它們包含在后續的CGI或FastCGI請求中,這些請求在處理HTTP請求的過程中執行。當應用給出200響應時,服務器忽略名字不以Variable-為前綴的響應報頭,并且忽略任何響應內容。
對于“200”(OK)以外的認證器響應狀態值,Web服務器拒絕訪問并將響應狀態、報頭和內容發回HTTP客戶端。
6.4?過濾器(Filter)
作為過濾器的FastCGI應用接收所有與HTTP請求相關聯的信息,以及額外的來自存儲在Web服務器上的文件的數據流,并產生數據流的“已過濾”版本作為HTTP響應。
過濾器在功能上類似響應器,接受一個數據文件作為參數。區別是,過濾器使得數據文件和過濾器本身都能用Web服務器的訪問控制機制進行訪問控制,而響應器接受數據文件名作為參數,必須在數據文件上執行自己的訪問控制檢查。
過濾器采取的步驟與響應器的相似。服務器首先提供環境變量,然后是標準輸入(常規形式的POST數據),最后是數據文件輸入:
如同響應器,過濾器應用通過FCGI_PARAMS接收來自Web服務器的名-值對。過濾器應用接收兩個過濾器特定的變量:FCGI_DATA_LAST_MOD和FCGI_DATA_LENGTH。?
接下來,過濾器應用通過FCGI_STDIN接收來自Web服務器的CGI/1.1 stdin數據。在收到流尾指示以前,應用從該流接收最多CONTENT_LENGTH字節。(只有HTTP客戶端未能提供時,應用收到的才少于CONTENT_LENGTH字節,例如因為客戶端崩潰了。)?
下一步,過濾器應用通過FCGI_DATA接收來自Web服務器的文件數據。該文件的最后修改時間(表示成自UTC?1970年1月1日以來的整秒數)是FCGI_DATA_LAST_MOD;應用可能查閱該變量并從緩存作出響應,而不讀取文件數據。在收到流尾指示以前,應用從該流接收最多FCGI_DATA_LENGTH字節。?
過濾器應用通過FCGI_STDOUT向Web服務器發送CGI/1.1 stdout數據,以及通過FCGI_STDERR的CGI/1.1 stderr數據。應用同時發送這些,而非相繼地。在開始寫入FCGI_STDOUT和FCGI_STDERR以前,應用必須等待讀取FCGI_STDIN完成,但是不需要在開始寫入這兩個流以前完成從FCGI_DATA的讀取。?
在發送其所有的stdout和stderr數據之后,應用發送FCGI_END_REQUEST記錄。應用設定protocolStatus組件為FCGI_REQUEST_COMPLETE,以及appStatus組件為類似的CGI程序通過exit系統調用返回的狀態代碼。
過濾器應當把在FCGI_STDIN上收到的字節數同CONTENT_LENGTH比較,以及把FCGI_DATA上的同FCGI_DATA_LENGTH比較。如果數字不匹配且過濾器是個查詢,過濾器響應應當提供數據丟失的指示。如果數字不匹配且過濾器是個更新,過濾器應當中止更新。
7.?錯誤
FastCGI應用以0狀態退出來指出它故意結束了,例如,為了執行原始形式的垃圾收集。FastCGI應用以非0狀態退出被假定為崩潰了。以0或非0狀態退出的Web服務器或其他的應用管理器如何響應應用超出了本規范的范圍。
Web服務器能通過向FastCGI應用發送SIGTERM來要求它退出。如果應用忽略SIGTERM,Web服務器能采用SIGKILL。
FastCGI應用使用FCGI_STDERR流和FCGI_END_REQUEST記錄的appStatus組件報告應用級別錯誤。在很多情形中,錯誤會通過FCGI_STDOUT流直接報告給用戶。
在Unix上,應用向syslog報告低級錯誤,包括FastCGI協議錯誤和FastCGI環境變量中的語法錯誤。依賴于錯誤的嚴重性,應用可能繼續或以非0狀態退出。
8.?類型和常量?/*
*?正在監聽的socket文件編號
*/
#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;
/*
* FCGI_Header中的字節數。協議的未來版本不會減少該數。
*/
#define FCGI_HEADER_LEN 8
/*
*?可用于FCGI_Header的version組件的值
*/
#define FCGI_VERSION_1 1
/*
*?可用于FCGI_Header的type組件的值
*/
#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)
/*
*?可用于FCGI_Header的requestId組件的值
*/
#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;
/*
*?可用于FCGI_BeginRequestBody的flags組件的掩碼
*/
#define FCGI_KEEP_CONN 1
/*
*?可用于FCGI_BeginRequestBody的role組件的值
*/
#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;
/*
*?可用于FCGI_EndRequestBody的protocolStatus組件的值
*/
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/*
*?可用于FCGI_GET_VALUES/FCGI_GET_VALUES_RESULT記錄的變量名
*/
#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;
9.?參考
National?Center?for Supercomputer Applications,?The Common Gateway Interface, version CGI/1.1.
D.R.T. Robinson,?The WWW Common Gateway Interface Version 1.1, Internet-Draft, 15 February 1996.
A.?表:記錄類型的屬性
下面的圖表列出了所有記錄類型,并指出各自的這些屬性:
WS->App:該類型的記錄只能由Web服務器發送到應用。其他類型的記錄只能由應用發送到Web服務器。?
management:該類型的記錄含有非特定于某個Web服務器請求的信息,而且使用null請求ID。其他類型的記錄含有請求特定的信息,而且不能使用null請求ID。?
stream:該類型的記錄組成一個由帶有空contentData的記錄結束的流。其他類型的記錄是離散的;各自攜帶一個有意義的數據單元。
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.?典型的協議消息流程
用于示例的補充符號約定:
流記錄的contentData(FCGI_PARAMS、FCGI_STDIN、FCGI_STDOUT和FCGI_STDERR)被描述成一個字符串。以" ... "結束的字符串是太長而無法顯示的,所以只顯示前綴。?
發送到Web服務器的消息相對于收自Web服務器的消息縮進排版。?
消息以應用經歷的時間順序顯示。
1.?在stdin上不帶數據的簡單請求,以及成功的響應:
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{FCGI_PARAMS, 1, ""}
{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}}
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<html>\n<head> ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}
3.?類似例1,但這次應用發現了錯誤。應用把一條消息記錄到stderr,向客戶端返回一個頁面,并且向Web服務器返回非0退出狀態。應用選擇用更多FCGI_STDOUT記錄發送頁面:
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, 0}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{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<head> ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_STDERR, 1, ""}
{FCGI_END_REQUEST, 1, {938, FCGI_REQUEST_COMPLETE}}
4.?在單條線路上多路復用的兩個例1實例。第一個請求比第二個難,所以應用顛倒次序完成這些請求:
{FCGI_BEGIN_REQUEST, 1, {FCGI_RESPONDER, FCGI_KEEP_CONN}}
{FCGI_PARAMS, 1, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{FCGI_PARAMS, 1, ""}
{FCGI_BEGIN_REQUEST, 2, {FCGI_RESPONDER, FCGI_KEEP_CONN}}
{FCGI_PARAMS, 2, "\013\002SERVER_PORT80\013\016SERVER_ADDR199.170.183.42 ... "}
{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<html>\n<head> ... "}
{FCGI_STDOUT, 2, ""}
{FCGI_END_REQUEST, 2, {0, FCGI_REQUEST_COMPLETE}}
{FCGI_STDOUT, 1, "<html>\n<head> ... "}
{FCGI_STDOUT, 1, ""}
{FCGI_END_REQUEST, 1, {0, FCGI_REQUEST_COMPLETE}}