zoukankan      html  css  js  c++  java
  • Fastcgi 协议解析及 get&post 使用实例

    前言:

    基于:

    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协议,其实就是WebServerphp-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-----
    ");
    
        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-----
    ");
    
        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,哈哈哈哈...

  • 相关阅读:
    ajax请求成功后打开新窗口地址
    向table添加水平滚动条
    使用jQuery实现类似开关按钮的效果
    利用jQuery实现CheckBox全选/全不选/反选
    jQuery插件开发
    九度oj 题目1214:丑数
    素数的筛选法
    九度oj 题目1367:二叉搜索树的后序遍历序列
    九度oj 题目1499:项目安排
    九度oj 题目1160:放苹果
  • 原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335250.html
Copyright © 2011-2022 走看看