目录
1.初识FastCGI协议
FastCGI 是一种协议,规定了FastCGI应用和支持FastCGI的Web服务器之间的接口。FastCGI是二进制连续传递的。
1.1消息头
FastCGI定义了多种类型的消息;nginx对FastCGI消息类型定义如下:
#define NGX_HTTP_FASTCGI_BEGIN_REQUEST 1
#define NGX_HTTP_FASTCGI_ABORT_REQUEST 2
#define NGX_HTTP_FASTCGI_END_REQUEST 3
#define NGX_HTTP_FASTCGI_PARAMS 4
#define NGX_HTTP_FASTCGI_STDIN 5
#define NGX_HTTP_FASTCGI_STDOUT 6
#define NGX_HTTP_FASTCGI_STDERR 7
#define NGX_HTTP_FASTCGI_DATA 8
一般情况下,最先发送的是BEGIN_REQUEST类型的消息,然后是PARAMS和STDIN类型的消息;
当FastCGI响应处理完后,将发送STDOUT和STDERR类型的消息,最后以END_REQUEST表示请求的结束。
FastCGI定义了一个统一结构的8个字节消息头,用来标识每个消息的消息体,以及实现消息数据的分割。结构体定义如下:
typedef struct {
u_char version; //FastCGI协议版本
u_char type; //消息类型
u_char request_id_hi; //请求ID
u_char request_id_lo;
u_char content_length_hi; //内容
u_char content_length_lo;
u_char padding_length; //内容填充长度
u_char reserved; //保留
} ngx_http_fastcgi_header_t;
我们看到请求ID与内容长度分别用两个u_char存储,实际结果的计算方法如下:
requestId = (request_id_hi << 8) + request_id_lo;
contentLength = (content_length_hi << 8) + content_length_lo;
消息体的长度始终是8字节的整数倍,当实际内容长度不足时,需要填充若干字节;填充代码如下所示:
padding = 8 - len % 8;
padding = (padding == 8) ? 0 : padding;
1.2消息体举例
BEGIN_REQUEST类型的消息标识FastCGI请求的开始,结构固定,定义如下:
typedef struct {
u_char role_hi; //标记FastCGI应用应该扮演的角色
u_char role_lo;
u_char flags;
u_char reserved[5];
} ngx_http_fastcgi_begin_request_t;
角色同样使用两个u_char存储,计算方法为:
role = (role_hi << 8) + role_lo;
最常用的是响应器(Responder)角色,FastCGI应用接收所有与HTTP请求相关的信息,并产生一个HTTP响应。
nginx配置文件中,fastcgi_param指令配置的若干参数,以及HTTP请求的消息头,都是通过FCGI_PARAMS类型的消息传递的,此消息就是若干个名—值对(此名—值对在php中可以通过$_SERVER[ ]获取);
传输格式为nameLength+valueLength+name+value。
为了节省空间,对于0~127长度的值,Length使用了一个char来表示,第一位为0,对于大于127的长度的值,Length使用了4个char来表示,第一位为1;如下图所示:
Length字段编码的逻辑如下:
if (val_len > 127) {
*b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
*b->last++ = (u_char) ((val_len >> 16) & 0xff);
*b->last++ = (u_char) ((val_len >> 8) & 0xff);
*b->last++ = (u_char) (val_len & 0xff);
} else {
*b->last++ = (u_char) val_len;
}
2. 基础知识
2.1 FastCGI配置
代码中搜索ngx_http_fastcgi_commands,查看fastcgi模块提供的配置指令;
static ngx_command_t ngx_http_fastcgi_commands[] = {
{ ngx_string("fastcgi_pass"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, //只能出现在location块中
ngx_http_fastcgi_pass,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
{ ngx_string("fastcgi_param"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, //可以出现在http配置块、server配置块、location配置块中
ngx_http_upstream_param_set_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_fastcgi_loc_conf_t, params_source), //ngx_http_fastcgi_loc_conf_t结构的params_source字段是存储配置参数的array,
NULL },
…………
}
fastcgi_pass指令用于配置上游FastCGI应用的ip:port,ngx_http_fastcgi_pass方法解析此指令(设置handler为ngx_http_fastcgi_handler方法,命中当前location规则的HTTP请求,请求处理的内容产生阶段会调用此handler);
fastcgi_param用于配置nginx向FastCGI应用传递的参数,在php中,我们可以通过$_SERVER[" "]获取这些参数;
解析fastcgi_param配置的代码实现如下:
char * ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
a = (ngx_array_t **) (p + cmd->offset); //ngx_http_fastcgi_loc_conf_t结构首地址加params_source字段的偏移
param = ngx_array_push(*a);
value = cf->args->elts;
param->key = value[1];
param->value = value[2];
param->skip_empty = 0;
if (cf->args->nelts == 4) { //if_not_empty用于配置参数是否必传(如果配置,当值为空时不会传向FastCGI应用传递此参数)
if (ngx_strcmp(value[3].data, "if_not_empty") != 0) {
return NGX_CONF_ERROR;
}
param->skip_empty = 1;
}
return NGX_CONF_OK;
}
2.2FastCGI配置预处理
fastcgi_param配置的所有参数会会存储在ngx_http_fastcgi_loc_conf_t结构体的params_source字段;
nginx为了方便生成fastcgi请求数据,会提前对params_source做一些预处理,预先初始化号每个名—值对的长度以及数据拷贝方法等;
2.1节查看fastcgi模块提供的配置指令时发现,某些配置指令出现在location配置块,有些配置却可以出现http配置块、server配置块和location配置块;即可能出现同一个指令同时出现在好几个配置块中,此时如何解析配置?
对于这些配置指令,nginx最终会执行一个merge操作,合并多个配置为一个;观察nginx的HTTP模块,大多模块都会存在一个merge_loc_conf字段(函数指针),用于merge配置;
fastcgi模块的merge操作由ngx_http_fastcgi_merge_loc_conf完成,其同时对params_source进行了一些预处理;代码如下:
static char * ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
prev->upstream.connect_timeout, 60000);
ngx_conf_merge_value(conf->upstream.pass_request_headers,
prev->upstream.pass_request_headers, 1); //配置HTTP头部是否传递给FastCGI应用,默认为1
ngx_conf_merge_value(conf->upstream.pass_request_body,
prev->upstream.pass_request_body, 1); //