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

相关文章
相关标签/搜索