[转][Nginx 源码分析] Fastcgi 模块(上)
类别:其他
状态:游客可见,可回,自己可关联(良好)
阅读:5674
评论:0
时间:April 10, 2012, 8:31 a.m.
关键字: Fastcgi nginx
大概处理流程
1) 计算出 FASTCgI params 和 request_headers 这两部分所需的空间,然后分配分配一个足够大的 buffer 来准备 copy 实际 params 和 header 内容。buffer 结构大概如下:
size = sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t) + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + len + padding + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */ b = ngx_create_temp_buf(r->pool, size); if (b == NULL) { return NGX_ERROR; }
2) 填充相应的 Header,并复制 FASTCgI params
和 request_headers 这两部分内容到 buffer中。
3) 如果有 request body,填充好 FASTCGI_STDIN Header,创建新的
buffer 来复制 body;并将 buffer 链接到 buffer chain 里面
4)
发送整个 buffer chain,fastcgi 请求结束。
详细源码分析:
计算 params 长度
len = 0; header_params = 0; ignored = NULL; flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); // 开始计算 params 长度 // params_len 是一个所有 params 长度的 array if (flcf->params_len) { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); ngx_http_script_flush_no_cacheable_variables(r, flcf->flushes); le.flushed = 1; le.ip = flcf->params_len->elts; le.request = r; // 循环处理每个 params_len item while (*(uintptr_t *) le.ip) { // le.ip 为函数指针 lcode = *(ngx_http_script_len_code_pt *) le.ip; // 取得 item key 的长度 // 函数 lcode 取得 le 的 len 成员,并把 le->ip 指向下一个 array item key_len = lcode(&le); // 计算对应 value 的长度 for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } // 不同 params_len item 以一个 NULL uintptr_t 指针分隔 // 这里把指针移动到下一个 params_len item,next item le.ip += sizeof(uintptr_t); // 计算传输这个 params item 所需要创建的 FASTCGI 报文的长度 len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len; } }
这段代码不太好理解的地方就是 params_len,及其用法
params_len
是一个 nginx 的 Array,起成员结构如下:
typedef struct { ngx_http_script_code_pt code; uintptr_t len; } ngx_http_script_copy_code_t;
成员 code 是一个函数指针,该函数如下:
size_t ngx_http_script_copy_len_code(ngx_http_script_engine_t *e) { ngx_http_script_copy_code_t *code; code = (ngx_http_script_copy_code_t *) e->ip; e->ip += sizeof(ngx_http_script_copy_code_t); return code->len; }
了解这些,params_len
部分的计算,就很好理解了。
这种数据结构和用法相当之神秘,找时间好好揣摩下。
计算 request_headers 长度
// 计算 request header 的长度 if (flcf->upstream.pass_request_headers) { allocated = 0; lowcase_key = NULL; // 创建一个 ignored 数组 if (flcf->header_params) { ignored = ngx_palloc(r->pool, flcf->header_params * sizeof(void *)); if (ignored == NULL) { return NGX_ERROR; } } // 取得 headers 的第一个 part part = &r->headers_in.headers.part; // 取得 header 元素的指针,array 类型 header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } // 如果当前 part 处理完毕,继续 next part = part->next; header = part->elts; i = 0; } if (flcf->header_params) { // 为 hash key 分配空间 if (allocated < header[i].key.len) { allocated = header[i].key.len + 16; lowcase_key = ngx_pnalloc(r->pool, allocated); if (lowcase_key == NULL) { return NGX_ERROR; } } hash = 0; // 把 key 转成小写,并计算出最后一个 ch 的 hash 值 for (n = 0; n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'A' && ch <= 'Z') { ch |= 0x20; } else if (ch == '-') { ch = '_'; } hash = ngx_hash(hash, ch); lowcase_key[n] = ch; } // 查找这个 header 是否在 ignore 名单之内 // yes ,把这个 header 指针放在 ignore 数组内,后面有用,然后继续处理下一个 if (ngx_hash_find(&flcf->headers_hash, hash, lowcase_key, n)) { ignored[header_params++] = &header[i]; continue; } // n 的值的计算和下面的其实一样 // 至于 sizeof 后再减一,是因为只需要附加个 "HTTP" 到 Header 上去,不需要 "_" n += sizeof("HTTP_") - 1; } else { n = sizeof("HTTP_") - 1 + header[i].key.len; } // 计算 FASTCGI 报文长度 len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1) + n + header[i].value.len; } }
创建 buffer 和 buffer chain
// FASTCGI 协议规定,数据必须 8 bit 对齐 padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; // 计算总的所需空间大小 size = sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t) + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + len + padding + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */ // 创建 buffer b = ngx_create_temp_buf(r->pool, size); if (b == NULL) { return NGX_ERROR; } // 创建 buffer chain,把刚创建的 buffer 链进去 cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; // 前两个 header 的内容是已经定义好的,这里简单复制过来 ngx_memcpy(b->pos, &ngx_http_fastcgi_request_start, sizeof(ngx_http_fastcgi_request_start_t)); // 取得第三个 header 的指针 h = (ngx_http_fastcgi_header_t *) (b->pos + sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t)); // 设置 fastcgi content len 域 h->content_length_hi = (u_char) ((len >> 8) & 0xff); h->content_length_lo = (u_char) (len & 0xff); h->padding_length = (u_char) padding; h->reserved = 0;
复制 params 内容
if (flcf->params_len) { ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); // 取得实际的 params e.ip = flcf->params->elts; // 把目前 buffer 的位置赋值给 e.pos e.pos = b->last; e.request = r; e.flushed = 1; le.ip = flcf->params_len->elts; while (*(uintptr_t *) le.ip) { // 这里依然是函数指针,和前面讲的类似 // 依然是,取得 key_len 和 var_len lcode = *(ngx_http_script_len_code_pt *) le.ip; key_len = (u_char) 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); // 开始构建 FASTCGI Name-Value Pairs // 设置 key len *e.pos++ = (u_char) key_len; // 设置 val len if (val_len > 127) { *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; } // 复制 key 和 value 到 buffer // code 属于函数指针,下面会有解释 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); 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); } // 更新 buffer last 值 b->last = e.pos; }
这里最难理解的就是如何复制具体的 key 和 value 到 buffer 的,还是函数指针问题。
code 函数定义如下:
void ngx_http_script_copy_code(ngx_http_script_engine_t *e) { u_char *p; ngx_http_script_copy_code_t *code; code = (ngx_http_script_copy_code_t *) e->ip; p = e->pos; // 复制相应 key 和 value 的内容到 buffer if (!e->skip) { e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_script_copy_code_t), code->len); } // 移动指针,等待处理下一个 e->ip += sizeof(ngx_http_script_copy_code_t) + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1)); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script copy: \"%*s\"", e->pos - p, p); }
复制 request header
if (flcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } // 如果是 ignored,继续下一个 for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { goto next; } } // 开始创建 FASTCGI Name-Value Pairs // 设置 key len key_len = sizeof("HTTP_") - 1 + header[i].key.len; if (key_len > 127) { *b->last++ = (u_char) (((key_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((key_len >> 16) & 0xff); *b->last++ = (u_char) ((key_len >> 8) & 0xff); *b->last++ = (u_char) (key_len & 0xff); } else { *b->last++ = (u_char) key_len; } // 设置 val len val_len = header[i].value.len; 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; } // 先给 Key 加一个 HTTP 的头 b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1); // 把 Heaer Name 转成 大写,然后复制到 buffer 中 for (n = 0; n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'a' && ch <= 'z') { ch &= ~0x20; } else if (ch == '-') { ch = '_'; } *b->last++ = ch; } // 复制该 Header 对应的值 b->last = ngx_copy(b->last, header[i].value.data, val_len); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, b->last - (key_len + val_len), val_len, b->last - val_len); next: continue; } }
这部分比较容易看懂。
复制 request body
if (flcf->upstream.pass_request_body) { body = r->upstream->request_bufs; // 把 buffer chain 赋值给 request_bufs r->upstream->request_bufs = cl; #if (NGX_SUPPRESS_WARN) file_pos = 0; pos = NULL; #endif while (body) { // 取得起始 pos if (body->buf->in_file) { file_pos = body->buf->file_pos; } else { pos = body->buf->pos; } next = 0; do { // 创建一个新的 buffer b = ngx_alloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } // 用 body->buf 赋值 ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); // 每次最多复制 32 * 1024 字节 // 结束则设置 next 值,退出循环 if (body->buf->in_file) { b->file_pos = file_pos; file_pos += 32 * 1024; if (file_pos >= body->buf->file_last) { file_pos = body->buf->file_last; next = 1; } b->file_last = file_pos; len = (ngx_uint_t) (file_pos - b->file_pos); } else { b->pos = pos; pos += 32 * 1024; if (pos >= body->buf->last) { pos = body->buf->last; next = 1; } b->last = pos; len = (ngx_uint_t) (pos - b->pos); } padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; // 创建 FASTCGI STDIN 报文 h->version = 1; h->type = NGX_HTTP_FASTCGI_STDIN; h->request_id_hi = 0; h->request_id_lo = 1; h->content_length_hi = (u_char) ((len >> 8) & 0xff); h->content_length_lo = (u_char) (len & 0xff); h->padding_length = (u_char) padding; h->reserved = 0; // 创建一个新的 chain link,把刚才的 buffer 链接上去 cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; // 再创建一个 buffer,生成一个新的 FASTCGI header // 然后链接到 buffer chain 中 b = ngx_create_temp_buf(r->pool, sizeof(ngx_http_fastcgi_header_t) + padding); if (b == NULL) { return NGX_ERROR; } // 如果前一个需要 paddding,这里需要设置 padding if (padding) { ngx_memzero(b->last, padding); b->last += padding; } h = (ngx_http_fastcgi_header_t *) b->last; b->last += sizeof(ngx_http_fastcgi_header_t); cl->next = ngx_alloc_chain_link(r->pool); if (cl->next == NULL) { return NGX_ERROR; } cl = cl->next; cl->buf = b; } while (!next); body = body->next; } } else { r->upstream->request_bufs = cl; }
body 部分,不会发生实际的数据 copy。仅仅是创建新的 ngx_buf_t
结构,然后修改 buffer 指针直接指向 body 的 buffer 地址,然后链入 buffer chain。
每个 buffer 最多管理 32 * 1024 字节内容,如果超过,则创建新的 ngx_buf_t
结构和相应的 FASTCGI Header 报文。
请求创建完毕后,会有 upstream 模块把 buffer chain 发送出去。
操作: