一. FastCGI协议简介
1.1 简介
FastCGI(Fast Common Gateway Interface, 快速通用网关接口)是一种通信协议。可以通过Unix Domain Socket, Named Pipe, TCP等方式按照FastCGI协议通信。
图 1.1 FastCGI简介
1.2 数据包格式
FastCGI数据包两部分, 头部(header), 包体(body), 每个数据包都必须包含header, body可以没有。header为8个字节, body必须为8的整数倍, 不是的话需要填充。
1.2.1 头部
typedef struct { unsigned char version; // 版本号 unsigned char type; // 数据包类型 unsigned char requestIdB1; // 记录id高8位 unsigned char requestIdB0; // 记录id低8位 unsigned char contentLengthB1; // 记录内容长度高8位(body长度高8位) unsigned char contentLengthB0; // 记录内容长度低8位(body长度低8位) unsigned char paddingLength; // 补齐位长度(body补齐长度) unsigned char reserved; // 补齐位}Header;
图 1.2 FastCGI协议数据包头部字段说明
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 //(web->fastcgi)传递参数 #define FCGI_STDIN 5 //(web->fastcgi)数据流传输数据 #define FCGI_STDOUT 6 //(fastcgi->web)数据流传输数据 #define FCGI_STDERR 7 //(fastcgi->web)数据流传输 #define FCGI_DATA 8 //(web->fastcgi)数据流传输 #define FCGI_GET_VALUES 9 //(web->fastcgi)查询fastcgi服务器性能参数 #define FCGI_GET_VALUES_RESULT 10 //(fastcgi->web)fastcgi性能参数查询返回 #define FCGI_UNKNOWN_TYPE 11 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
1.2.2 params类型数据包
图 1.3 Params数据包
说明:
params数据包以key, value格式发送, 具体格式为(keyLen, valLen, key, val)
key或者val长度大于127时,会用4个字节存储长度,否则用一个字节
1.2.3 数据流类型数据包(stdin, stdout, stderr, data)
图 1.4 数据流类型数据包
1.3 通信流程示例
图 1.5 FastCGI简单通信流程
说明:
begin request 代表请求开始, end request 代表请求结束。
除begin request, end request类型数据包外, 其他类型数据包在发送完成后,需要发送一个只有头部,包体长度为0, 也就是没有包体的数据包,代表这种类型的数据包发送结束。上述params, stdin, stdout, stderr 都需要发送结束包。
1.4 参考
二. Nginx FastCGI
nginx发送的缓冲区数据格式如下:
图 2.1 FastCGI数据包总体结构图
说明:
本部分主要依据ngx_http_fastcgi_create_request函数, 该函数会构造缓存区,并向其中写入上图所示内容,在该函数执行完后,会根据实际情况选择是否继续向缓冲区写入数据,后续情况在此不做讨论。
ngx_http_fastcgi_create_request 函数所需的变量, 在进入该函数之前认为已经初始化完成。
2.1 示例
2.1.1 upstream 发送缓冲区
//r.upstream.request_bufs.buf.pos //begin request header\001\001\000\001\000\b\000\000 //begin request body\000\001\000\000\000\000\000\000 //begin params header\001\004\000\001\002\b\000\000 //begin parmas body\n\004PRODUCTIONtrue\017\061SCRIPT_FILENAME/home/codes/nginx-1.6.2/output/html8100/index.php\f\000QUERY_STRING\016\003REQUEST_METHODGET\f\000CONTENT_TYPE\016\000CONTENT_LENGTH\v\nSCRIPT_NAME/index.php\v\001REQUEST_URI/\f\nDOCUMENT_URI/index.php\r'DOCUMENT_ROOT/home/codes/nginx-1.6.2/output/html8100\017\bSERVER_PROTOCOLHTTP/1.1\021\aGATEWAY_INTERFACECGI/1.1\017\vSERVER_SOFTWAREnginx/1.6.2\v\tREMOTE_ADDR127.0.0.1\v\005REMOTE_PORT42146\v\tSERVER_ADDR127.0.0.1\v\004SERVER_PORT8100\v\000SERVER_NAME\017\003REDIRECT_STATUS200\017\vHTTP_USER_AGENTcurl/7.29.0\t\016HTTP_HOSTlocalhost:8100\v\003HTTP_ACCEPT*/* //end params header\001\004\000\001\000\000\000\000 //begin stdin header, 包体长度为0, 可以认为stdin类型数据包发送结束\001\005\000\001\000\000\000\000
2.1.2 Params 数据包参数整理
2.2 源码解析
2.2.1 基础
2.2.1.1 le.ip结构图
图 2.2 le.ip 结构图
2.2.1.2 e.ip结构图
图2.3 e.ip结构图
2.2.2 ngx_http_fastcgi_create_request
该函数主要依据图2.1, 将所需数据写入到ngx_http_request_t对应的ngx_http_upstream_t的缓冲区中。
写入key, val时, 通过调用相应函数实现, 该函数是与对应的key, val放置在一起的, 如图2.3所示。
//params数据包写入核心代码while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = (u_char) lcode(&le); //获取key的长度 lcode = *(ngx_http_script_len_code_pt *) le.ip; skip_empty = lcode(&le); //查看空时是否跳过 for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); //当前key:value结束, le.ip后移1位 if (skip_empty && val_len == 0) { //value为空, 并且设置空时跳过该key e.skip = 1; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); e.skip = 0; continue; } *e.pos++ = (u_char) key_len; //写入key len if (val_len > 127) { //写入value len *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *e.pos++ = (u_char) ((val_len >> 16) & 0xff); *e.pos++ = (u_char) ((val_len >> 8) & 0xff); *e.pos++ = (u_char) (val_len & 0xff); } else { *e.pos++ = (u_char) val_len; } while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); //调用code存储的处理函数, 负责将key, value内容写入缓存, 并将e.ip后移 } e.ip += sizeof(uintptr_t); //当前Key:Value结束, 跳过NULL, e.ip后移一位 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, e.pos - (key_len + val_len), val_len, e.pos - val_len); }