leevis.com
leevis.com copied to clipboard
nginx proxy 模块请求发往上游
概述
nginx通过proxy_pass url; 来指定一组上游服务器,来实现7层http的反向代理功能。 通过URL指定一组上游服务器,URL可以是变量、域名、upstream的配置名称。
server {
......
set $ups "127.0.0.1:8990";
location /test1/ {
proxy_pass http://$ups/;
}
location /test2/ {
proxy_pass http://$ups;
}
location /test3/ {
proxy_pass http://127.0.0.1:8991/;
}
location /test4/ {
proxy_pass http://127.0.0.1:8991;
}
location /test5/ {
proxy_pass http://127.0.0.1:8991/www/;
}
}
对照上述4种配置的测试结果:
# curl -v 'http://127.0.0.1:8080/test1/hello/liwq'
$nc -l 127.0.0.1 8990
GET / HTTP/1.0
# curl -v 'http://127.0.0.1:8080/test2/hello/liwq'
$nc -l 127.0.0.1 8990
GET /test2/hello/liwq HTTP/1.0
#curl -v 'http://127.0.0.1:8080/test3/hello/liwq'
$nc -l 127.0.0.1 8991
GET /hello/liwq HTTP/1.0
#curl -v 'http://127.0.0.1:8080/test4/hello/liwq'
$nc -l 127.0.0.1 8991
GET /test4/hello/liwq HTTP/1.0
#curl -v 'http://127.0.0.1:8080/test5/hello/liwq'
$nc -l 127.0.0.1 8991
GET /www/hello/liwq HTTP/1.0
小结: 根据proxy_pass后的URL中是否有变量和是否有uri,转到上游的url不同:
- 有变量有uri:转到上游的请求url是proxy_pass 的URL中的uri。
- 有变量无uri:转到上游的请求url是原来请求的url。
- 无变量有uri:转到上游的请求url是proxy_pass 的URL中的uri + 原来请求去掉location 的name剩下的url。
- 无变量无uri:转到上游的请求url是原来请求的url。
代码解析
配置解析
下面通过解析proxy模块的proxy_pass指令来看看,proxy模块是如何选择上游服务器的,又是如何拼接请求把请求发往上游服务器的。下面介绍proxy_pass指令对应的代码ngx_http_proxy_pass函数。
- 通过检测plcf->upstream.upstream || plcf->proxy_lengths 来判断是否在一个location重复配置proxy_pass指令。
- 赋值该location下content阶段的处理函数为ngx_http_proxy_handler。
- 判断URL是否有变量:
- 有变量则调用ngx_http_script_compile函数把变量的回调函数添加到plcf->proxy_lengths和plcf->proxy_values数组中。
- 没有变量,则调用ngx_http_upstream_add查找一组上游配置。
通过配置指令proxy_set_header设置一组发到上游的header,nginx代码是通过ngx_conf_set_keyval_slot函数把key val插入到plcf->headers_source数组中,数组的元素是ngx_keyval_t。 在merge_loc_conf 回调函数中,通过调用ngx_http_proxy_init_headers函数初始header。
ngx_http_proxy_init_headers 函数解析:
- 分配了bucket,说明已经初始化完成了,不用再次初始化了。
- 初始化headers_names为hash表,headers_merged为header 的key val结构。
- 合并headers_source和default_headers到headers_merged数组中,两个数组中有相同的元素,取headers_source。
- 遍历合并后的headers_merged数组,初始化hash表初始化所用的headers_names 数组。同时把合并数组中的header的长度以及回调函数存到headers->lengths数组中,内容存以及回调函数存到headers->values数组中,因涉及到变量,所以比较复杂。lengths数组中的元素是ngx_http_script_copy_code_t结构体,values数组中的元素是ngx_http_script_copy_code_t结构体,和该结构体后边紧跟的内容。如果header的val没有变量,则一个数组元素对应一个header,否则多个数组元素才对应一个header,两个header在数组中会用null隔开。val中变量会调用ngx_http_script_compile函数编译。
- 编译headers->hash。
typedef struct {
ngx_array_t *flushes;
ngx_array_t *lengths;
ngx_array_t *values; // proxy_set_header的val支持变量
ngx_hash_t hash; // 配置文件中通过proxy_set_header设置的回源请求头和nginx默认的请求头
} ngx_http_proxy_headers_t;
static ngx_int_t
ngx_http_proxy_init_headers(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf,
ngx_http_proxy_headers_t *headers, ngx_keyval_t *default_headers)
{
u_char *p;
size_t size;
uintptr_t *code;
ngx_uint_t i;
ngx_array_t headers_names, headers_merged;
ngx_keyval_t *src, *s, *h;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_script_compile_t sc;
ngx_http_script_copy_code_t *copy;
if (headers->hash.buckets) {
return NGX_OK;
}
// 构建hash表使用
if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
// 配置文件proxy_set_header设置的头和nginx默认的头合并,
// 如果配置文件和默认的有冲突,取配置文件的。
if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t))
!= NGX_OK)
{
return NGX_ERROR;
}
// 回源请求头长度
headers->lengths = ngx_array_create(cf->pool, 64, 1);
if (headers->lengths == NULL) {
return NGX_ERROR;
}
// 回源请求头内容
headers->values = ngx_array_create(cf->pool, 512, 1);
if (headers->values == NULL) {
return NGX_ERROR;
}
// 通过proxy_set_header设置的请求头
// 复制到headers_merged数组中
if (conf->headers_source) {
src = conf->headers_source->elts;
for (i = 0; i < conf->headers_source->nelts; i++) {
s = ngx_array_push(&headers_merged);
if (s == NULL) {
return NGX_ERROR;
}
*s = src[i];
}
}
// 再把默认的请求头复制到headers_merged数组中,如果headers_merged已经有的则跳过。
h = default_headers;
while (h->key.len) {
src = headers_merged.elts;
for (i = 0; i < headers_merged.nelts; i++) {
if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
goto next;
}
}
s = ngx_array_push(&headers_merged);
if (s == NULL) {
return NGX_ERROR;
}
*s = *h;
next:
h++;
}
src = headers_merged.elts;
for (i = 0; i < headers_merged.nelts; i++) {
hk = ngx_array_push(&headers_names);
if (hk == NULL) {
return NGX_ERROR;
}
hk->key = src[i].key;
hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len);
hk->value = (void *) 1;
if (src[i].value.len == 0) {
continue;
}
copy = ngx_array_push_n(headers->lengths,
sizeof(ngx_http_script_copy_code_t));
if (copy == NULL) {
return NGX_ERROR;
}
copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
copy->len = src[i].key.len;
size = (sizeof(ngx_http_script_copy_code_t)
+ src[i].key.len + sizeof(uintptr_t) - 1)
& ~(sizeof(uintptr_t) - 1);
copy = ngx_array_push_n(headers->values, size);
if (copy == NULL) {
return NGX_ERROR;
}
copy->code = ngx_http_script_copy_code;
copy->len = src[i].key.len;
p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
ngx_memcpy(p, src[i].key.data, src[i].key.len);
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = &src[i].value;
sc.flushes = &headers->flushes;
sc.lengths = &headers->lengths;
sc.values = &headers->values;
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_ERROR;
}
code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
if (code == NULL) {
return NGX_ERROR;
}
*code = (uintptr_t) NULL;
code = ngx_array_push_n(headers->values, sizeof(uintptr_t));
if (code == NULL) {
return NGX_ERROR;
}
*code = (uintptr_t) NULL;
}
code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t));
if (code == NULL) {
return NGX_ERROR;
}
*code = (uintptr_t) NULL;
hash.hash = &headers->hash;
hash.key = ngx_hash_key_lc;
hash.max_size = conf->headers_hash_max_size;
hash.bucket_size = conf->headers_hash_bucket_size;
hash.name = "proxy_headers_hash";
hash.pool = cf->pool;
hash.temp_pool = NULL;
return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
}
请求解析
下游请求到达时,解析完请求头后调用11个阶段,当调用到content阶段的时候执行ngx_http_proxy_handler回调函数。
- 调用ngx_http_upstream_create函数创建一个upstream的结构体,赋值给r->upstream;
- 创建proxy模块的请求上下文,其类型为ngx_http_proxy_ctx_t结构体。
- 判断plcf->proxy_lengths数组是否为null
- 如果为null,则说明proxy_pass的url没有变量。
- 如果不为null,则说明url有变量,调用ngx_http_proxy_eval。
- 赋值upstream结构体回调函数和变量,r->request_body_no_buffering为真表示不换成request,u->buffering为真表示缓存resp。
- 调用ngx_http_read_client_request_body函数读取完body后,再调用ngx_http_upstream_init函数初始化上游链接。
全部读取完下游请求后调用ngx_http_upstream_init初始化上游链接,实际上是调用的ngx_http_upstream_init_request函数。
-
如果不保存上游结果且不忽略客户端异常且post_action为空,则赋值request的读写回调:
r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;
-
客户端请求有body,则赋值:
u->request_bufs = r->request_body->bufs;
-
调用u->create_request回调函数创建发送到上游的请求体。其回调函数为:ngx_http_proxy_create_request
- 计算发往上游请求的长度(方法长度+http版本长度+回车换行长度+uri的长度+[配置的body的长度]+ 配置的请求头的长度+原请求请求头的长度),注意:如果没有配置proxy_set_body,且proxy_pass_request_body为on,发到上游的body是原请求的body,原请求的body在r->request_body->bufs中,已经赋值给u->request_bufs。
- 分配请求长度的内存,用来构建发往上游的请求。
- plcf->proxy_lengths && ctx->vars.uri.len proxy_pass的URL有变量也有uri。则发到上游的uri为URL配置的uri。
- URL中没有配置uri,解析客户端的请求uri有效且不是子请求,则发到上游的uri为原客户端的uri。
- 是有效的location且proxy_pass的URL有uri,则发往上游的请求uri是proxy_pass的URL的uri+原请求的uri减去location name前缀再拼上原请求的参数。否则,发往上游请求的url是原客户端的url。
- 请求头中,val为空的key val会被丢弃。
- 如果没有配置proxy_set_body,且proxy_pass_request_body为on,且原请求有body,则把body挂到组装好的请求的内存链后边。
- u->request_bufs指向发往上游的请求头+请求体。
-
uscf->peer.init
-
调用ngx_http_upstream_connect函数向上游发起链接请求。
// 解析指令 proxy_pass URL; 中的URL保存到vars结构体。
typedef struct {
ngx_str_t key_start; // 指向uri
ngx_str_t schema; // 是http还是https协议
ngx_str_t host_header; // ip:port 或host
ngx_str_t port; // 指定的端口
ngx_str_t uri; // 指定的uri
} ngx_http_proxy_vars_t;
typedef struct {
ngx_http_status_t status;
ngx_http_chunked_t chunked;
ngx_http_proxy_vars_t vars;
// 通过该指令proxy_set_body 设置的body的长度 或者 原请求body长度。
off_t internal_body_length;
ngx_chain_t *free;
ngx_chain_t *busy;
unsigned head:1;
unsigned internal_chunked:1;
unsigned header_sent:1;
} ngx_http_proxy_ctx_t;
static ngx_int_t
ngx_http_proxy_create_request(ngx_http_request_t *r)
{
size_t len, uri_len, loc_len, body_len,
key_len, val_len;
uintptr_t escape;
ngx_buf_t *b;
ngx_str_t method;
ngx_uint_t i, unparsed_uri;
ngx_chain_t *cl, *body;
ngx_list_part_t *part;
ngx_table_elt_t *header;
ngx_http_upstream_t *u;
ngx_http_proxy_ctx_t *ctx;
ngx_http_script_code_pt code;
ngx_http_proxy_headers_t *headers;
ngx_http_script_engine_t e, le;
ngx_http_proxy_loc_conf_t *plcf;
ngx_http_script_len_code_pt lcode;
u = r->upstream;
plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
#if (NGX_HTTP_CACHE)
headers = u->cacheable ? &plcf->headers_cache : &plcf->headers;
#else
headers = &plcf->headers;
#endif
if (u->method.len) {
/* HEAD was changed to GET to cache response */
method = u->method;
} else if (plcf->method) {
if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) {
return NGX_ERROR;
}
} else {
method = r->method_name;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
if (method.len == 4
&& ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0)
{
ctx->head = 1;
}
// GET uri HTTP/1.0
// ngx_http_proxy_version 是定义的字符串常量,以0结尾,所以减去1。 CRLF同理。
len = method.len + 1 + sizeof(ngx_http_proxy_version) - 1
+ sizeof(CRLF) - 1;
escape = 0;
loc_len = 0;
unparsed_uri = 0;
if (plcf->proxy_lengths && ctx->vars.uri.len) {
// proxy_pass 指令的URL有变量 and URL指定了uri
uri_len = ctx->vars.uri.len;
} else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri && r == r->main)
{ // proxy_pass 没有指定uri and 请求的uri没有编码 and 原始请求
unparsed_uri = 1;
uri_len = r->unparsed_uri.len;
} else {
// location 有效and proxy_pass 指定了uri 则
// 取location name的长度(proxy_pass 指令所属location 的name)
loc_len = (r->valid_location && ctx->vars.uri.len) ?
plcf->location.len : 0;
if (r->quoted_uri || r->space_in_uri || r->internal) {
escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
r->uri.len - loc_len, NGX_ESCAPE_URI);
}
// proxy_pass 指定uri的长度 + 原请求uri减去location name的长度 + encode添加额外字符的长度
// + ? + args 参数长度。
uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape
+ sizeof("?") - 1 + r->args.len;
}
if (uri_len == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"zero length URI to proxy");
return NGX_ERROR;
}
len += uri_len;
ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes);
ngx_http_script_flush_no_cacheable_variables(r, headers->flushes);
if (plcf->body_lengths) {
le.ip = plcf->body_lengths->elts;
le.request = r;
le.flushed = 1;
body_len = 0;
while (*(uintptr_t *) le.ip) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
body_len += lcode(&le);
}
ctx->internal_body_length = body_len;
len += body_len;
} else if (r->headers_in.chunked && r->reading_body) {
ctx->internal_body_length = -1;
ctx->internal_chunked = 1;
} else {
ctx->internal_body_length = r->headers_in.content_length_n;
}
le.ip = headers->lengths->elts;
le.request = r;
le.flushed = 1;
while (*(uintptr_t *) le.ip) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
key_len = lcode(&le);
// 每组请求头之间通过NULL指针分割
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);
// 如果对应请求头的值为空,则跳过该请求头
if (val_len == 0) {
continue;
}
// 添加一组请求头的长度 key: value
len += key_len + sizeof(": ") - 1 + val_len + sizeof(CRLF) - 1;
}
// 原请求的头是否可以转发到后端
if (plcf->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;
}
// ngx_http_proxy_headers数组和通过proxy_set_header指令指定的头会覆盖原请求的头
if (ngx_hash_find(&headers->hash, header[i].hash,
header[i].lowcase_key, header[i].key.len))
{
continue;
}
len += header[i].key.len + sizeof(": ") - 1
+ header[i].value.len + sizeof(CRLF) - 1;
}
}
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
// 以上只是计算长度为了分配内存。以下会拼接发往上游的请求
/* the request line */
b->last = ngx_copy(b->last, method.data, method.len);
*b->last++ = ' ';
u->uri.data = b->last;
// 以下是拼接请求的uri
if (plcf->proxy_lengths && ctx->vars.uri.len) {
// proxy_pass 的URI 有变量。转发到上游的uri 只解析proxy_pass 后的。
b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
} else if (unparsed_uri) {
b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len);
} else {
if (r->valid_location) {
b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
}
if (escape) {
ngx_escape_uri(b->last, r->uri.data + loc_len,
r->uri.len - loc_len, NGX_ESCAPE_URI);
b->last += r->uri.len - loc_len + escape;
} else {
// 拼接原请求uri移除location name的前缀
b->last = ngx_copy(b->last, r->uri.data + loc_len,
r->uri.len - loc_len);
}
// 拼接参数。。。
if (r->args.len > 0) {
*b->last++ = '?';
b->last = ngx_copy(b->last, r->args.data, r->args.len);
}
}
u->uri.len = b->last - u->uri.data;
// 如果没有通过proxy_http_version指定http版本,则默认会选择http1.0
if (plcf->http_version == NGX_HTTP_VERSION_11) {
b->last = ngx_cpymem(b->last, ngx_http_proxy_version_11,
sizeof(ngx_http_proxy_version_11) - 1);
} else {
b->last = ngx_cpymem(b->last, ngx_http_proxy_version,
sizeof(ngx_http_proxy_version) - 1);
}
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = headers->values->elts;
e.pos = b->last;
e.request = r;
e.flushed = 1;
// 重置计算header头的长度
le.ip = headers->lengths->elts;
while (*(uintptr_t *) le.ip) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
// 跳过key
(void) lcode(&le);
// 计算val的长度
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);
if (val_len == 0) {
// 对应的key没有值,则跳过。不拼接该header
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;
}
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
*e.pos++ = ':'; *e.pos++ = ' ';
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.pos++ = CR; *e.pos++ = LF;
}
b->last = e.pos;
if (plcf->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;
}
if (ngx_hash_find(&headers->hash, header[i].hash,
header[i].lowcase_key, header[i].key.len))
{
continue;
}
b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
*b->last++ = ':'; *b->last++ = ' ';
b->last = ngx_copy(b->last, header[i].value.data,
header[i].value.len);
*b->last++ = CR; *b->last++ = LF;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http proxy header: \"%V: %V\"",
&header[i].key, &header[i].value);
}
}
/* add "\r\n" at the header end */
*b->last++ = CR; *b->last++ = LF;
if (plcf->body_values) {
e.ip = plcf->body_values->elts;
e.pos = b->last;
e.skip = 0;
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}
b->last = e.pos;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http proxy header:%N\"%*s\"",
(size_t) (b->last - b->pos), b->pos);
if (r->request_body_no_buffering) {
// 原请求的body不在内存中
u->request_bufs = cl;
if (ctx->internal_chunked) {
// u->output 是向上游发起请求
// 原请求是chunked类型的
u->output.output_filter = ngx_http_proxy_body_output_filter;
u->output.filter_ctx = r;
}
} else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) {
body = u->request_bufs;
u->request_bufs = cl;
while (body) {
b = ngx_alloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
ngx_memcpy(b, body->buf, sizeof(ngx_buf_t));
cl->next = ngx_alloc_chain_link(r->pool);
if (cl->next == NULL) {
return NGX_ERROR;
}
cl = cl->next;
cl->buf = b;
body = body->next;
}
} else {
u->request_bufs = cl;
}
b->flush = 1;
cl->next = NULL;
return NGX_OK;
}