Fastcgi 协议解析及 getpost 使用实例
前言:
基于:
csdn1
婁神的描述
其實看上面兩位大佬的博客就已經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版本信息,目前一般定義為1unsigned char type; //每次發送的消息的類型.相當于flag,具體表示見下面:unsigned char requestIdB1; //合起來表示本次請求的編號 IDunsigned 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 1FCGI_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 建立的 sockfdint requestId_; //record 里的請求IDint flag_; //用來標志當前讀取內容是否為html內容} FastCgi_t;void FastCgi_init(FastCgi_t *c) {c->sockfd_ = 0; //與php-fpm 建立的 sockfdc->flag_ = 0; //record 里的請求IDc->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主要有如下這些:
| *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,為了發 bodyFCGI_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 表示沒有 bodysendParams(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。通過上面的介紹,我們可以總結出常見的記錄格式
| 1 | header(消息頭) + 開始請求體(8字節) |
| 3 | header + 結束請求體(8字節) |
| 4 | header + name-value長度(8字節) + 具體的name-value |
| 5,6,7 | header + 具體內容 |
最后,附上我的webserver項目地址,里邊含有使用到的fastcgi庫.求star,求fork,哈哈哈哈...
轉載于:https://www.cnblogs.com/Tattoo-Welkin/p/10335250.html
總結
以上是生活随笔為你收集整理的Fastcgi 协议解析及 getpost 使用实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: W - Pasha and Phone
- 下一篇: css:position