Nginx反向代理详解

1. 相关配置

Nginx实现反向代理功能主要由proxy_pass、upstream指令实现,配置指令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

http {
...
upstream proxy {
server 127.0.0.1:8080
}

...
server {
...
location / {
proxy_pass http://proxy
}
...
}
}

具体配置可以参考ngx_http_proxy_module

2. 源码解析

Nginx实现反向代理功能的源码在src/http/modules/ngx_http_proxy_module.c,因为反向代理模块是一种upstream模块,所以还有一些基础代码在src/http/ngx_http_upstream.c中

2.1 入口函数

反向代理模块的入口函数是ngx_http_proxy_handler,我们看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
static ngx_int_t
ngx_http_proxy_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_upstream_t *u;
ngx_http_proxy_ctx_t *ctx;
ngx_http_proxy_loc_conf_t *plcf;
#if (NGX_HTTP_CACHE)
ngx_http_proxy_main_conf_t *pmcf;
#endif

if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);

plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);

u = r->upstream;

if (plcf->proxy_lengths == NULL) {
ctx->vars = plcf->vars;
u->schema = plcf->vars.schema;
#if (NGX_HTTP_SSL)
u->ssl = (plcf->upstream.ssl != NULL);
#endif

} else {
if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}

u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;

u->conf = &plcf->upstream;

#if (NGX_HTTP_CACHE)
pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module);

u->caches = &pmcf->caches;
u->create_key = ngx_http_proxy_create_key;
#endif

u->create_request = ngx_http_proxy_create_request;
u->reinit_request = ngx_http_proxy_reinit_request;
u->process_header = ngx_http_proxy_process_status_line;
u->abort_request = ngx_http_proxy_abort_request;
u->finalize_request = ngx_http_proxy_finalize_request;
r->state = 0;

if (plcf->redirects) {
u->rewrite_redirect = ngx_http_proxy_rewrite_redirect;
}

if (plcf->cookie_domains || plcf->cookie_paths) {
u->rewrite_cookie = ngx_http_proxy_rewrite_cookie;
}

u->buffering = plcf->upstream.buffering;

u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
if (u->pipe == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

u->pipe->input_filter = ngx_http_proxy_copy_filter;
u->pipe->input_ctx = r;

u->input_filter_init = ngx_http_proxy_input_filter_init;
u->input_filter = ngx_http_proxy_non_buffered_copy_filter;
u->input_filter_ctx = r;

u->accel = 1;

if (!plcf->upstream.request_buffering
&& plcf->body_values == NULL && plcf->upstream.pass_request_body
&& (!r->headers_in.chunked
|| plcf->http_version == NGX_HTTP_VERSION_11))
{
r->request_body_no_buffering = 1;
}

rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}

return NGX_DONE;
}
  1. 该函数首先调用ngx_http_upstream_create函数从内存池中创建ngx_http_upstream_s结构体;
1
2
3
if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
  1. 接下来初始化ngx_http_upstream_s各成员,应当注意ngx_http_upstream_s结构体的几个回调函数,这个是实现反向代理的重要功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
u->create_request = ngx_http_proxy_create_request;
u->reinit_request = ngx_http_proxy_reinit_request;
u->process_header = ngx_http_proxy_process_status_line;
u->abort_request = ngx_http_proxy_abort_request;
u->finalize_request = ngx_http_proxy_finalize_request;
r->state = 0;

if (plcf->redirects) {
u->rewrite_redirect = ngx_http_proxy_rewrite_redirect;
}

if (plcf->cookie_domains || plcf->cookie_paths) {
u->rewrite_cookie = ngx_http_proxy_rewrite_cookie;
}

u->buffering = plcf->upstream.buffering;

u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
if (u->pipe == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

u->pipe->input_filter = ngx_http_proxy_copy_filter;
u->pipe->input_ctx = r;

u->input_filter_init = ngx_http_proxy_input_filter_init;
u->input_filter = ngx_http_proxy_non_buffered_copy_filter;
u->input_filter_ctx = r;

u->accel = 1;

2.2 绑定入口函数

  1. nginx在解析配置文件时,遇到proxy_pass指令时将上述的入口函数绑定到content阶段的handler上,解析proxy_pass执行后的参数,确定上游服务器,那么当请求到达时,进入到入口函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    static char *
    ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
    ngx_http_proxy_loc_conf_t *plcf = conf;

    size_t add;
    u_short port;
    ngx_str_t *value, *url;
    ngx_url_t u;
    ngx_uint_t n;
    ngx_http_core_loc_conf_t *clcf;
    ngx_http_script_compile_t sc;

    if (plcf->upstream.upstream || plcf->proxy_lengths) {
    return "is duplicate";
    }

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    clcf->handler = ngx_http_proxy_handler;

    if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') {
    clcf->auto_redirect = 1;
    }

    value = cf->args->elts;

    url = &value[1];

    // 计算变量数
    n = ngx_http_script_variables_count(url);

    if (n) {

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf = cf;
    sc.source = url;
    sc.lengths = &plcf->proxy_lengths;
    sc.values = &plcf->proxy_values;
    sc.variables = n;
    sc.complete_lengths = 1;
    sc.complete_values = 1;

    if (ngx_http_script_compile(&sc) != NGX_OK) {
    return NGX_CONF_ERROR;
    }

    #if (NGX_HTTP_SSL)
    plcf->ssl = 1;
    #endif

    return NGX_CONF_OK;
    }

    if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) {
    add = 7;
    port = 80;

    } else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {

    #if (NGX_HTTP_SSL)
    plcf->ssl = 1;

    add = 8;
    port = 443;
    #else
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    "https protocol requires SSL support");
    return NGX_CONF_ERROR;
    #endif

    } else {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
    return NGX_CONF_ERROR;
    }

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.url.len = url->len - add;
    u.url.data = url->data + add;
    u.default_port = port;
    u.uri_part = 1;
    u.no_resolve = 1;

    // 根据proxy_pass指令后面的值,查找upstream块
    plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
    if (plcf->upstream.upstream == NULL) {
    return NGX_CONF_ERROR;
    }

    plcf->vars.schema.len = add;
    plcf->vars.schema.data = url->data;
    plcf->vars.key_start = plcf->vars.schema;

    ngx_http_proxy_set_vars(&u, &plcf->vars);

    plcf->location = clcf->name;

    if (clcf->named
    #if (NGX_PCRE)
    || clcf->regex
    #endif
    || clcf->noname)
    {
    if (plcf->vars.uri.len) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    "\"proxy_pass\" cannot have URI part in "
    "location given by regular expression, "
    "or inside named location, "
    "or inside \"if\" statement, "
    "or inside \"limit_except\" block");
    return NGX_CONF_ERROR;
    }

    plcf->location.len = 0;
    }

    plcf->url = *url;

    return NGX_CONF_OK;
    }


3. 注意事项

  1. 对session的处理,nginx默认使用轮询,若某个IP的请求被代理到A上,这个IP的下一个请求可能会被代理到B上,这样就会有问题,可考虑使用ip hash