FastCGI規範

工程師WWW發表於2016-11-07
介紹

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_STDERRFCGI_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_DA他的讀取。 
在傳送其所有的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}}

相關文章