注:当前分析基于 Nginx之搭建反向代理实现tomcat分布式集群 的配置。
下面介绍在上面的配置中用到的指令。
语法:upstream name { ... }默认值:none使用环境:http
该指令用于设置一组可以在 proxy_pass 和 fastcgi_pass 指令中使用的代理服务器,默认的负载均衡方式为轮询。示例如下:
upstream backend {ample weight = 5;server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;server unix:/tmp/backend3;
}
语法:server name [parameters]默认值:none使用环境:upstream
该指令用于指定后端服务器的名称和参数。服务器的名称可以是一个域名、一个 IP 地址、端口号或 UNIX Socket。
在后端服务器名称之后,可以跟以下参数:
将指定请求传递到上游服务器,格式为 URL。
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;
#endifngx_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;
};
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;
#endifsigned 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;
#endifngx_str_t module;NGX_COMPAT_BEGIN(2)NGX_COMPAT_END
} ngx_http_upstream_conf_t;
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
};
假设 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 = _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__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;
}
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-<s; 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;
}
server 指令就是为 upstream 定义一个服务器地址(带有端口号的域名、IP 地址,或者是 UNIX 套接字)和一个可选的参数。参数如下:
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;
}
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);
}
假设当前 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;
#endifu->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;
#endifdefault: /* AF_INET */sin = (struct sockaddr_in *) &u->sockaddr;if (sin->sin_addr.s_addr == INADDR_ANY) {u->wildcard = 1;}break;}return NGX_OK;
}
该指令指定请求被传递到上游服务器,格式为 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;scplete_lengths = 1;scplete_values = 1;if (ngx_http_script_compile(&sc) != NGX_OK) {return NGX_CONF_ERROR;}#if (NGX_HTTP_SSL)plcf->ssl = 1;
#endifreturn NGX_CONF_OK;}if (ngx_strncasecmp(url->data, (u_char *) "", 7) == 0) {add = 7;port = 80;} else if (ngx_strncasecmp(url->data, (u_char *) "", 8) == 0) {#if (NGX_HTTP_SSL)plcf->ssl = 1;add = 8;port = 443;
#elsengx_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 = _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;
}
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;
}
对于 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;
#endifdefault: /* 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;
#endifdefault: /* 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-& == 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;}}
#endifif (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;}
}
当监听到服务器与客户端之间的套接字可读,即客户端发送数据给服务器时,即会调用该 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 结构体,用于专门处理此次客户端的请求。
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->ailers, 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-<s* 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->t_length_n = -1;r->headers_in.keep_alive_n = -1;r->t_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);
#endifreturn r;
}
当为该客户端的请求创建好 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方法* 分配更多的接收缓冲区。到底是分配多大?这有f文件中的* 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;}}}
}
当处理完请求消息的请求行后,就会调用 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] = '