FastCGI

一、FastCGI 协议

1、FastCGI报文格式

FastCGI报文格式如下:

typedef struct {
          unsigned char version;  //版本
          unsigned char type;      //类型
          unsigned char requestIdB1;    //请求Id
          unsigned char requestIdB0;        
          unsigned char contentLengthB1;    //负载长度
          unsigned char contentLengthB0;
          unsigned char paddingLength;      //填充长度
          unsigned char reserved;              //保留字节
          unsigned char contentData[contentLength]; //负载数据
          unsigned char paddingData[paddingLength]; //填充数据
} FCGI_Record;

FastCGI报文是8字节对齐的,其中报头为前8个字节为报头,其中报头可以用下面结构表示:

typedef struct {
          unsigned char version;
          unsigned char type;
          unsigned char requestIdB1;
          unsigned char requestIdB0
          unsigned char contentLengthB1;
          unsigned char contentLengthB0;
          unsigned char paddingLength;
          unsigned char reserved;
} FCGI_Header;

2、FastCGI报文解析

1) version : 表示FastCGI版本
有如下值:
#define FCGI_VERSION_1 1

2) type:表示报文的类型
有如下值:
/#define FCGI_BEGIN_REQUEST 1
/#define FCGI_ABORT_REQUEST 2
/#define FCGI_END_REQUEST 3
/#define FCGI_PARAMS 4
/#define FCGI_STDIN 5
/#define FCGI_STDOUT 6
/#define FCGI_STDERR 7
/#define FCGI_DATA 8
/#define FCGI_GET_VALUES 9
/#define FCGI_GET_VALUES_RESULT 10
/#define FCGI_UNKNOWN_TYPE 11
/#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
其中主要关注:
FCGI_BEGIN_REQUEST: 请求开始,由客户端发起的请求。
FCGI_PARAMS:fastcgi参数,即一些服务器变量,如HTTP_USER_AGENT。应该是由web服务器发送的。
FCGI_STDIN:请求数据。由客户端post的数据。
FCGI_STDOUT:应答数据。由我们作出的响应。
FCGI_STDERR:出错数据。由我们作为的错误响应。
FCGI_END_REQUEST:请求结束。由我们响应。 当我们发送该报文,表示一次http请求结束。

3)requestId:请求id
当一次请求时,请求id相同。
其中requestIdB1为高8位,requestIdB0为低8位。
requestId = requestIdB1 << 8 + requestIdB0

4)contentLength:负载数据长度
其中contentLengthB1为高8位,contentLengthB0为低8位。
contentLength = contentLengthB1 << 8 + contentLengthB0

5)paddingLength:填充数据长度
因为FastCGI报文是8字节对齐的,当负载数据长度%8!=0时,需要填充数据使其8字节对齐。
paddingLength = (8 - contentLength % 8) % 8;
可知paddingLength<8。

6)reserved:保留字节
该字节保留,使FastCGI报头长度为8字节。

7)contentData:负载数据
长度为contentLength。

8)paddingData:填充数据
长度为paddingLength。

3、请求开始报文

当报头类型type为FCGI_BEGIN_REQUEST时,该报文为请求开始报文,当用户发起一个http请求时,开始到达的第一个报文。请求开始报文的负载规定为8个字节。格式如下:

typedef struct {
     unsigned char roleB1;
     unsigned char roleB0;
     unsigned char flags;
     unsigned char reserved[5];
} FCGI_BeginRequestBody;

1)role
roleB1为高8位,roleB0为低8位,role可以取以下值:
/#define FCGI_RESPONDER 1
/#define FCGI_AUTHORIZER 2
/#define FCGI_FILTER 3
一般处理FCGI_RESPONDER即可。

2)flags
当flags为0时,表示本次请求完毕后关闭链接,为1时则不关闭链接。

3)reserved:保留字节
使8字节对齐,无需填充字节。可知报头paddingLength=0

4、fastcgi参数报文

报头类型type为FCGI_PARAMS时表示该报文为fastcgi报文,fastcgi参数报文由web代理服务器整理发送的(应该就是这样的)。为多个name-value对,负载格式如下:

typedef struct {  //该定义非C++标准定义
    unsigned char nameLengthB3;
    [
      unsigned char nameLengthB2;
      unsigned char nameLengthB1;
      unsigned char nameLengthB0;
    ]
    unsigned char valueLengthB3;
     [
      unsigned char valueLengthB2;
      unsigned char valueLengthB1;
      unsigned char valueLengthB0;
    ]
    unsigned char nameData[nameLength];
    unsigned char valueData[valueLength];
}FCGI_NameValuePair;

1)nameLength:名字长度
当nameLength的值大于127,则nameLength用4字节存储
nameLength = (nameLengthB3 << 24) + (nameLengthB2 << 16) + (nameLengthB1 << 8) + nameLengthB0;
当nameLength的值不超过127,则nameLength用1字节存储。
nameLength = nameLengthB3;

2)valueLength:值长度
类似nameLength。
当valueLength的值大于127,则valueLength用4字节存储
valueLength= (valueLengthB3 << 24) + (valueLengthB2 << 16) + (valueLengthB1 << 8) + valueLengthB0;
当valueLength的值不超过127,则valueLength用1字节存储。
valueLength= valueLengthB3;
所以nameLength+valueLength的所占的字节数可以为1+1=2, 1+4=5, 4+1=5, 4+4=8。

