Fastcgi 協議解析及 get&post 使用實例

前言:

基於:php

csdn1html

婁神的描述git

其實看上面兩位大佬的博客就已經ojbk了.寫的目地主要是本身總結學習一下.github

基礎:

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協議,其實就是WebServerphp-fpm之間通訊的規則(或者說是'語言')**web

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的不一樣而有所不一樣.app

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,哈哈哈哈...

相關文章
相關標籤/搜索