Nginx之最简单的反向代理机制分析
注:当前分析基于 Nginx之搭建反向代理实现tomcat分布式集群 的配置。
1. 用到的指令
下面介绍在上面的配置中用到的指令。
upstream 指令
语法:upstream name { ... }
默认值:none
使用环境:http
该指令用于设置一组可以在 proxy_pass 和 fastcgi_pass 指令中使用的代理服务器,默认的负载均衡方式为轮询。示例如下:
upstream backend {
server backend1.example.com weight = 5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
server 指令
语法:server name [parameters]
默认值:none
使用环境:upstream
该指令用于指定后端服务器的名称和参数。服务器的名称可以是一个域名、一个 IP 地址、端口号或 UNIX Socket。
在后端服务器名称之后,可以跟以下参数:
- weight=NUMBER:设置服务器的权重,权重数值越高,被分配到的客户端请求数越多。如果没有设置权重,则为默认权重 1.
- max_fails=NUMBER:在参数 fail_timeout 指定的时间内对后端服务器请求失败的次数,如果检测到后端服务器无法连接及发生服务器错误(404错误除外),则标记为失败。如果没有设置,则为默认值 1。设为数组 0 将关闭这项检查。
- fail_timeout=TIME:在经历参数 max_fails 设置的失败次数后,暂停的时间。
- down:标记服务器为永久离线状态,用于 ip_hash 指令。
- backup:仅仅在非 backup 服务器全部宕机或繁忙的时候才启用。
proxy_pass 指令
将指定请求传递到上游服务器,格式为 URL。
2. 数据结构
2.1 ngx_http_upstream_t
typedef struct ngx_http_upstream_s ngx_http_upstream_t;
struct ngx_http_upstream_s {
/*
* 处理读事件的回调方法,每一个阶段都有不同的 read_event_handler
*/
ngx_http_upstream_handler_pt read_event_handler;
/*
* 处理写事件的回调方法,每一个阶段都有不同的 write_event_handler
*/
ngx_http_upstream_handler_pt write_event_handler;
/*
* 表示主动向上游服务器发起的连接
*/
ngx_peer_connection_t peer;
/*
* 当向下游客户端转发响应时(ngx_http_request_t 结构体中的 subrequest_in_memory
* 标志位为 0),如果打开了缓存且认为上游网速更快(conf 配置中的 buffering 标志
* 位为 1),这时会使用 pipe 成员来转发响应。在使用这种方式转发响应时,必须由
* HTTP 模块在使用 upstream 机制前构造 pipe 结构体,否则会出现严重的 coredump
* 错误.
*/
ngx_event_pipe_t *pipe;
/*
* request_bufs 以链表的方式把 ngx_buf_t 缓存区链接起来,它表示所有需要发送到
* 上游服务器的请求内容。所以,HTTP 模块实现的 create_request 回调方法就在于
* 构造 request_bufs 链表
*/
ngx_chain_t *request_bufs;
/*
* 定义了向下游发送响应的方式
*/
ngx_output_chain_ctx_t output;
ngx_chain_writer_ctx_t writer;
/*
* 使用 upstream 机制时的各种配置
*/
ngx_http_upstream_conf_t *conf;
ngx_http_upstream_srv_conf_t *upstream;
#if (NGX_HTTP_CACHE)
ngx_array_t *caches;
#endif
/*
* HTTP 模块在实现 process_header 方法时,如果希望 upstream 直接转发响应,
* 就需要把解析出的响应头部适配为 HTTP 的响应头部,同时需要把包头中的信息
* 设置到 headers_in 结构体,这样,会把 headers_in 中设置的头部添加到要发
* 送到下游客户端的响应头部 headers_out 中
*/
ngx_http_upstream_headers_in_t headers_in;
/*
* 用于解析主机域名
*/
ngx_http_upstream_resolved_t *resolved;
ngx_buf_t from_client;
/*
* 接收上游服务器响应包头的缓冲区,在不需要把响应直接转发给客户端,
* 或者 buffering 标志位为 0 的情况下转发包体时,接收包体的缓冲区
* 仍然使用 buffer。注意,如果没有自定义 input_filter 方法处理包体,
* 将会使用 buffer 存储全部的包体,这时 buffer 必须足够大,它的大小
* 由 ngx_http_upstream_conf_t 配置结构体中的 buffer_size 成员决定
*/
ngx_buf_t buffer;
/*
* 表示来自上游服务器的响应包体的长度
*/
off_t length;
/*
* out_bufs 在两种场景下有不同的意义:1. 当不需要转发包体,且使用默认
* 的 input_filter 方法(也就是 ngx_http_upstream_non_buffered_filter
* 方法)处理包体时,out_bufs 将会指向响应包体,事实上,out_bufs 链表
* 中会产生多个 ngx_buf_t 缓冲区,每个缓冲区都指向 buffer 缓存中的一部
* 分,而这里的一部分就是每次调用 recv 方法接收到的一段 TCP 流。2. 当
* 需要转发响应包体到下游时(buffering 标志位为 0,即以下游网速优先),
* 这个链表指向上一次向下游转发响应到现在这段时间内接收自上游的缓存响应
*/
ngx_chain_t *out_bufs;
/*
* 当需要转发响应包体到下游时(buffering 标志位为 0,即以下游网速优先),
* 它表示上一次向下游转发响应时没有发送完的内容
*/
ngx_chain_t *busy_bufs;
/*
* 这个链表将用于回收 out_bufs 中已经发送给下游的 ngx_buf_t 结构体,这
* 同样应用在 buffering 标志位为 0 即以下游网速优先的场景
*/
ngx_chain_t *free_bufs;
/*
* 处理包体前的初始化方法,其中 data 参数用于传递用户数据结构,它实际上
* 就是下面的 input_filter_ctx 指针
*/
ngx_int_t (*input_filter_init)(void *data);
/*
* 处理包体的方法,其中 data 参数用于传递用户数据结构,它实际上就是下面的
* input_filter_ctx 指针,而 bytes 表示本次接收到的包体长度。返回 NGX_ERROR
* 时表示处理包体错误,请求需要结束,否则都将继续 upstream 流程
*/
ngx_int_t (*input_filter)(void *data, ssize_t bytes);
/*
* 用于传递 HTTP 模块自定义的数据结构,在 input_filter_init 和 input_filter
* 方法被回调时会作为参数传递过去
*/
void *input_filter_ctx;
#if (NGX_HTTP_CACHE)
ngx_int_t (*create_key)(ngx_http_request_t *r);
#endif
/*
* HTTP 模块实现的 create_request 方法用于构造发往上游服务器的请求
*/
ngx_int_t (*create_request)(ngx_http_request_t *r);
/*
* 与上游服务器的通信失败后,如果按照重试规则还需要再次向上游服务器发起
* 连接,则会调用 reinit_request 方法
*/
ngx_int_t (*reinit_request)(ngx_http_request_t *r);
/*
* 解析上游服务器返回响应的包头,返回 NGX_AGAIN 表示包头还没有接收完整,
* 返回 NGX_HTTP_UPSTREAM_INVALID_HEADER 表示包头不合法,返回 NGX_ERROR
* 表示出现错误,返回 NGX_OK 表示解析到完整的包头.
*/
ngx_int_t (*process_header)(ngx_http_request_t *r);
void (*abort_request)(ngx_http_request_t *r);
/*
* 请求结束时会调用
*/
void (*finalize_request)(ngx_http_request_t *r,
ngx_int_t rc);
/*
* 在上游返回的响应出现 Location 或者 Refresh 头部时表示重定向时,会通过
* ngx_http_upstream_process_headers 方法调用到可由 HTTP 模块实现的
* rewrite_redirect 方法
*/
ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r,
ngx_table_elt_t *h, size_t prefix);
ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r,
ngx_table_elt_t *h);
ngx_msec_t timeout;
/*
* 用于表示上游响应的错误码、包体长度等信息
*/
ngx_http_upstream_state_t *state;
ngx_str_t method;
/*
* schema 和 uri 成员仅在记录日志时会用到,除此之外没有意义
*/
ngx_str_t schema;
ngx_str_t uri;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_str_t ssl_name;
#endif
ngx_http_cleanup_pt *cleanup;
/*
* 是否指定文件缓存路径的标志位
*/
unsigned store:1;
/*
* 是否启用文件缓存
*/
unsigned cacheable:1;
unsigned accel:1;
/*
* 是否基于 SSL 协议访问上游服务器
*/
unsigned ssl:1;
#if (NGX_HTTP_CACHE)
unsigned cache_status:3;
#endif
/*
* 向下游转发上游的响应包体时,是否开启更大的内存及临时磁盘文件用于
* 缓存来不及发送到下游的响应包体.
*/
unsigned buffering:1;
unsigned keepalive:1;
unsigned upgrade:1;
/*
* request_sent 表示是否已经向上游服务器发送了请求,当 request_sent 为
* 1 时,表示 upstream 机制已经向上游服务器发送了全部或者部分的请求。
* 事实上,这个标志位更多的是为了使用 ngx_output_chain 方法发送请求,
* 因为该方法发送请求时会自动把未发送完的 request_bufs 链表记录下来,
* 为了防止反复发送重复请求,必须有 request_sent 标志位记录是否调用过
* ngx_output_chain 方法
*/
unsigned request_sent:1;
unsigned request_body_sent:1;
/*
* 将上游服务器的响应划分为包头和包尾,如果把响应直接转发给客户端,
* header_sent 标志位表示包头是否发送,header_sent 为 1 时表示已经
* 把包头转发给客户端了。如果不转发响应到客户端,则 header_sent
* 没有意义.
*/
unsigned header_sent:1;
};
2.2 ngx_http_upstream_conf_t
typedef struct {
/*
* 当在 ngx_http_upstream_t 结构体中没有实现 resolved 成员时,upstream 这个
* 结构体才会生效,它会定义上游服务器的配置
*/
ngx_http_upstream_srv_conf_t *upstream;
/*
* 建立 TCP 连接的超时时间,实际上就是写事件添加到定时器中设置的超时时间
*/
ngx_msec_t connect_timeout;
/*
* 发送请求的超时时间。通常就是写事件添加到定时器中设置的超时时间
*/
ngx_msec_t send_timeout;
/*
* 接收响应的超时时间。通常就是读事件添加到定时器中设置的超时时间
*/
ngx_msec_t read_timeout;
ngx_msec_t next_upstream_timeout;
/*
* TCP 的 SO_SNOLOWAT 选项,表示发送缓冲区的下限
*/
size_t send_lowat;
/*
* 定义了接收头部的缓冲区分配的内存大小(ngx_http_upstream_t 中的 buffer
* 缓冲区),当不转发响应给下游或者在 buffering 标志位为 0 的情况下转发
* 响应时,它同样表示接收包体的缓冲区大小
*/
size_t buffer_size;
size_t limit_rate;
/*
* 仅当 buffering 标志位为 1,并且向下游转发响应时生效。它会设置到
* ngx_event_pipe_t 结构体的 busy_size 成员中
*/
size_t busy_buffers_size;
/*
* 在 buffering 标志位为 1 时,如果上游速度快于下游速度,将有可能把来自上游的
* 响应存储到临时文件中,而 max_temp_file_size 指定了临时文件的最大长度。实际
* 上,它将限制 ngx_event_pipe_t 结构体中的 temp_file
*/
size_t max_temp_file_size;
/*
* 表示将缓冲区中的响应写入临时文件时一次写入字符流的最大长度
*/
size_t temp_file_write_size;
size_t busy_buffers_size_conf;
size_t max_temp_file_size_conf;
size_t temp_file_write_size_conf;
/*
* 以缓存响应的方式转发上游服务器的包体时所使用的内存大小
*/
ngx_bufs_t bufs;
/*
* 针对 ngx_http_upstream_t 结构体中保存解析完的包头的 headers_in 成员,
* ignore_headers 可以按照二进制位使得 upstream 在转发包头时跳过对某些
* 头部的处理。作为 32 位整型,理论上 ignore_headers 最多可以表示 32 个
* 需要跳过不予处理的头部
*/
ngx_uint_t ignore_headers;
/*
* 以二进制位来表示一些错误码,如果处理上游响应时发现这些错误码,那么在
* 没有将响应转发给下游客户端时,将会选择下一个上游服务器来重发请求
*/
ngx_uint_t next_upstream;
/*
* 在 buffering 标志位为 1 的情况下转发响应时,将有可能把响应存放到临时文件
* 中。在 ngx_http_upstream_t 中的 store 标志位为 1 时,store_access 表示
* 所创建的目录、文件的权限.
*/
ngx_uint_t store_access;
ngx_uint_t next_upstream_tries;
/*
* 决定转发响应方式的标志位,buffering 为 1 时表示打开缓存,这时认为上游
* 的网速快于下游的网速,会尽量地在内存或者磁盘中缓存来自上游的响应;如果
* buffering 为 0,仅会开辟一块固定大小的内存块作为缓存来转发响应
*/
ngx_flag_t buffering;
ngx_flag_t request_buffering;
ngx_flag_t pass_request_headers;
ngx_flag_t pass_request_body;
/*
* 标志位,为 1 时表示与上游服务器交互时将不检查 Nginx 与下游客户端间的连接
* 是否断开。也就是说,即使下游客户端主动关闭了连接,也不会中断与上游服务器
* 间的交互.
*/
ngx_flag_t ignore_client_abort;
/*
* 当解析上游响应的包头时,如果解析后设置到 headers_in 结构体中的 status_n
* 错误码大于 400,则会试图把它与 error_page 中指定的错误码相匹配,如果匹配
* 上,则发送 error_page 中指定的响应,否则继续返回上游服务器的错误码.
*/
ngx_flag_t intercept_errors;
/*
* buffering 标志位为 1 的情况下转发响应时才有意义。这时,如果 cyclic_temp_file
* 为 1,则会试图复用临时文件中已经使用过的空间。不建议将 cyclic_temp_file
* 设为 1.
*/
ngx_flag_t cyclic_temp_file;
ngx_flag_t force_ranges;
/*
* 在 buffering 标志位为 1 的情况下转发响应时,存放临时文件的路径
*/
ngx_path_t *temp_path;
/*
* 不转发的头部。实际上是通过 ngx_http_upstream_hide_hash 方法,
* 根据 hide_headers 和 pass_headers 动态数组构造出的需要隐藏的
* HTTP 头部散列表
*/
ngx_hash_t hide_headers_hash;
/*
* 当转发上游响应头部(ngx_http_upstream_t 中 headers_in 结构体中的头部)
* 给下游客户端时,如果不希望某些头部转发给下游,就设置到 hide_headers
* 动态数组中
*/
ngx_array_t *hide_headers;
/*
* 当转发上游响应头部(ngx_http_upstream_t 中的 headers_in 结构体中的头部)
* 给下游客户端时,upstream 机制默认不会转发如 "Data"、"Server" 之类的头部,
* 如果确实希望直接转发它们到下游,就设置到 pass_headers 动态数组中
*/
ngx_array_t *pass_headers;
/*
* 连接上游服务器时使用的本机地址
*/
ngx_http_upstream_local_t *local;
#if (NGX_HTTP_CACHE)
ngx_shm_zone_t *cache_zone;
ngx_http_complex_value_t *cache_value;
ngx_uint_t cache_min_uses;
ngx_uint_t cache_use_stale;
ngx_uint_t cache_methods;
off_t cache_max_range_offset;
ngx_flag_t cache_lock;
ngx_msec_t cache_lock_timeout;
ngx_msec_t cache_lock_age;
ngx_flag_t cache_revalidate;
ngx_flag_t cache_convert_head;
ngx_flag_t cache_background_update;
ngx_array_t *cache_valid;
ngx_array_t *cache_bypass;
ngx_array_t *cache_purge;
ngx_array_t *no_cache;
#endif
/*
* 当 ngx_http_upstream_t 中的 store 标志位为 1 时,如果需要将上游的响应
* 存放到文件中,store_lengths 将表示存放路径的长度,而 store_values
* 表示存放路径
*/
ngx_array_t *store_lengths;
ngx_array_t *store_values;
#if (NGX_HTTP_CACHE)
signed cache:2;
#endif
signed store:2;
/*
* 上面的 intercept_errors 标志位定义了 400 以上的错误码将会与 error_page
* 比较后再行处理,实际上这个规则是可以有一个例外情况,如果将 intercept_404
* 标志位设为 1,当上游返回 404 时会直接转发这个错误码给下游,而不会去与
* errpr_page 进行比较.
*/
unsigned intercept_404:1;
/*
* 为 1 时,将会根据 ngx_http_upstream_t 中 headers_in 结构体里的 X-Accel-Buffering
* 头部(它的值会是 yes 和 no)来改变 buffering 标志位,当其值为 yes 时,buffering
* 标志位为 1。因此,change_buffering 为 1 时将有可能根据上游服务器返回的响应头部,
* 动态地决定是以上游网速优先还是以下游网速优先
*/
unsigned change_buffering:1;
#if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_ssl_t *ssl;
ngx_flag_t ssl_session_reuse;
ngx_http_complex_value_t *ssl_name;
ngx_flag_t ssl_server_name;
ngx_flag_t ssl_verify;
#endif
ngx_str_t module;
NGX_COMPAT_BEGIN(2)
NGX_COMPAT_END
} ngx_http_upstream_conf_t;
3. upstream 的解析
static ngx_command_t ngx_http_upstream_commands[] = {
{ ngx_string("upstream"),
NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
ngx_http_upstream,
0,
0,
NULL },
{ ngx_string("server"),
NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
ngx_http_upstream_server,
NGX_HTTP_SRV_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
3.1 ngx_http_upstream
假设 upstream{} 的配置如下:
upstream rong {
server 192.168.56.101:8080;
server 192.168.56.101:8081;
}
则调用该函数进行解析:
static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
char *rv;
void *mconf;
ngx_str_t *value;
ngx_url_t u;
ngx_uint_t m;
ngx_conf_t pcf;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx, *http_ctx;
ngx_http_upstream_srv_conf_t *uscf;
ngx_memzero(&u, sizeof(ngx_url_t));
value = cf->args->elts;
u.host = value[1];
u.no_resolve = 1;
u.no_port = 1;
/* 先从 upstreams 数组中检测是否已有相同的项存在,若没有则新创建一个
* ngx_http_upstream_srv_conf_t,该结构体代表一个上游服务器,
* 然后将其添加到 upstreams 数组中 */
uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_WEIGHT
|NGX_HTTP_UPSTREAM_MAX_CONNS
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
|NGX_HTTP_UPSTREAM_BACKUP);
if (uscf == NULL) {
return NGX_CONF_ERROR;
}
/* 对于 http{} 中的每一个 block,都要构建属于该 block 的 ngx_http_conf_ctx_t,
* 该结构体中包含三个指针数组成员:main_conf、srv_conf、loc_conf,存放着该
* block 解析到的所有配置 */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
http_ctx = cf->ctx;
/* 当前 block 的 main_conf 指向上一级的 block 的 main_conf,对于 upstream,
* 上一级的 block 即为 http{} */
ctx->main_conf = http_ctx->main_conf;
/* 下面是分配创建属于该 upstream{} 的 srv_conf 和 loc_conf */
/* the upstream{}'s srv_conf */
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/* 将上面创建的 ngx_http_upstream_srv_conf_t 的首地址存放到当前 upstream 模块
* 在 srv_conf 数组中的索引处
* 注:当前 ngx_http_upstream_module 模块没有实现 create_srv_conf 函数,因此
* 下面调用的 create_srv_conf 不会导致这里的值被覆盖了 */
ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;
uscf->srv_conf = ctx->srv_conf;
/* the upstream{}'s loc_conf */
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
for (m = 0; cf->cycle->modules[m]; m++) {
if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[m]->ctx;
if (module->create_srv_conf) {
mconf = module->create_srv_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf;
}
}
/* 创建 servers 数组,该数组中每一个元素都为 ngx_http_upstream_server_t
* 类型的结构体,该结构体代表 upstream{} 中的 server 指令 */
uscf->servers = ngx_array_create(cf->pool, 4,
sizeof(ngx_http_upstream_server_t));
if (uscf->servers == NULL) {
return NGX_CONF_ERROR;
}
/* parse inside upstream{} */
pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_UPS_CONF;
/* 这里开始解析 upstream{} */
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK) {
return rv;
}
/* 若 upstream{} 中没有配置 server 指令,则表明发生错误了 */
if (uscf->servers->nelts == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"no servers are inside upstream");
return NGX_CONF_ERROR;
}
return rv;
}
3.1.1 ngx_http_upstream_add
ngx_http_upstream_srv_conf_t *
ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
{
ngx_uint_t i;
ngx_http_upstream_server_t *us;
ngx_http_upstream_srv_conf_t *uscf, **uscfp;
ngx_http_upstream_main_conf_t *umcf;
if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {
if (ngx_parse_url(cf->pool, u) != NGX_OK) {
if (u->err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in upstream \"%V\"", u->err, &u->url);
}
return NULL;
}
}
/* 获取 ngx_http_upstream_module 模块在 main 级别的配置结构体 */
umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
/* upstreams 是一个数组,每一个数组元素的类型为 ngx_http_upstream_srv_conf_t */
uscfp = umcf->upstreams.elts;
/* 遍历该数组,检测是否已有相同的存在,若是,则返回该已添加到 upstreams 数组中的
* ngx_http_upstream_srv_conf_t 结构体 */
for (i = 0; i < umcf->upstreams.nelts; i++) {
if (uscfp[i]->host.len != u->host.len
|| ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
!= 0)
{
continue;
}
if ((flags & NGX_HTTP_UPSTREAM_CREATE)
&& (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE))
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate upstream \"%V\"", &u->host);
return NULL;
}
if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"upstream \"%V\" may not have port %d",
&u->host, u->port);
return NULL;
}
if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"upstream \"%V\" may not have port %d in %s:%ui",
&u->host, uscfp[i]->port,
uscfp[i]->file_name, uscfp[i]->line);
return NULL;
}
if (uscfp[i]->port && u->port
&& uscfp[i]->port != u->port)
{
continue;
}
if (flags & NGX_HTTP_UPSTREAM_CREATE) {
uscfp[i]->flags = flags;
uscfp[i]->port = 0;
}
return uscfp[i];
}
/* 若upstreams数组中没有,则新创建一个 ngx_http_upstream_srv_conf_t */
uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
if (uscf == NULL) {
return NULL;
}
uscf->flags = flags;
/* 该代理服务器的对应的域名,对于上面的示例则为 rong */
uscf->host = u->host;
/* 配置文件的绝对路径 */
uscf->file_name = cf->conf_file->file.name.data;
/* 记录 upstream {} 在配置文件中的行号 */
uscf->line = cf->conf_file->line;
uscf->port = u->port;
uscf->no_port = u->no_port;
if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) {
uscf->servers = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_upstream_server_t));
if (uscf->servers == NULL) {
return NULL;
}
us = ngx_array_push(uscf->servers);
if (us == NULL) {
return NULL;
}
ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
us->addrs = u->addrs;
us->naddrs = 1;
}
/* 将该新构建的 ngx_http_upstream_srv_conf_t 添加到 upstreams 数组中
* 每一个 ngx_http_upstream_srv_conf_t 代表一个代理服务器 */
uscfp = ngx_array_push(&umcf->upstreams);
if (uscfp == NULL) {
return NULL;
}
*uscfp = uscf;
return uscf;
}
3.2 ngx_http_upstream_server
server 指令就是为 upstream 定义一个服务器地址(带有端口号的域名、IP 地址,或者是 UNIX 套接字)和一个可选的参数。参数如下:
- weight: 设置一个服务器的优先级优先于其他服务器,默认为 1.
- max_fails:设置在 fail_timeout 时间之内尝试对一个服务器连接的最大次数,如果超过这个次数,那么就会被标记为 down。
- fail_timeout:在这个指定的时间内服务器必须提供响应,如果在这个时间内没有收到响应,那么服务器将会被标记为 down 状态。
- max_conns:该服务器的最大连接数。
- backup:一旦其他服务器宕机,那么有该标记的机器就会接收请求。
- down:标记为一个服务器不再接受任何请求。
static char *
ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf = conf;
time_t fail_timeout;
ngx_str_t *value, s;
ngx_url_t u;
ngx_int_t weight, max_conns, max_fails;
ngx_uint_t i;
ngx_http_upstream_server_t *us;
/* 从 servers 数组中取出一个已经分配好的 ngx_http_upstream_server_t
* 该数组中每一个 ngx_http_upstream_server_t 结构体代表一个 server */
us = ngx_array_push(uscf->servers);
if (us == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
value = cf->args->elts;
/* 默认权重为 1,值越大表示该服务器的优先级越高 */
weight = 1;
max_conns = 0;
max_fails = 1;
fail_timeout = 10;
/* 若该 server 指令有第 3 个以上的参数,则进行解析 */
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
goto not_supported;
}
weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
if (weight == NGX_ERROR || weight == 0) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) {
goto not_supported;
}
max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10);
if (max_conns == NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
goto not_supported;
}
max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
if (max_fails == NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
goto not_supported;
}
s.len = value[i].len - 13;
s.data = &value[i].data[13];
fail_timeout = ngx_parse_time(&s, 1);
if (fail_timeout == (time_t) NGX_ERROR) {
goto invalid;
}
continue;
}
if (ngx_strcmp(value[i].data, "backup") == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
goto not_supported;
}
us->backup = 1;
continue;
}
if (ngx_strcmp(value[i].data, "down") == 0) {
if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
goto not_supported;
}
us->down = 1;
continue;
}
goto invalid;
}
ngx_memzero(&u, sizeof(ngx_url_t));
/* 该服务器的 url */
u.url = value[1];
/* 若没有指定端口,则该服务器的默认端口为 80 */
u.default_port = 80;
/* 解析该 url */
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
if (u.err) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%s in upstream \"%V\"", u.err, &u.url);
}
return NGX_CONF_ERROR;
}
us->name = u.url;
us->addrs = u.addrs;
us->naddrs = u.naddrs;
us->weight = weight;
us->max_conns = max_conns;
us->max_fails = max_fails;
us->fail_timeout = fail_timeout;
return NGX_CONF_OK;
invalid:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
not_supported:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"balancing method does not support parameter \"%V\"",
&value[i]);
return NGX_CONF_ERROR;
}
3.2.1 ngx_parse_url
ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p;
size_t len;
p = u->url.data;
len = u->url.len;
/* unix */
if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
return ngx_parse_unix_domain_url(pool, u);
}
/* IPv6 */
if (len && p[0] == '[') {
return ngx_parse_inet6_url(pool, u);
}
/* IPv4 */
return ngx_parse_inet_url(pool, u);
}
3.2.2 ngx_parse_inet_url
假设当前 server 指定的 url 为: "192.168.56.101:8080"
static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
u_char *p, *host, *port, *last, *uri, *args;
size_t len;
ngx_int_t n;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
u->socklen = sizeof(struct sockaddr_in);
sin = (struct sockaddr_in *) &u->sockaddr;
sin->sin_family = AF_INET;
u->family = AF_INET;
host = u->url.data;
last = host + u->url.len;
port = ngx_strlchr(host, last, ':');
uri = ngx_strlchr(host, last, '/');
args = ngx_strlchr(host, last, '?');
if (args) {
if (uri == NULL || args < uri) {
uri = args;
}
}
if (uri) {
if (u->listen || !u->uri_part) {
u->err = "invalid host";
return NGX_ERROR;
}
u->uri.len = last - uri;
u->uri.data = uri;
last = uri;
if (uri < port) {
port = NULL;
}
}
/* 存在端口号 */
if (port) {
port++;
len = last - port;
/* 将端口号转化为整型值 */
n = ngx_atoi(port, len);
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
u->port = (in_port_t) n;
/* 将主机字节序的端口号转化为网络字节序 */
sin->sin_port = htons((in_port_t) n);
/* 存放字符串形式的端口号 */
u->port_text.len = len;
u->port_text.data = port;
last = port - 1;
} else {
if (uri == NULL) {
if (u->listen) {
/* test value as port only */
n = ngx_atoi(host, last - host);
if (n != NGX_ERROR) {
if (n < 1 || n > 65535) {
u->err = "invalid port";
return NGX_ERROR;
}
u->port = (in_port_t) n;
sin->sin_port = htons((in_port_t) n);
u->port_text.len = last - host;
u->port_text.data = host;
u->wildcard = 1;
return NGX_OK;
}
}
}
u->no_port = 1;
u->port = u->default_port;
sin->sin_port = htons(u->default_port);
}
len = last - host;
if (len == 0) {
u->err = "no host";
return NGX_ERROR;
}
u->host.len = len;
u->host.data = host;
if (u->listen && len == 1 && *host == '*') {
sin->sin_addr.s_addr = INADDR_ANY;
u->wildcard = 1;
return NGX_OK;
}
sin->sin_addr.s_addr = ngx_inet_addr(host, len);
if (sin->sin_addr.s_addr != INADDR_NONE) {
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
u->naddrs = 1;
u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
if (u->addrs == NULL) {
return NGX_ERROR;
}
sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
if (sin == NULL) {
return NGX_ERROR;
}
ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));
u->addrs[0].sockaddr = (struct sockaddr *) sin;
u->addrs[0].socklen = sizeof(struct sockaddr_in);
p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
if (p == NULL) {
return NGX_ERROR;
}
u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
&u->host, u->port) - p;
u->addrs[0].name.data = p;
return NGX_OK;
}
if (u->no_resolve) {
return NGX_OK;
}
if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
return NGX_ERROR;
}
u->family = u->addrs[0].sockaddr->sa_family;
u->socklen = u->addrs[0].socklen;
ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);
switch (u->family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) &u->sockaddr;
if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
u->wildcard = 1;
}
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) &u->sockaddr;
if (sin->sin_addr.s_addr == INADDR_ANY) {
u->wildcard = 1;
}
break;
}
return NGX_OK;
}
4. proxy_pass 的解析
该指令指定请求被传递到上游服务器,格式为 ULR。
static ngx_command_t ngx_http_proxy_commands[] = {
{ ngx_string("proxy_pass"),
NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
ngx_http_proxy_pass,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
...
};
当在配置文件中检测到有 proxy_pass 指令时,会调用 ngx_http_proxy_pass 函数进行解析:
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";
}
/* 获取 ngx_http_core_module 模块 loc 级别的配置结构体 */
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
/* 设置 handler 的回调函数 */
clcf->handler = ngx_http_proxy_handler;
/* 该 Location 的名称 */
if (clcf->name.data[clcf->name.len - 1] == '/') {
clcf->auto_redirect = 1;
}
/* value = "proxy_pass" */
value = cf->args->elts;
/* proxy_pass 的值,即要将请求传递给上游服务器的 url */
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));
/* 去掉 "http://" 或 "https://" 后的长度 */
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 指定的 url 名称,从 ngx_http_upstream_main_conf_t
* 结构体的 upstreams 数组中找到该 url 对应的 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);
/* 当前 location{} 的名称 */
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;
}
4.1 ngx_http_proxy_set_vars
static void
ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v)
{
if (u->family != AF_UNIX) {
if (u->no_port || u->port == u->default_port) {
v->host_header = u->host;
if (u->default_port == 80) {
ngx_str_set(&v->port, "80");
} else {
ngx_str_set(&v->port, "443");
}
} else {
v->host_header.len = u->host.len + 1 + u->port_text.len;
v->host_header.data = u->host.data;
v->port = u->port_text;
}
v->key_start.len += v->host_header.len;
} else {
ngx_str_set(&v->host_header, "localhost");
ngx_str_null(&v->port);
v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1;
}
v->uri = u->uri;
}
5. ngx_http_init_connection
对于 http 连接事件,当监听的客户端连接请求并接受连接后,第一个调用的函数都为该 ngx_http_init_connection 函数。该函数构建了该服务器与客户端之间的连接 ngx_connection_t 结构体,并将读事件添加到定时器和 epoll 事件监控机制中。当监听到客户端发送的数据到达时,即会调用回调函数 ngx_http_wait_request_handler 进行处理。
void
ngx_http_init_connection(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_event_t *rev;
struct sockaddr_in *sin;
ngx_http_port_t *port;
ngx_http_in_addr_t *addr;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
ngx_http_in6_addr_t *addr6;
#endif
/* 为当前的 HTTP 连接创建一个 ngx_http_connection_t 结构体,该结构体
* 代表当前的 HTTP 连接 */
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
if (hc == NULL) {
ngx_http_close_connection(c);
return;
}
/* 将 data 指针指向表示当前 HTTP 连接的 ngx_http_connection_t */
c->data = hc;
/* find the server configuration for the address:port */
/* listening:这个连接对应的 ngx_listening_t 监听对象,此连接由 listening
* 监听端口的事件建立.
* servers: 对于 HTTP 模块,该指针指向 ngx_http_port_t 结构体,该结构体
* 实际保存着当前监听端口的地址信息.
*/
port = c->listening->servers;
/* 若该端口对应主机上的多个地址 */
if (port->naddrs > 1) {
/*
* there are several addresses on this port and one of them
* is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
* is required to determine a server address
*/
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) c->local_sockaddr;
addr6 = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
}
hc->addr_conf = &addr6[i].conf;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) c->local_sockaddr;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
hc->addr_conf = &addr[i].conf;
break;
}
} else {
/* 本机的监听端口对应的 sockaddr 结构体 */
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
hc->addr_conf = &addr6[0].conf;
break;
#endif
default: /* AF_INET */
addr = port->addrs;
hc->addr_conf = &addr[0].conf;
break;
}
}
/* the default server configuration for the address:port */
hc->conf_ctx = hc->addr_conf->default_server->ctx;
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
if (ctx == NULL) {
ngx_http_close_connection(c);
return;
}
ctx->connection = c;
ctx->request = NULL;
ctx->current_request = NULL;
c->log->connection = c->number;
c->log->handler = ngx_http_log_error;
c->log->data = ctx;
c->log->action = "waiting for request";
c->log_error = NGX_ERROR_INFO;
/* 连接对应的读事件 */
rev = c->read;
/* 为该连接的读事件设置回调处理函数 */
rev->handler = ngx_http_wait_request_handler;
/* 为该连接的写事件设置回调处理函数,该函数为一个空函数,什么也不做 */
c->write->handler = ngx_http_empty_handler;
#if (NGX_HTTP_V2)
if (hc->addr_conf->http2) {
rev->handler = ngx_http_v2_init;
}
#endif
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);
if (sscf->enable || hc->addr_conf->ssl) {
c->log->action = "SSL handshaking";
if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"no \"ssl_certificate\" is defined "
"in server listening on SSL port");
ngx_http_close_connection(c);
return;
}
hc->ssl = 1;
rev->handler = ngx_http_ssl_handshake;
}
}
#endif
if (hc->addr_conf->proxy_protocol) {
hc->proxy_protocol = 1;
c->log->action = "reading PROXY protocol";
}
/*
* 标志位,为1时表示当前事件已经准备就绪,也就是说,允许这个事件的消费者模块
* 处理这个事件。在HTTP框架中,经常会检查事件的ready标志位以确定是否可以接收
* 请求或者发送响应 */
if (rev->ready) {
/* the deferred accept(), iocp */
/* 为 1,表示开启了负载均衡机制,此时不会立刻执行该读事件,而是将当前的
* 读事件添加到 ngx_posted_events 延迟执行队列中 */
if (ngx_use_accept_mutex) {
ngx_post_event(rev, &ngx_posted_events);
return;
}
/* 若没有开启负载均衡机制,则直接处理该读事件 */
rev->handler(rev);
return;
}
/* 将读事件添加到定时器中,超时时间为 post_accept_timeout 毫秒
* post_accept_timeout 在配置文件中没有配置的话,默认为 60000
* 毫秒 */
ngx_add_timer(rev, c->listening->post_accept_timeout);
/* 将该连接添加到可重用双向链表的头部 */
ngx_reusable_connection(c, 1);
/* 将该读事件添加到事件驱动模块中,这样当该事件对应的 TCP 连接上
* 一旦出现可读事件(如接收到 TCP 连接的另一端发送来的字节流)就会
* 回调该事件的 handler 方法 */
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
}
6. ngx_http_wait_request_handler
当监听到服务器与客户端之间的套接字可读,即客户端发送数据给服务器时,即会调用该 ngx_http_wait_request_handler 函数进行处理。
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
u_char *p;
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
/* 事件相关的对象。通常 data 都是指向 ngx_connection_t 连接对象。
* 开启文件异步 I/O 时,它可能会指向 ngx_event_aio_t 结构体 */
c = rev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
/* 检查该读事件是否已经超时,若超时,则关闭该连接 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
/* 标志位,为 1 时表示连接关闭 */
if (c->close) {
ngx_http_close_connection(c);
return;
}
/* 由 ngx_http_init_connection 函数知,此时该 data 指针指向
* ngx_http_connection_t 结构体 */
hc = c->data;
/* 获取该 server{} 对应的配置项结构体 */
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
size = cscf->client_header_buffer_size;
/* 用于接收、缓存客户端发来的字节流,每个事件消费模块可自由决定从连接池中
* 分配多大的空间给 buffer 这个接收缓存字段。例如,在 HTTP 模块中,它的大小
* 决定于 client_header_buffer_size 配置项 */
b = c->buffer;
/* 若没有为当前连接的接收/发送缓存分配内存 */
if (b == NULL) {
/* 分配一个 size 大小的临时缓存(表示该缓存中的数据在内存中且
* 该缓存中的数据可以被修改) */
b = ngx_create_temp_buf(c->pool, size);
if (b == NULL) {
ngx_http_close_connection(c);
return;
}
c->buffer = b;
} else if (b->start == NULL) {
b->start = ngx_palloc(c->pool, size);
if (b->start == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = b->start;
b->last = b->start;
b->end = b->last + size;
}
/* 调用接收字节流的回调函数 ngx_unix_recv 接收客户端发送的数据 */
n = c->recv(c, b->last, size);
if (n == NGX_AGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
/*
* We are trying to not hold c->buffer's memory for an idle connection.
*/
if (ngx_pfree(c->pool, b->start) == NGX_OK) {
b->start = NULL;
}
return;
}
if (n == NGX_ERROR) {
ngx_http_close_connection(c);
return;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client closed connection");
ngx_http_close_connection(c);
return;
}
/* last 指向缓存中有效数据的末尾 */
b->last += n;
if (hc->proxy_protocol) {
hc->proxy_protocol = 0;
p = ngx_proxy_protocol_read(c, b->pos, b->last);
if (p == NULL) {
ngx_http_close_connection(c);
return;
}
b->pos = p;
if (b->pos == b->last) {
c->log->action = "waiting for request";
b->pos = b->start;
b->last = b->start;
ngx_post_event(rev, &ngx_posted_events);
return;
}
}
c->log->action = "reading client request line";
/* 将该连接从 reusable_connections_queue 可重用双向链表中删除 */
ngx_reusable_connection(c, 0);
/* 为当前客户端连接创建并初始化一个 ngx_http_request_t 结构体
* 并将 c->data 指向该结构体 */
c->data = ngx_http_create_request(c);
if (c->data == NULL) {
ngx_http_close_connection(c);
return;
}
/* 设置该读事件的回调处理函数 */
rev->handler = ngx_http_process_request_line;
/* 开始解析该客户端请求的请求行 */
ngx_http_process_request_line(rev);
}
该函数主要是接收该客户端发送的数据,然后调用 ngx_http_create_request 函数为该客户端的请求创建一个 ngx_http_request_t 结构体,用于专门处理此次客户端的请求。
6.1 ngx_http_create_request
ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{
ngx_pool_t *pool;
ngx_time_t *tp;
ngx_http_request_t *r;
ngx_http_log_ctx_t *ctx;
ngx_http_connection_t *hc;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_main_conf_t *cmcf;
/* 处理请求的次数加 1 */
c->requests++;
/* 在该函数返回前,data 还是指向 ngx_http_connection_t 结构体 */
hc = c->data;
/* 获取当前 server{} 下的配置结构体 */
cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
/* 为该客户端请求分配一个内存池 */
pool = ngx_create_pool(cscf->request_pool_size, c->log);
if (pool == NULL) {
return NULL;
}
/* 从内存池 pool 中为 ngx_http_request_t 结构体分配内存 */
r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
if (r == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
/* 该请求的内存池,在 ngx_http_free_request 方法中销毁。
* 它与 ngx_connection_t 中的内存池意义不同,当请求释放时,TCP 连接
* 可能并没有关闭,这时请求的内存池会销毁,但 ngx_connection_t 的
* 内存池并不会销毁. */
r->pool = pool;
/* 代表当前 HTTP 连接 */
r->http_connection = hc;
r->signature = NGX_HTTP_MODULE;
/* 指向这个请求对应的客户端连接 */
r->connection = c;
/* 存放请求对应的存放 main 级别配置结构体的指针数组 */
r->main_conf = hc->conf_ctx->main_conf;
/* 存放请求对应的存放 srv 级别配置结构体的指针数组 */
r->srv_conf = hc->conf_ctx->srv_conf;
/* 存放请求对应的存放 loc 级别配置结构体的指针数组 */
r->loc_conf = hc->conf_ctx->loc_conf;
/* 在接收完 HTTP 头部,第一次在业务上处理 HTTP 请求时,HTTP 框架提供的
* 处理方法是 ngx_http_process_request。但如果该方法无法一次处理完该
* 请求的全部业务,在归还控制权到 epoll 事件模块后,该请求回调时,
* 将通过 ngx_http_request_handler 方法来处理,而这个方法中对于可读
* 事件的处理就是调用 read_event_handler 处理请求,也就是说,HTTP 模块
* 希望在底层处理请求的读事件时,重新实现 read_event_handler 方法 */
r->read_event_handler = ngx_http_block_reading;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_set_connection_log(r->connection, clcf->error_log);
/* header_in: 存储读取到的 HTTP 头部数据 */
r->header_in = hc->busy ? hc->busy->buf : c->buffer;
/* headers_out: HTTP 模块会把想要发送的 HTTP 响应信息放到 headers_out 中,
* 期望 HTTP 框架将 headers_out 中的成员序列化为 HTTP 响应包发送给用户 */
if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
return NULL;
}
if (ngx_list_init(&r->headers_out.trailers, r->pool, 4,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_destroy_pool(r->pool);
return NULL;
}
/* 存放指向所有的 HTTP 模块的上下文结构体的指针数组 */
r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
if (r->ctx == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
* sizeof(ngx_http_variable_value_t));
if (r->variables == NULL) {
ngx_destroy_pool(r->pool);
return NULL;
}
#if (NGX_HTTP_SSL)
if (c->ssl) {
r->main_filter_need_in_memory = 1;
}
#endif
/* 当前请求既可能是用户发来的请求,也可能是派生出的子请求,而 main
* 则标识一系列相关的派生子请求的原始请求,一般可通过 main 和当前
* 请求的地址是否相等来判断当前请求是否为用户发来的原始请求 */
r->main = r;
/* 表示当前请求的引用次数。例如,在使用 subrequest 功能时,依附在
* 这个请求上的子请求数目会返回到 count 上,每增加一个子请求,count
* 数就要加 1. 其中任何一个子请求派生出新的子请求时,对应的原始请求
*(main 指针指向的请求)的 count 值都要加 1。又如,当我们接收 HTTP
* 包体时,由于这也是一个异步调用,所有 count 上也需要加 1,这样在结束
* 请求时,就不会在 count 引用计数未清零时销毁请求。
*
* 在 HTTP 模块中每进行一类新的操作,包括为一个请求添加新的事件,或者把
* 一些已经由定时器、epoll 中移除的事件重新加入其中,都需要把这个请求的
* 引用计数加 1,这是因为需要让 HTTP 框架知道,HTTP 模块对于该请求有
* 独立的异步处理机制,将由该 HTTP 模块决定这个操作什么时候结束,防止
* 在这个操作还未结束时 HTTP 框架却把这个请求销毁了 */
r->count = 1;
tp = ngx_timeofday();
/* 当前请求初始化时的时间。start_sec是格林威治时间1970年1月1日0:0:0到当前时间的秒数。
* 如果这个请求是子请求,则该时间是子请求的生成时间;如果这个请求是用户发来的请求,
* 则是在建立起TCP连接后,第一次接收到可读事件时的时间 */
r->start_sec = tp->sec;
/* 与start_sec配合使用,表示相对于start_sec秒的毫秒偏移量 */
r->start_msec = tp->msec;
r->method = NGX_HTTP_UNKNOWN;
r->http_version = NGX_HTTP_VERSION_10;
/* ngx_http_process_request_headers 方法在接收、解析完 HTTP 请求的
* 头部后,会把解析完的每一个HTTP头部加入到 headers_in 的 headers 链表中,
* 同时会构造 headers_in 中的其他成员 */
r->headers_in.content_length_n = -1;
r->headers_in.keep_alive_n = -1;
r->headers_out.content_length_n = -1;
r->headers_out.last_modified_time = -1;
/* 表示使用 rewrite 重写 URL 的次数。因为目前最多可以更改 10 次,
* 所以 uri_changes 初始化为 11,而每重写 URL 一次就把 uri_changes
* 减 1,一旦 uri_changes 等于 0,则向用户返回失败 */
r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
/* 表示允许派生子请求的个数,当前最多可为 50,因此该值初始化为 51 */
r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
/* 设置当前请求的状态为正在读取请求的状态 */
r->http_state = NGX_HTTP_READING_REQUEST_STATE;
ctx = c->log->data;
ctx->request = r;
ctx->current_request = r;
r->log_handler = ngx_http_log_error_handler;
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
r->stat_reading = 1;
(void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
#endif
return r;
}
7. ngx_http_process_request_line
当为该客户端的请求创建好 ngx_http_request_t 结构体后,调用 ngx_http_process_request_line 函数处理该请求的请求行。
static void
ngx_http_process_request_line(ngx_event_t *rev)
{
ssize_t n;
ngx_int_t rc, rv;
ngx_str_t host;
ngx_connection_t *c;
ngx_http_request_t *r;
/* rev->data 指向当前客户端连接对象 ngx_connection_t */
c = rev->data;
/* 由前面知,当接收到客户端的请求数据并为该请求创建一个
* ngx_http_request_t 结构体后,c->data 就重新设置为指向
* 该结构体 */
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"http process request line");
/* 检测该读事件是否已经超时 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = NGX_AGAIN;
for ( ;; ) {
if (rc == NGX_AGAIN) {
/* 读取客户端的请求数据到 header_in 指向的缓存中,若该缓存中
* 已有数据,则直接返回该缓存中数据的大小 */
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
/* 该函数仅解析请求消息中的第一个行,即请求行 */
rc = ngx_http_parse_request_line(r, r->header_in);
/* 解析请求行成功 */
if (rc == NGX_OK) {
/* the request line has been parsed successfully */
/* 请求行的大小 */
r->request_line.len = r->request_end - r->request_start;
/* 指向接收缓冲区中请求行的起始地址,注意,这里并没有内存分配/拷贝 */
r->request_line.data = r->request_start;
r->request_length = r->header_in->pos - r->request_start;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http request line: \"%V\"", &r->request_line);
/* 该请求的方法名,GET 或 POST 或其他 */
r->method_name.len = r->method_end - r->request_start + 1;
r->method_name.data = r->request_line.data;
/* http_protocol.data = "HTTP/1.1" */
if (r->http_protocol.data) {
r->http_protocol.len = r->request_end - r->http_protocol.data;
}
/* 解析该请求的 uri */
if (ngx_http_process_request_uri(r) != NGX_OK) {
return;
}
if (r->host_start && r->host_end) {
host.len = r->host_end - r->host_start;
host.data = r->host_start;
rc = ngx_http_validate_host(&host, r->pool, 0);
if (rc == NGX_DECLINED) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid host in request line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
if (rc == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
return;
}
r->headers_in.server = host;
}
if (r->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0
&& ngx_http_set_virtual_server(r, &r->headers_in.server)
== NGX_ERROR)
{
return;
}
ngx_http_process_request(r);
return;
}
/* 初始化该 header_in.headers 链表 */
if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
c->log->action = "reading client request headers";
/* 上面解析完请求行后,开始处理请求消息的请求头部 */
rev->handler = ngx_http_process_request_headers;
ngx_http_process_request_headers(rev);
return;
}
if (rc != NGX_AGAIN) {
/* there was error while a request line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
if (rc == NGX_HTTP_PARSE_INVALID_VERSION) {
ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED);
} else {
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
}
return;
}
/* NGX_AGAIN: a request line parsing is still incomplete */
/* ngx_http_parse_reqeust_line 方法返回NGX_AGAIN,则表示需要接收更多的字符流,
* 这时需要对header_in缓冲区做判断,检查是否还有空闲的内存,如果还有未使用的
* 内存可以继续接收字符流,否则调用ngx_http_alloc_large_header_buffer方法
* 分配更多的接收缓冲区。到底是分配多大?这有nginx.conf文件中的
* large_client_header_buffers 配置项指定 */
if (r->header_in->pos == r->header_in->end) {
rv = ngx_http_alloc_large_header_buffer(r, 1);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
r->request_line.len = r->header_in->end - r->request_start;
r->request_line.data = r->request_start;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long URI");
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
return;
}
}
}
}
8. ngx_http_process_request_headers
当处理完请求消息的请求行后,就会调用 ngx_http_process_request_headers 函数开始处理请求消息的请求头部。
static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
u_char *p;
size_t len;
ssize_t n;
ngx_int_t rc, rv;
ngx_table_elt_t *h;
ngx_connection_t *c;
ngx_http_header_t *hh;
ngx_http_request_t *r;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_main_conf_t *cmcf;
c = rev->data;
r = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"http process request header line");
/* 检测该读事件是否超时 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
c->timedout = 1;
ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
/* 获取 main 级别的配置 */
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
rc = NGX_AGAIN;
/* 在该循环中,将 HTTP 请求头一个个的解析出来,并添加到
* headers_in.header 链表中 */
for ( ;; ) {
if (rc == NGX_AGAIN) {
/* 若当前 heder_in 指向的缓存已全部使用完,则需要分配更多的内存 */
if (r->header_in->pos == r->header_in->end) {
/* 为该缓存分配更多的内存 */
rv = ngx_http_alloc_large_header_buffer(r, 0);
if (rv == NGX_ERROR) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (rv == NGX_DECLINED) {
p = r->header_name_start;
r->lingering_close = 1;
if (p == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too large request");
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
len = r->header_in->end - p;
if (len > NGX_MAX_ERROR_STR - 300) {
len = NGX_MAX_ERROR_STR - 300;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too long header line: \"%*s...\"",
len, r->header_name_start);
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
return;
}
}
/* 读取数据,若 header_in 指向的缓存中仍然有未处理的数据,则
* 直接返回,否则需要从 socket 中读取数据 */
n = ngx_http_read_request_header(r);
if (n == NGX_AGAIN || n == NGX_ERROR) {
return;
}
}
/* the host header could change the server configuration context */
/* 获取当前 server{} 下的配置 */
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/* 该函数是将头部数据解析出一行 */
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
if (rc == NGX_OK) {
r->request_length += r->header_in->pos - r->header_name_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
/* there was error while a header line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line: \"%*s\"",
r->header_end - r->header_name_start,
r->header_name_start);
continue;
}
/* a header line has been parsed successfully */
/* 将解析出来的请求头存入到 headers_in.headers 链表中 */
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
h->hash = r->header_hash;
/* 头部名称,如 "Host" */
h->key.len = r->header_name_end - r->header_name_start;
h->key.data = r->header_name_start;
h->key.data[h->key.len] = '\0';
/* 该头部对应的值,如 "192.168.56.101" */
h->value.len = r->header_end - r->header_start;
h->value.data = r->header_start;
h->value.data[h->value.len] = '\0';
h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
if (h->lowcase_key == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if (h->key.len == r->lowcase_index) {
/* r->lowcase_header 存放的上面解析出来的 h->key.data
* 的小写字符串 */
ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
} else {
ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
}
/* 在 headers_in_hash 指向的 hash 表中寻找是否与该 lowcase_key
* 相同的项 */
hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
h->lowcase_key, h->key.len);
/* 若能找到,则调用该头部对应的的处理方法 */
if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
return;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header: \"%V: %V\"",
&h->key, &h->value);
continue;
}
/* 请求消息的请求头部解析完成 */
if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
/* a whole header has been parsed successfully */
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http header done");
r->request_length += r->header_in->pos - r->header_name_start;
r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
/* 对解析后的 HTTP 头部字段的一些处理,检测解析出来的请求头是否正确 */
rc = ngx_http_process_request_header(r);
if (rc != NGX_OK) {
return;
}
/* 在解析并处理 HTTP 的头部数据后,开始处理该 HTTP 请求 */
ngx_http_process_request(r);
return;
}
if (rc == NGX_AGAIN) {
/* a header line parsing is still not complete */
continue;
}
/* rc == NGX_HTTP_PARSE_INVALID_HEADER */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line");
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
return;
}
}
9. ngx_http_process_request
void
ngx_http_process_request(ngx_http_request_t *r)
{
ngx_connection_t *c;
c = r->connection;
#if (NGX_HTTP_SSL)
if (r->http_connection->ssl) {
long rc;
X509 *cert;
ngx_http_ssl_srv_conf_t *sscf;
if (c->ssl == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent plain HTTP request to HTTPS port");
ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
return;
}
sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
if (sscf->verify) {
rc = SSL_get_verify_result(c->ssl->connection);
if (rc != X509_V_OK
&& (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client SSL certificate verify error: (%l:%s)",
rc, X509_verify_cert_error_string(rc));
ngx_ssl_remove_cached_session(sscf->ssl.ctx,
(SSL_get0_session(c->ssl->connection)));
ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR);
return;
}
if (sscf->verify == 1) {
cert = SSL_get_peer_certificate(c->ssl->connection);
if (cert == NULL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent no required SSL certificate");
ngx_ssl_remove_cached_session(sscf->ssl.ctx,
(SSL_get0_session(c->ssl->connection)));
ngx_http_finalize_request(r, NGX_HTTPS_NO_CERT);
return;
}
X509_free(cert);
}
}
}
#endif
/* 由于现在已经开始准备调用各 HTTP 模块处理请求了,不再存在
* 接收 HTTP 请求头部超时的问题,因此需要从定时器中将当前
* 连接的读事件移除
*/
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
r->stat_reading = 0;
(void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
r->stat_writing = 1;
#endif
/* 设置该读、写事件的回调函数 */
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
/*
* 设置 ngx_http_request_t 结构体的 read_event_handler 方法为
* ngx_http_block_reading。当再次有读事件到来时,这个方法可以
* 认为不做任何事,它的意义在于,目前已经开始处理 HTTP 请求,
* 除非某个 HTTP 模块重新设置了 read_event_handler 方法,否则
* 任何读事件都将得不到处理,也可以认为读事件被阻塞了
*/
r->read_event_handler = ngx_http_block_reading;
/* 该函数确定 phase_handler 的值,即从 ngx_http_phase_engine_t 指定
* 数组的第几个回调函数开始执行,然后依次执行 HTTP 的各个阶段 */
ngx_http_handler(r);
ngx_http_run_posted_requests(c);
}
9.1 ngx_http_handler
void
ngx_http_handler(ngx_http_request_t *r)
{
ngx_http_core_main_conf_t *cmcf;
r->connection->log->action = NULL;
/* 如果 internal 标志位为 1,则表示当前需要做内部跳转,将要把
* 结构体中的 phase_handler 序号置为 server_rewrite_index. */
if (!r->internal) {
/* 当 internal 标志位为 0 时,表示不需要重定向(如刚开始处理请求时),
* 将 phase_handler 序号置为 0,意味着从 ngx_http_phase_engine_t 指定
* 数组的第一个回调方法开始执行 */
switch (r->headers_in.connection_type) {
case 0:
r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
break;
case NGX_HTTP_CONNECTION_CLOSE:
r->keepalive = 0;
break;
case NGX_HTTP_CONNECTION_KEEP_ALIVE:
/* 标志位,为 1 表示当前请求是 keepalive 请求,即长连接 */
r->keepalive = 1;
break;
}
/* 延迟关闭标志位,为 1 时表示需要延迟关闭。例如,在接收完
* HTTP 头部时如果发现包体存在,该标志位为设为 1,而放弃接收
* 包体时会设为 0 */
r->lingering_close = (r->headers_in.content_length_n > 0
|| r->headers_in.chunked);
/* 置为 0,表示从 ngx_http_phase_engine_t 指定数组的第一个回调
* 方法开始执行 */
r->phase_handler = 0;
} else {
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/* 这里,把phase_handler序号设为server_rewrite_index,这意味着
* 无论之前执行到哪一个阶段,马上都要重新从NGX_HTTP_SERVER_REWRITE_PHASE
* 阶段开始再次执行,这是Nginx的请求可以反复rewrite重定向的基础 */
r->phase_handler = cmcf->phase_engine.server_rewrite_index;
}
r->valid_location = 1;
#if (NGX_HTTP_GZIP)
r->gzip_tested = 0;
r->gzip_ok = 0;
r->gzip_vary = 0;
#endif
r->write_event_handler = ngx_http_core_run_phases;
/* 开始执行 HTTP 请求的各个阶段 */
ngx_http_core_run_phases(r);
}
10. ngx_http_core_run_phases
/* ngx_http_phase_engine_t结构体就是所有的ngx_http_phase_handler_t组成的数组 */
typedef struct {
/* handlers是由ngx_http_phase_handler_t构成的数组首地址,它表示一个请求可能
* 经历的所有ngx_http_handler_pt处理方法 */
ngx_http_phase_handler_t *handlers;
/* 表示NGX_HTTP_SERVER_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法
* 在handlers数组中的序号,用于在执行HTTP请求的任何阶段中快速跳转到
* NGX_HTTP_SERVER_REWRITE_PHASE阶段处理请求 */
ngx_uint_t server_rewrite_index;
/* 表示NGX_HTTP_REWRITE_PHASE阶段第1个ngx_http_phase_handler_t处理方法
* 在handlers数组中的序号,用于在执行HTTP请求的任何阶段中快速跳转到
* NGX_HTTP_REWRITE_PHASE阶段处理请求 */
ngx_uint_t location_rewrite_index;
}ngx_http_phase_engine_t;
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_phase_handler_t *ph;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
/* handlers 是由 ngx_http_phase_handler_t 构成的数组首地址,它表示
* 一个请求可能经历的所有 ngx_http_handler_pt 处理方法 */
ph = cmcf->phase_engine.handlers;
/*
* 在处理到某一个 HTTP 阶段时,HTTP 框架将会在 checker 方法已实现的前提下
* 首先调用 checket 方法来处理请求,而不会直接调用任何阶段中的handler方法,
* 只有在checket方法中才会去调用handler方法。因此,事实上所有的checker方法
* 都是由框架中的 ngx_http_core_module 模块实现的,且普通的 HTTP 模块无法
* 重定义 checket 方法 */
while (ph[r->phase_handler].checker) {
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
if (rc == NGX_OK) {
return;
}
}
}
下面 HTTP 各个阶段的分析可参考 HTTP 阶段执行
执行 HTTP 各个阶段中,在 NGX_HTTP_CONTENT_PHASE 阶段之前的所有阶段都可参考 HTTP 阶段执行,当到了 NGX_HTTP_CONTENT_PHASE 阶段之后,就会开始进行与 upstream 相关的处理。
11. NGX_HTTP_CONTENT_PHASE
该阶段实现的 checker 方法为 ngx_http_core_content_phase:
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
size_t root;
ngx_int_t rc;
ngx_str_t path;
/* r->content_handler 是在执行 NGX_HTTP_FIND_CONFIG_PHASE 阶段中找到匹配的
* location 后,调用 ngx_http_update_location_config 函数进行设置的,而该函数
* 中赋给 r->content_handler 的 clcf->handler 又是在解析 proxy_pass 设置的,
* 指向的回调函数为 ngx_http_proxy_handler 函数 */
if (r->content_handler) {
/* 指向的回调函数为一个空函数,什么也不处理 */
r->write_event_handler = ngx_http_request_empty_handler;
/* 因此,这里会先调用 ngx_http_proxy_handler 函数进行处理 */
ngx_http_finalize_request(r, r->content_handler(r));
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"content phase: %ui", r->phase_handler);
rc = ph->handler(r);
if (rc != NGX_DECLINED) {
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
/* rc == NGX_DECLINED */
ph++;
if (ph->checker) {
r->phase_handler++;
return NGX_AGAIN;
}
/* no content handler was found */
if (r->uri.data[r->uri.len - 1] == '/') {
if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"directory index of \"%s\" is forbidden", path.data);
}
ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
return NGX_OK;
}
Nginx之最简单的反向代理机制分析的更多相关文章
- 用nginx的反向代理机制解决前端跨域问题在nginx上部署web静态页面
用nginx的反向代理机制解决前端跨域问题在nginx上部署web静态页面 1.什么是跨域以及产生原因 跨域是指a页面想获取b页面资源,如果a.b页面的协议.域名.端口.子域名不同,或是a页面为ip地 ...
- Nginx服务器部署 负载均衡 反向代理
Nginx服务器部署负载均衡反向代理 LVS Nginx HAProxy的优缺点 三种负载均衡器的优缺点说明如下: LVS的优点: 1.抗负载能力强.工作在第4层仅作分发之用,没有流量的产生,这个特点 ...
- 使用go语言实现简单的反向代理工具激活IntelliJ和PyCharm,持续更新
最近Jetbrians系列IDE更新至2017.3版本,激活检测机制也变成了动态封禁域名,导致大部分域名激活被屏蔽了,所以找了下资料,根据ilanyu的代码,改了下地址,实现了本地反向代理激活服务器. ...
- 使用Nginx对.NetCore站点进行反向代理
前言 之前的博客我已经在Linux上部署好了.NetCore站点且通过Supervisor对站点进行了进程守护,同时也安装好了Nginx.Nginx的用处非常大,还是简单说下,它最大的功能就是方便我们 ...
- Centos 7 上使用nginx为Node.js配置反向代理时错误:(13: Permission denied) while connecting to upstream
错误来源:Centos 7 上使用nginx为Node.js配置反向代理时产生(13: Permission denied) while connecting to upstream的错误 nginx ...
- nginx配置虚拟主机、反向代理和负载均衡
为了实现这个功能,需要修改nginx的配置文件,将nginx.conf清理一下,使结构更清晰. worker_processes ; events { worker_connections ; } h ...
- Java 动态代理机制分析及扩展
Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...
- Java 动态代理机制分析及扩展,第 1 部分
Java 动态代理机制分析及扩展,第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 本文通过分析 Java 动态代理的机制和特 ...
- Java代理和动态代理机制分析和应用
本博文中项目代码已开源下载地址:GitHub Java代理和动态代理机制分析和应用 概述 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息 ...
随机推荐
- 5. Java的注释,标识符、标识符的命名规范
什么是标识符符? 凡是可以由自己命名的地方都称为修饰符. 例: 项目名 ,包名 ,类名 .方法名 2. 命名规范. ① 不可使用java关键字和保留字,但是可以包含关键字和保留字. ② ...
- Java基础第一天--继承、修饰符
继承 继承的概述: 继承是面向对象三大特征之一.可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法. //创建父类 public class Fu{ public void sh ...
- 打印n对括号的全部有效组合(左右括号正确匹配)Java实现
一.问题导出 今天在做算法题目的时候遇到了一道左右括号匹配的问题,问题如下: 二.问题思考 (1)这种要列出全部可能性的题目很容易想到由小到大去发现规律,用递归或者暴力搜索. 首先1的情况,一个括号只 ...
- deep_learning_学习资料
TensorFlow的55个经典案例:https://blog.csdn.net/xzy_thu/article/details/76220654 吴恩达机器学习: 1.序列学习:https://mo ...
- aop 的一些表达式
切入点的表达式 execution(void cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang. ...
- 学习笔记:自己编译安装OpenCV+测试opencv安装是否成功
1. 安装编译依赖的软件包 # 安装读写不同图片类型的库: sudo apt-get install libjpeg8-dev libtiff4-dev libjasper-dev libpng12- ...
- Python Scrapy爬虫框架之初次使用
此篇博客为本人对小甲鱼的课程的总结. 关于Scrapy的安装网上都有方法,这里便不再叙述. 使用Scrapy抓取一个网站一共需要四个步骤: 0.创建一个Scrapy项目: 1.定义Item容器: 2. ...
- Introduction to Go Modules
转:https://roberto.selbach.ca/intro-to-go-modules/ git init git add * git commit -am "First comm ...
- JavaScript教程——实例对象与 new 命令
典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念.所谓“类”就是对象的模板,对象就是“类”的实例.但是,JavaScript 语言的对象体系,不是基于“类”的,而 ...
- JavaScript教程——数据类型概述
简介 JavaScript 语言的每一个值,都属于某一种数据类型.JavaScript 的数据类型,共有六种.(ES6 又新增了第七种 Symbol 类型的值,本教程不涉及.) 数值(number): ...