3)nameData,valueData
name数据和value数据。如在nginx做反向代理时,一般的fastCGI参数可以
在/usr/local/nginx/conf/fastcgi_param文件看到。大致解析如下:
vi /usr/local/nginx/conf/fastcgi_param

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;    #脚本文件请求的路径
fastcgi_param QUERY_STRING $query_string;                   #请求的参数;如?app=123
fastcgi_param REQUEST_METHOD $request_method;               #请求的动作(GET,POST)
fastcgi_param CONTENT_TYPE $content_type;                   #请求头中的Content-Type字段
fastcgi_param CONTENT_LENGTH $content_length;               #请求头中的Content-length字段。
fastcgi_param SCRIPT_NAME $fastcgi_script_name;             #脚本名称 
fastcgi_param REQUEST_URI $request_uri;                     #请求的地址不带参数
fastcgi_param DOCUMENT_URI $document_uri;                   #与$uri相同。 
fastcgi_param DOCUMENT_ROOT $document_root;                 #网站的根目录。在server配置中root指令中指定的值 
fastcgi_param SERVER_PROTOCOL $server_protocol;             #请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
fastcgi_param GATEWAY_INTERFACE CGI/1.1;                    #cgi 版本
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;         #nginx 版本号,可修改、隐藏
fastcgi_param REMOTE_ADDR $remote_addr;                     #客户端IP
fastcgi_param REMOTE_PORT $remote_port;                     #客户端端口
fastcgi_param SERVER_ADDR $server_addr;                     #服务器IP地址
fastcgi_param SERVER_PORT $server_port;                     #服务器端口
fastcgi_param SERVER_NAME $server_name;                     #服务器名,域名在server配置中指定的server_name
#fastcgi_param PATH_INFO $path_info;                        #可自定义变量

5、请求数据报文

报头类型type为FCGI_STDIN时表示该报文为请求数据的报文,即用户通过http协议post上来的数据。直接获取数据即可,数据长度为contentLength,而填充数据则抛弃。
FastCGI报文的负载的最大长度为0xFFFF(因为contentLength为2字节的)。当一个报文不够大,则会有多个,所以fastcgi参数报文、请求数据报文、应答数据报文、出错数据报文都可能有多个。除了请求开始报文和请求结束报文外,当某类报文发送完成后,都会发送一个该类的一个空报文。

6、应答数据报文

应答数据报文为我们做出应答的报文。将type设为FCGI_STDOUT,requestId设为与请求id相同,负载为返回的数据。注意负载大于0xFFFF时要分块。当发送完成后,加一个空的FCGI_STDOUT报文表示应答报文发送完毕。

7、出错数据报文

出错数据报文为我们做出应答的出错报文。将type设为FCGI_STDERR,requestId设为与请求id相同,负载为返回的出错数据。注意负载大于0xFFFF时要分块。当发送完成后,加一个空的FCGI_STDERR报文表示应答报文发送完毕。

8、请求结束报文

该报文为我们做出全部应答后发送给客户端的报文,表示该请求结束。请求报文的负载固定为下面格式:

typedef struct {
     unsigned char appStatusB3;
     unsigned char appStatusB2;
     unsigned char appStatusB1;
     unsigned char appStatusB0;
     unsigned char protocolStatus;
     unsigned char reserved[3];
} FCGI_EndRequestBody;

1)appStatus:返回的状态码
appStatus占4个字节,B3为最高字节,B0位最低字节,appStatus默认为0即可。

2)protocolStatus:协议状态
值为:
/#define FCGI_REQUEST_COMPLETE 0
/#define FCGI_CANT_MPX_CONN 1
/#define FCGI_OVERLOADED 2
/#define FCGI_UNKNOWN_ROLE 3
一般为FCGI_REQUEST_COMPLETE即可。

3)reserved:保留的3个字节

二、nginx反向代理及FastCGI的实现

1、nginx反向代理

设置为unix域套接字,nginx将用户的http请求通过FastCGI协议发送到unix套接字上,C++程序通过监听及读取套接字来进行操作。
伪代码如下:

int main(int argc, char *argv[])
{
    fd = socket(AF_LOCAL);   //创建unix域套接字
    bind(fd, unix_path);
    sockfd = accept(fd);     //http请求到来,建立了链接

    FCGI_Record record;
    read(&record);            //得到请求开始报文
    doParseRequestBegin(record);          //解析请求开始报文
    do
    {
         read(&record);                              //得到param报文
        doParseRequestParam(record);      //解析和合并1个或多个param报文
    } while(参数对报文还没读完);       //param报文长度不为0

    do
    {
         read(&record);                              //得到请求数据报文
        doParseRequestStdin(record);      //解析和合并1个或多个请求数据报文
    } while(stdin报文还没读完);            //stdin报文的长度不为0

    string outData;
    processOutData(outData);                //加工响应数据
    processStdout(recordList, outData);          //将数据分块(如果超出报文最大长度)
    for (int i = 0; i < recordList; i++)                  //发送数据块给客户端
        write(recordList.get(i));
    generateEmptyStdout(record);          //生成空的stdout报文
    write(record);                                     //发送空stdout表示stdout结束

    string errData;
    processErrData(errData);      //加工出错数据(如果有的话)
    processStdout(recordList, errData);  //出错数据分块
    for (int i = 0; i < recordList; i++)
        write(recordList.get(i));      //发送数据块给客户端
    generateEmptyStderr(record);    //生成空的stderr报文
    write(record);         //发送空stderr表示stderr结束(即使没有出错数据也发送)

    generateRequestEnd(record);   //生成请求结束报文
    write(record);    //发送请求结束报文
     //一次请求结束
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容