Fastcgi 協議解析及 get&post 使用例項
前言:
基於:
其實看上面兩位大佬的部落格就已經ojbk
了.寫的目地主要是自己總結學習一下.
基礎:
1.基礎的 WebServer
應該支援客戶端請求靜態檔案和動態檔案.
2. 瀏覽器是不能夠解析動態的php
檔案的!那麼我們編寫伺服器程式時候如果遇到請求.php
動態檔案時就應該將php
檔案翻譯為html
檔案.
3. php-fpm
就能夠將php
檔案翻譯為html
檔案.所以我們的webserver
將通過程序間通訊把php
檔案交給php-fpm
,然後把php-fpm
翻譯過後的html
檔案發給客戶端即可,(php-fpm)就等價於一個CGI 伺服器
4.那麼我們如何才能讓
php-fpm
幫我們解析我們想要翻譯成.html檔案的.php檔案呢?通過**fastcgi
協議,其實就是WebServer
與php-fpm
之間通訊的規則(或者說是'語言')**
1. fastcgi 協議
(1) 請求頭
和’任何協議
一樣,fastcgi
協議也有一個訊息頭或者叫做請求頭.其格式是固定的.用以表示訊息體的型別和資訊.任意一個FastCGI資料包必須以一個8位元組的訊息頭開始
typedef struct
{
unsigned char version; //FCGI版本資訊,目前一般定義為1
unsigned char type; //每次傳送的訊息的型別.相當於flag,具體表示見下面:
unsigned char requestIdB1; //合起來表示本次請求的編號 ID
unsigned char requestIdB0;
unsigned char contentLengthB1; //合起來表示 body 長度
unsigned char contentLengthB0;
unsigned char paddingLength; //填充位元組長度,填充長度不可超過255位元組
unsigned char reserved; //保留位元組
} FCGI_Header; //訊息頭
type
欄位分別是如下含義:
// FCGI_Header 中 type 的具體值
#define FCGI_BEGIN_REQUEST 1 //一次請求的開始(web->fastcgi)
#define FCGI_ABORT_REQUEST 2 //異常終止一次請求(web->fastcgi)
#define FCGI_END_REQUEST 3 //請求處理完畢,正常結束(fastcgi->web)
#define FCGI_PARAMS 4 /*傳遞引數,表明訊息中包含的資料為某個name-value對
(web->fastcgi)*/
#define FCGI_STDIN 5
/*POST 內容傳遞,從瀏覽器接收到的POST請求資料(表單提交等)以訊息的形式發給php-fpm時,
這種訊息的type就得設為5(web->fastcgi)*/
#define FCGI_STDOUT 6
//正常響應內容,php-fpm給web伺服器回的正常響應訊息的type就設為6(fastcgi->web)
#define FCGI_STDERR 7
//php-fpm給web伺服器回的錯誤響應設為7(fastcgi->web)
#define FCGI_DATA 8 //向CGI程式傳遞的額外資料(WEB->FastCGI)
#define FCGI_GET_VALUES 9 // 向FastCGI程式詢問一些環境變數(WEB->FastCGI)
#define FCGI_GET_VALUES_RESULT 10 // 詢問環境變數的結果(FastCGI->WEB)
#define FCGI_UNKNOWN_TYPE 11 //通知 webserver 所請求 type 非正常型別
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) // 未知型別,可能用作拓展
requestIdB1
,requestIdB0
合起來表示本次請求的編號,其中requestIdB1
是請求編號的高八位,requestIdB0
是請求編號的低八位。這個欄位的存在允許Web伺服器在一次連線中向FastCGI伺服器傳送多個不同的請求,只要使用不同的請求編號即可
contentLengthB1`` contentLengthB0
)合起來表示訊息頭後仍有多少位元組的資料(資料長度),contentLengthB1
表示其高八位,contentLengthB0
表示其低八位。資料長度的表示範圍在0~65535(即2^16-1)之間,因而若資料超過65535位元組,則必須將之分為多個數據包來傳輸(程式設計中要注意這一點)
例項1:makeHeader函式的構造
//FCGI的版本
#define FCGI_VERSION_1 1
FCGI_Header makeHeader(int type, int requestId,
int contentLength, int paddingLength)
{
FCGI_Header header;
header.version = FCGI_VERSION_1;
header.type = (unsigned char)type;
/* 兩個欄位儲存請求ID */
header.requestIdB1 = (unsigned char)((requestId >> 8) & 0xff);
header.requestIdB0 = (unsigned char)(requestId & 0xff);
/* 兩個欄位儲存內容長度 */
header.contentLengthB1 = (unsigned char)((contentLength >> 8) & 0xff);
header.contentLengthB0 = (unsigned char)(contentLength & 0xff);
/* 填充位元組的長度 */
header.paddingLength = (unsigned char)paddingLength;
/* 儲存位元組賦為 0 */
header.reserved = 0;
return header;
}
(2) 訊息體:
類似於http
協議,在我們傳送完訊息頭之後,我們就需要傳送訊息體了.那這裡肯定還是會因為type
的不同而有所不同.
type == FCGI_BEGIN_REQUEST 1 一次請求的開始(web->fastcgi)
這種訊息是一中固定的8
位元組結構,因此我們會推出訊息頭中的contentLengthBx
在這種情況下肯定也是固定的.
typedef struct
{
unsigned char roleB1;
unsigned char roleB0;
//合起來表示 webserver 所期望php-fpm 扮演的角色,具體取值下面有
unsigned char flags; //確定 php-fpm 處理完一次請求之後是否關閉,flag=1,不關閉
unsigned char reserved[5]; //保留欄位
} FCGI_BeginRequestBody; //開始請求體
//webserver 期望 php-fpm 扮演的角色(想讓php-fpm做什麼)
#define FCGI_RESPONDER 1
//接受http關聯的所有資訊,併產生http響應,接受來自webserver的PARAMS環境變數
#define FCGI_AUTHORIZER 2
//對於認證的會關聯其http請求,未認證的則關閉請求
#define FCGI_FILTER 3
//過濾web server 中的額外資料流,併產生過濾後的http響應
總的來講,fastcgi
協議中規定了三種角色,有:
enum FCGI_Role {
FCGI_RESPONDER = 1, // 響應器,php-fpm接受我們的http所關聯的資訊,併產生響應
FCGI_AUTHORIZER = 2,
//認證器,php-fpm會對我們的請求進行認證,認證通過的其會返回響應,認證不通過則關閉請求
FCGI_FILTER = 3 // 過濾器,過濾請求中的額外資料流,併產生過濾後的http響應
};
一般,我們的webserver
就把它當作響應器就行了(也就是說把該欄位設為FCGI_RESPONDER
)
例項2:與php-fpm的連線與第一次請求
typedef struct
{
int sockfd_; //與php-fpm 建立的 sockfd
int requestId_; //record 裡的請求ID
int flag_; //用來標誌當前讀取內容是否為html內容
} FastCgi_t;
void FastCgi_init(FastCgi_t *c)
{
c->sockfd_ = 0; //與php-fpm 建立的 sockfd
c->flag_ = 0; //record 裡的請求ID
c->requestId_ = 0; //用來標誌當前讀取內容是否為html內容
}
void setRequestId(FastCgi_t *c, int requestId)
{
c->requestId_ = requestId;
}
int main()
{
FastCgi_t *c;
c = (FastCgi_t *)malloc(sizeof(FastCgi_t));
FastCgi_init(c);
setRequestId(c, 1); /*1 用來表明此訊息為請求開始的第一個訊息*/
startConnect(c); //略,就是與127.0.0.1 9000 建立了一個連線
sendStartRequestRecord(c); //主要是這個函式!!!!
sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");
sendParams(c, "REQUEST_METHOD", "POST");
...
}
sendStartRequestRecord(c)
第一次請求函式:
typedef struct
{
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags; //確定 php-fpm 處理完一次請求之後是否關閉
unsigned char reserved[5]; //保留欄位
} FCGI_BeginRequestBody; //開始請求體
typedef struct
{
FCGI_Header header; //訊息頭
FCGI_BeginRequestBody body; //開始請求體
} FCGI_BeginRequestRecord; //完整訊息--開始
FCGI_BeginRequestBody makeBeginRequestBody(int role, int keepConnection)
{
FCGI_BeginRequestBody body;
/* 兩個位元組儲存期望 php-fpm 扮演的角色 */
body.roleB1 = (unsigned char)((role >> 8) & 0xff);
body.roleB0 = (unsigned char)(role & 0xff);
/* 大於0長連線,否則短連線 */
body.flags = (unsigned char)((keepConnection) ? FCGI_KEEP_CONN : 0);
bzero(&body.reserved, sizeof(body.reserved));
return body;
}
int sendStartRequestRecord(FastCgi_t *c)
{
int rc;
FCGI_BeginRequestRecord beginRecord;
beginRecord.header =
makeHeader(FCGI_BEGIN_REQUEST, c->requestId_, sizeof(beginRecord.body), 0);
beginRecord.body = makeBeginRequestBody(FCGI_RESPONDER, 0);
rc = write(c->sockfd_, (char *)&beginRecord, sizeof(beginRecord));
assert(rc == sizeof(beginRecord));
return 1;
}
type == FCGI_END_REQUEST 3 //請求處理完畢,正常結束(fastcgi->web)
8位元組固定格式:
typedef struct
{
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
//合起來表示CGI程式的結束狀態,0為正常,此處是一個網路位元組序,需要手動轉換
unsigned char protocolStatus; //fastcgi協議狀態,如下:
unsigned char reserved[3];
} FCGI_EndRequestBody; //結束訊息體
//幾種結束狀態
#define FCGI_REQUEST_COMPLETE 0 //正常結束
#define FCGI_CANT_MPX_XONN 1 //拒絕新請求,單執行緒
#define FCGI_OVERLOADED 2 //拒絕新請求,應用負載了
#define FCGI_UNKNOWN_ROLE 3 //webserver 指定了一個應用不能識別的角色
type == FCGI_PARAMS 4 傳遞引數,表明訊息中包含的資料為某個name-value對(web->fastcgi)
向php-fpm
傳遞name-value對,可傳遞自己的,也可以傳遞fastcgi
提供的.fasttcgi
提供的name
主要有如下這些:
name名 | 含義 |
---|---|
*SCRIPT_NAME | 要執行的CGI程式的名字 |
*REQUEST_METHOD | 資訊傳輸方式(GET/POST/PUT等) |
*QUERY_STRING | 查詢字串 |
CONTENT_LENGTH | 向CGI標準輸入傳遞的資訊長度(應當等於FCGI_STDIN訊息contentLength欄位之和) |
CONTENT_TYPE | 向CGI標準輸入傳遞的資訊型別 |
其餘更多的可參考婁神的boke
type == FCGI_STDIN 5 POST 內容傳遞,從瀏覽器接收到的POST請求資料(表單提交等)以訊息的形式發給php-fpm時,這種訊息的type就得設為5(web->fastcgi)
***例項3 : 完成 post 請求
#include <stdio.h>
#include <stdlib.h>
#include "fcgi.h"
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
FastCgi_t *c;
c = (FastCgi_t *)malloc(sizeof(FastCgi_t));
FastCgi_init(c);
setRequestId(c, 1); /*1 用來表明此訊息為請求開始的第一個訊息*/
startConnect(c);
sendStartRequestRecord(c);
sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");
sendParams(c, "REQUEST_METHOD", "POST");
sendParams(c, "CONTENT_LENGTH", "17"); // 17 為body的長度 !!!!
sendParams(c, "CONTENT_TYPE", "application/x-www-form-urlencoded");
sendEndRequestRecord(c);
/*FCGI_Header makeHeader(int type, int requestId,
int contentLength, int paddingLength)*/
//設定type==5,為了發 body
FCGI_Header t = makeHeader(FCGI_STDIN, c->requestId_, 17, 0); // 17 為body的長度 !!!!
send(c->sockfd_, &t, sizeof(t), 0);
/*傳送正式的 body */
send(c->sockfd_, "a=20&b=10&c=5&d=6", 17, 0); // 17 為body的長度 !!!!
//製造頭告訴 body 結束
FCGI_Header endHeader;
endHeader = makeHeader(FCGI_STDIN, c->requestId_, 0, 0);
send(c->sockfd_, &endHeader, sizeof(endHeader), 0);
printf("end-----\n");
readFromPhp(c);
FastCgi_finit(c);
return 0;
}
Operation.php檔案
<html>
<body>
<?php
#預定義的 $_REQUEST 變數包含了 $_GET、$_POST 和 $_COOKIE 的內容。
#$_REQUEST 變數可用來收集通過 GET 和 POST 方法傳送的表單資料。
$a=$_REQUEST["a"];
$b=$_REQUEST["b"];
$c=$_REQUEST["c"];
$d=$_REQUEST["d"];
$result =($a-$b)+($c*$d);
echo $a.' - '.$b. ' + ' .$c. ' * ' .$d. " = $result"
// echo '1';
// var_dump($_REQUEST);
// echo $a;
?>
</body>
</html>
執行截圖:
***例項4 : 完成簡單 get 請求
見:csdn1
***例項5: 完成帶引數 get 請求
#include <stdio.h>
#include <stdlib.h>
#include "fcgi.h"
#include <sys/types.h>
#include <sys/socket.h>
int main()
{
FastCgi_t *c;
c = (FastCgi_t *)malloc(sizeof(FastCgi_t));
FastCgi_init(c);
setRequestId(c, 1); /*1 用來表明此訊息為請求開始的第一個訊息*/
startConnect(c);
sendStartRequestRecord(c);
sendParams(c, "SCRIPT_FILENAME", "/home/Shengxi-Liu/WebServer/wwwroot/php/Operation.php");
sendParams(c, "REQUEST_METHOD", "GET");
sendParams(c, "CONTENT_LENGTH", "0"); // 0 表示沒有 body
sendParams(c, "CONTENT_TYPE", "text/html");
sendParams(c, "QUERY_STRING", "a=20&b=10&c=5&d=6");
sendEndRequestRecord(c); //告訴cgi程式 head 有多長
/*
int sendEndRequestRecord(FastCgi_t *c)
{
int rc;
FCGI_Header endHeader;
endHeader = makeHeader(FCGI_PARAMS, c->requestId_, 0, 0);
rc = write(c->sockfd_, (char *)&endHeader, FCGI_HEADER_LEN);
assert(rc == FCGI_HEADER_LEN);
return 1;
}
*/
printf("end-----\n");
readFromPhp(c);
FastCgi_finit(c);
return 0;
}
執行截圖:
需要注意的是查詢字串(QUERY_STRING
)必須放在sendEndRequestRecord(c);
函式之前,想一想http
協議是怎樣處理帶引數的get
就要知道了.....
(3) 一個完整訊息稱為一個 record ,我們每次傳送的單位就是record。通過上面的介紹,我們可以總結出常見的記錄格式
type | record |
---|---|
1 | header(訊息頭) + 開始請求體(8位元組) |
3 | header + 結束請求體(8位元組) |
4 | header + name-value長度(8位元組) + 具體的name-value |
5,6,7 | header + 具體內容 |
最後,附上我的webserver
專案地址,裡邊含有使用到的fastcgi
庫.求star,求fork,哈哈哈哈...