FastCGI协议
前言
最近采用C++开发一个webserver,功能类似于nginx。支持其中的多站点host、反向代理、负载均衡、支持cgi、支持session、cookie。支持FastCGI,类似nginx可以监听9000端口连接php-fpm,因此查资料找相关FastCGI协议相关。
CGI及FastCGI相关知识点
- CGI(common gateway interface)通常翻译为共同网关接口:是Web Server与后台编程语言交互的一种协议。但是CGI有一个致命的缺点,就是每处理一个请求度需要fork一个进程,不断地fork是一项很消耗时间和资源的工作,这样低效的方式明显不能满足需求。
- FastCGI是一个常驻型的CGI,可以一直执行,只要激活后,不会每次都花时间去fork一次,而且还支持分布式运算。
学习过PHP的童鞋都知道在PHP有个叫php-fpm,它是FastCGI在PHP中的一种实现。我们常常在nginx中配置fastpass 127.0.0.1:9000,其实nginx将动态语言交由PHP处理。
FastCGI协议详细说明
列举一些常用传入参数
SCRIPT_FILENAME # 执行脚本文件所在路径 REQUEST_METHOD # 请求方式 常见的GET POST CONTENT_LENGTH # 针对POST请求中body的长度 CONTENT_TYPE # 请求MIME类型 QUERY_STRING # url参数(URL中?后面参数) HTTP_ACCEPT # 浏览器能够接收Content-types类型 HTTP_COOKIE # 传入HTTP头部中cookie值 HTTP_USER_AGENT # 传入HTTP头部中user-agent值 HTTP_REFERER # HTTP头部中Referer值
协议请求头
在发送开始报文以及发送结束报文都涉及到其中的header头部信息,将其共同部分抽取为一个struct结构体。header请求头在协议中占用8个字节。struct FASTCGI_Header { unsigned char version; // FastCGI协议版本 unsigned char type; // 协议操作类型(开始请求、发送参数、发送POST中body数据、结束请求、错误输出等) unsigned char requestIdB1; // 请求requestId unsigned char requestIdB0; unsigned char contentLengthB1; // 请求body长度 unsigned char contentLengthB0; unsigned char paddingLength; // 填充长度 unsigned char reserved; // 保留字节 };
协议请求体
- 开始请求body:
struct FastCgiBeginRequestBody { unsigned char roleB1; // Web服务器期望 php 所扮演的角色(常见有三种角色) unsigned char roleB0; unsigned char flags; // 响应后是否立即关闭 unsigned char reserved[5]; // 保留字节 };
role角色分析说明:
①php-fpm接收http请求并产生相应响应(响应器)
②php-fpm对于请求先进行一次认证AUTHORIZER,不通过则拒绝响应返回(鉴权器)
③过滤请求中的额外数据流,并产生过滤后的http响应(过滤器)
* **结束请求body:**
struct FastCgiEndRequestBody {
unsigned char appStatusB3; // 4个字节表示结束状态
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus; // 协议状态
unsigned char reserved[3];
};
GET请求中参数构造(传入参数按照key-value)
初始部分计算整体body长度,body总体长度为:len(key) + len(value) + (key大于128字节则用4个字节保存key,否则使用1个字节) + (value大于128字节则用4个字节保存key,否则使用1个字节)
unsigned char bodyBuff[bodylen]; int j = 0; /* 如果 nameLen 小于128字节 */ if (nlen < 128) { bodyBuff[j++] = (unsigned char) nlen; //nameLen用1个字节保存 } else { /* nameLen 用 4 个字节保存 */ bodyBuff[j++] = (unsigned char) ((nlen >> 24) | 0x80); bodyBuff[j++] = (unsigned char) (nlen >> 16); bodyBuff[j++] = (unsigned char) (nlen >> 8); bodyBuff[j++] = (unsigned char) nlen; } /* valueLen 小于 128 就用一个字节保存 */ if (vlen < 128) { bodyBuff[j++] = (unsigned char) vlen; } else { /* valueLen 用 4 个字节保存 */ bodyBuff[j++] = (unsigned char) ((vlen >> 24) | 0x80); bodyBuff[j++] = (unsigned char) (vlen >> 16); bodyBuff[j++] = (unsigned char) (vlen >> 8); bodyBuff[j++] = (unsigned char) vlen; } for (size_t i = 0; i < strlen(name); i++) { bodyBuff[j++] = name[i]; } for (size_t i = 0; i < strlen(value); i++) { bodyBuff[j++] = value[i]; }
POST请求中参数构造
POST请求发送body数据大小或许比较大,采用while循环发送,限制每次发送的最大字节长度。
while (len > 0) { if (len > FASTCGI_MAX_LENGTH) { bodylen = FASTCGI_MAX_LENGTH; } FASTCGI_Header header = makeHeader(FASTCGI_STDIN, requestId, bodylen, 0); ret = writen(socketFd, (char *) &header, FASTCGI_HEADER_LENGTH); if (ret != FASTCGI_HEADER_LENGTH) { return -1; } ret = writen(socketFd, data, bodylen); if (ret != bodylen) { return -1; } len -= bodylen; data += bodylen; }
结束请求(FCGI_END_REQUEST)
通过发送一个FCGI_END_REQUEST记录给Web服务器,来通知它某个请求被终止。
struct FastCgiEndRequestBody { unsigned char appStatusB3; unsigned char appStatusB2; unsigned char appStatusB1; unsigned char appStatusB0; unsigned char protocolStatus; unsigned char reserved[3]; };
appStatus代表角色(响应器、鉴权器、过滤器)
protocolStatus代表结束状态。有四种取值,①FASTCGI_REQUEST_COMPLETE 正常请求结束 ②FASTCGI_CANT_MPX_CONN 拒绝新请求进入,无法并发处理 ③FASTCGI_OVERLOADED 拒绝新请求进入,资源不足的情况发生 ④FASTCGI_UNKNOWN_ROLE 拒绝新请求进入,未知角色返回数据接收(FASTCGI_STDOUT、FASTCGI_STDERR)
判断其中返回头部type的类型,常见的两种正常输出、错误输出情况。并且读取其中的头部中的内容长度(contentLen)。
if (respHeader.type == FASTCGI_STDOUT) { // 正常情况 } else if (respHeader.type == FASTCGI_STDERR) { // 异常情况 }
返回数据解析处理成header头、body数据
由于直接返回的数据包含header头部数据,例如Content-type值、网页跳转、set-cookie等混合在body中,解析分为header头部、body体。
运用状态机方式变量字符。key-value串结束后即为body部分。for (; (*cur != '\0' && !isFinish); cur++) { switch (hState) { case H_START: if (*cur == CR || *cur == LF) { break; } key_start = cur; hState = H_KEY; break; case H_KEY: if (*cur == ':') { key_end = cur; hState = H_SPACES_AFTER_COLON; break; } break; case H_SPACES_AFTER_COLON: if (*cur == ' ') { break; } hState = H_VALUE; value_start = cur; break; case H_VALUE: if (*cur == CR) { value_end = cur; hState = H_CR; } break; case H_CR: if (*cur == LF) { // key - start while (key_start != key_end) { header += *key_start; key_start++; } header += ": "; // key - end // value - start while (value_start != value_end) { header += *value_start; value_start++; } header += "\r\n"; // value - end hState = H_LF; } else { isFinish = true; } break; case H_LF: if (*cur == CR) { hState = H_END_CR; } else { key_start = cur; hState = H_KEY; } break; case H_END_CR: if (*cur == LF) { hState = H_END_LF; } else { isFinish = true; } break; case H_END_LF: isFinish = true; break; } }
详细代码见项目代码FastCGI