Nginx(五):http反向代理的实现
上一篇nginx的文章中,我们理解了整个http正向代理的运行流程原理,主要就是事件机制接入,header解析,body解析,然后遍历各种checker,以及详细讲解了其正向代理的具体实现过程。这已经让我们对整个nginx有了较深入的了解,但nginx核心固然重要,但其扩展功能才是其吸引大家的地方。而它的扩展功能又是无穷无尽的,这是好事又是坏事,好事是功能特别多,坏事是我们不可能都能探究其每个模块。
个人觉得,nginx至少有两大必备的功能:http服务器(正向代理),http反向代理(服务转发);所以,既然前面我们弄清了其正向代理的实现,接下就是搬另一座大山的时刻了。
0. 反向代理白话
所谓反向代理,实际就是其本身不做服务器的功能,它只是起到一个代理的角色,当有人请求它的时候,它按照已知的规则将该请求转发到目标服务器上,完成工作后,它再将结果响应给到客户端。在客户端看来,nginx它就是一个目标服务器。这样做有什么好处呢?其实是非常多的,列举两个:屏蔽底层许多不同服务器的差异避免上游过多关心切换问题而导致业务重心不稳;屏蔽内网的各种防火墙限制,上游只需关注与nginx间的网络通畅性即可;统一管控上游接入切换方便;
看起来反向代理功能很棒,而且表面一看就是一转发功能,并非难事。但事实真如此吗?
要想知道难不难,我们得思考下这个代理服务器都会面临什么业务要求?
1. 得有支持转向任意服务器的能力;
2. 得有支持任意http协议的处理能力(不仅仅是get/post);
3. 得有保持请求源信息的能力(如客户端ip);
4. 得是高性能的、支持高并发的;
前几个看起来都是最基本的东西,难度并不大,后面的要求又很虚,看起来也没毛病。但是单要你实现一个高性能、高并发的系统,可能也不会很简单哦。而这里的高性能高并发是硬性要求,因为这里是被作为统一入口服务的,如果自己无法满足这要求,那么下游再强的能力也无济于事了。
另外,因作为一个通用的代理服务器,那么它一定是会随时变动的,那么如何支持动态变更配置又是一个问题。通常我们会基于数据库去实现配置,但引入数据库这个组件,将会带来很大的未知。而如果想基于其他的配置,也许就没那么方便了。
总之,好用的反向代理服务器并不多,这不是没有原因的。
1:nginx 静态文件配置
要配置反向代理服务器,只需在http server中配置 proxy_pass 代理即可。(当然了,你可以根据前缀配置许多不同的代理、server)
http {include mime.types;default_type application/octet-stream;#log_format main '$remote_addr - $remote_user [$time_local] "$request" '# '$status $body_bytes_sent "$http_referer" '# '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main;sendfile on;#tcp_nopush on;#keepalive_timeout 0;keepalive_timeout 65;#gzip on;server {listen 8085;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location /tohello {# 将请求转发给另一个服务器proxy_pass http://localhost:8081/hello;# 保持请求端信息proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}location / {root html;index index.html index.htm;}}# 后续可以添加无数个server 扩展}
配置简单吧,实际核心就三两行代码搞定:监听端口号 listen、访问域名 server_name、代理转发 proxy_pass。明显这是nginx成功的原因之一。
本文要讨论的场景是,如果我访问 http://localhost:8085/tohello/getUsers?pageNum=1&pageSize=2, 实际我想访问的是其背后的服务,那nginx将如何干成这件事呢?
2. 静态文件模块的注册
整个反向代理的功能,基本都聚合在 proxy_module中,或更准确的说是入口处理都在proxy_module中。但proxy和静态文件模块相比,又是相当复杂的。它的注册也比static_module更复杂。如下:
// http/modules/ngx_http_proxy_module.cngx_module_t ngx_http_proxy_module;// 所有支持的操作命令定义,想查看完整定义,请点击后续代码块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 },{ ngx_string("proxy_set_header"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,ngx_conf_set_keyval_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, headers_source),NULL },...ngx_null_command};static ngx_http_module_t ngx_http_proxy_module_ctx = {ngx_http_proxy_add_variables, /* preconfiguration */NULL, /* postconfiguration */ngx_http_proxy_create_main_conf, /* create main configuration */NULL, /* init main configuration */NULL, /* create server configuration */NULL, /* merge server configuration */ngx_http_proxy_create_loc_conf, /* create location configuration */ngx_http_proxy_merge_loc_conf /* merge location configuration */};// 暴露模块服务ngx_module_t ngx_http_proxy_module = {NGX_MODULE_V1,&ngx_http_proxy_module_ctx, /* module context */ngx_http_proxy_commands, /* module directives */NGX_HTTP_MODULE, /* module type */NULL, /* init master */NULL, /* init module */NULL, /* init process */NULL, /* init thread */NULL, /* exit thread */NULL, /* exit process */NULL, /* exit master */NGX_MODULE_V1_PADDING};
想要查看更多命令,请点击下面链接。
// 所有支持的操作命令定义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 },{ ngx_string("proxy_redirect"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,ngx_http_proxy_redirect,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_cookie_domain"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,ngx_http_proxy_cookie_domain,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_cookie_path"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,ngx_http_proxy_cookie_path,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_store"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_proxy_store,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_store_access"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,ngx_conf_set_access_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.store_access),NULL },{ ngx_string("proxy_buffering"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.buffering),NULL },{ ngx_string("proxy_request_buffering"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.request_buffering),NULL },{ ngx_string("proxy_ignore_client_abort"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_client_abort),NULL },{ ngx_string("proxy_bind"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,ngx_http_upstream_bind_set_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.local),NULL },{ ngx_string("proxy_socket_keepalive"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.socket_keepalive),NULL },{ ngx_string("proxy_connect_timeout"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.connect_timeout),NULL },{ ngx_string("proxy_send_timeout"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.send_timeout),NULL },{ ngx_string("proxy_send_lowat"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat),&ngx_http_proxy_lowat_post },{ ngx_string("proxy_intercept_errors"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors),NULL },{ ngx_string("proxy_set_header"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,ngx_conf_set_keyval_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, headers_source),NULL },{ ngx_string("proxy_headers_hash_max_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, headers_hash_max_size),NULL },{ ngx_string("proxy_headers_hash_bucket_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, headers_hash_bucket_size),NULL },{ ngx_string("proxy_set_body"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, body_source),NULL },{ ngx_string("proxy_method"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_set_complex_value_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, method),NULL },{ ngx_string("proxy_pass_request_headers"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_headers),NULL },{ ngx_string("proxy_pass_request_body"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_request_body),NULL },{ ngx_string("proxy_buffer_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size),NULL },{ ngx_string("proxy_read_timeout"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.read_timeout),NULL },{ ngx_string("proxy_buffers"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,ngx_conf_set_bufs_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.bufs),NULL },{ ngx_string("proxy_busy_buffers_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.busy_buffers_size_conf),NULL },{ ngx_string("proxy_force_ranges"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.force_ranges),NULL },{ ngx_string("proxy_limit_rate"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.limit_rate),NULL },#if (NGX_HTTP_CACHE){ ngx_string("proxy_cache"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_proxy_cache,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_cache_key"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_proxy_cache_key,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },{ ngx_string("proxy_cache_path"),NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,ngx_http_file_cache_set_slot,NGX_HTTP_MAIN_CONF_OFFSET,offsetof(ngx_http_proxy_main_conf_t, caches),&ngx_http_proxy_module },{ ngx_string("proxy_cache_bypass"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_http_set_predicate_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_bypass),NULL },{ ngx_string("proxy_no_cache"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_http_set_predicate_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.no_cache),NULL },{ ngx_string("proxy_cache_valid"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_http_file_cache_valid_set_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_valid),NULL },{ ngx_string("proxy_cache_min_uses"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_min_uses),NULL },{ ngx_string("proxy_cache_max_range_offset"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_off_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_max_range_offset),NULL },{ ngx_string("proxy_cache_use_stale"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_conf_set_bitmask_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_use_stale),&ngx_http_proxy_next_upstream_masks },{ ngx_string("proxy_cache_methods"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_conf_set_bitmask_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods),&ngx_http_upstream_cache_method_mask },{ ngx_string("proxy_cache_lock"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock),NULL },{ ngx_string("proxy_cache_lock_timeout"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_timeout),NULL },{ ngx_string("proxy_cache_lock_age"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_lock_age),NULL },{ ngx_string("proxy_cache_revalidate"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_revalidate),NULL },{ ngx_string("proxy_cache_convert_head"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_convert_head),NULL },{ ngx_string("proxy_cache_background_update"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_background_update),NULL },#endif{ ngx_string("proxy_temp_path"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,ngx_conf_set_path_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_path),NULL },{ ngx_string("proxy_max_temp_file_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.max_temp_file_size_conf),NULL },{ ngx_string("proxy_temp_file_write_size"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_size_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.temp_file_write_size_conf),NULL },{ ngx_string("proxy_next_upstream"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_conf_set_bitmask_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream),&ngx_http_proxy_next_upstream_masks },{ ngx_string("proxy_next_upstream_tries"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_tries),NULL },{ ngx_string("proxy_next_upstream_timeout"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_msec_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.next_upstream_timeout),NULL },{ ngx_string("proxy_pass_header"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_array_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.pass_headers),NULL },{ ngx_string("proxy_hide_header"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_array_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.hide_headers),NULL },{ ngx_string("proxy_ignore_headers"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_conf_set_bitmask_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_headers),&ngx_http_upstream_ignore_headers_masks },{ ngx_string("proxy_http_version"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_enum_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, http_version),&ngx_http_proxy_http_version },#if (NGX_HTTP_SSL){ ngx_string("proxy_ssl_session_reuse"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_session_reuse),NULL },{ ngx_string("proxy_ssl_protocols"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,ngx_conf_set_bitmask_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_protocols),&ngx_http_proxy_ssl_protocols },{ ngx_string("proxy_ssl_ciphers"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_ciphers),NULL },{ ngx_string("proxy_ssl_name"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_set_complex_value_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_name),NULL },{ ngx_string("proxy_ssl_server_name"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_server_name),NULL },{ ngx_string("proxy_ssl_verify"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify),NULL },{ ngx_string("proxy_ssl_verify_depth"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_num_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_depth),NULL },{ ngx_string("proxy_ssl_trusted_certificate"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_trusted_certificate),NULL },{ ngx_string("proxy_ssl_crl"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_crl),NULL },{ ngx_string("proxy_ssl_certificate"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate),NULL },{ ngx_string("proxy_ssl_certificate_key"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_conf_set_str_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate_key),NULL },{ ngx_string("proxy_ssl_password_file"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,ngx_http_proxy_ssl_password_file,NGX_HTTP_LOC_CONF_OFFSET,0,NULL },#endifngx_null_command};
proxy 模块有非常多命令可以操作,即可配置项非常之多:各种代理方式、自定义变量、缓存、cookie、ssl、upstream等等。所以,造就了这个模块的复杂性。
但是,我们不打算了解全部(也了解不了),我们只看大概,其如何设置header及如何转发请求即可。
3. 核心代理功能实现
proxy 代理处理算是content处理的一个分支,所以同样会被 core_content_phase 管理. 但不同的是, 它是在content_handler中被调用, 而不是作为 handler被调用.
// http/ngx_http_core_module.cngx_int_tngx_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;if (r->content_handler) {r->write_event_handler = ngx_http_request_empty_handler;// 调用 content_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;}
在上面的注册之后,会在 ngx_http_proxy_merge_loc_conf 被调用时,将handler设置为 ngx_http_proxy_handler。
// http/modules/ngx_http_proxy_module.c// 代理功能入口static ngx_int_tngx_http_proxy_handler(ngx_http_request_t *r){ngx_int_t rc;ngx_http_upstream_t *u;ngx_http_proxy_ctx_t *ctx;ngx_http_proxy_loc_conf_t *plcf;#if (NGX_HTTP_CACHE)ngx_http_proxy_main_conf_t *pmcf;#endif// 创建upstream, 即转发流准备if (ngx_http_upstream_create(r) != NGX_OK) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t));if (ctx == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}// 将创建好的上下文信息赋给 r->ctx 中// r->ctx[ngx_http_proxy_module.ctx_index] = ctx;ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);// 设置upstream 信息u = r->upstream;if (plcf->proxy_lengths == NULL) {// {key_start = {len = 21, data = 0x8000b4110 "http://localhost:8081/hello"},// schema = {len = 7, data = 0x8000b4110 "http://localhost:8081/hello"},// host_header = {len = 14, data = 0x8000b4117 "localhost:8081/hello"},// port = {len = 4, data = 0x8000b4121 "8081/hello"},// uri = {len = 6, data = 0x8000b4125 "/hello"}}ctx->vars = plcf->vars;u->schema = plcf->vars.schema;#if (NGX_HTTP_SSL)u->ssl = (plcf->upstream.ssl != NULL);#endif} else {if (ngx_http_proxy_eval(r, ctx, plcf) != NGX_OK) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}}u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;u->conf = &plcf->upstream;#if (NGX_HTTP_CACHE)pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module);u->caches = &pmcf->caches;u->create_key = ngx_http_proxy_create_key;#endifu->create_request = ngx_http_proxy_create_request;u->reinit_request = ngx_http_proxy_reinit_request;u->process_header = ngx_http_proxy_process_status_line;u->abort_request = ngx_http_proxy_abort_request;u->finalize_request = ngx_http_proxy_finalize_request;r->state = 0;// 重定向设置if (plcf->redirects) {u->rewrite_redirect = ngx_http_proxy_rewrite_redirect;}if (plcf->cookie_domains || plcf->cookie_paths) {u->rewrite_cookie = ngx_http_proxy_rewrite_cookie;}u->buffering = plcf->upstream.buffering;u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));if (u->pipe == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}u->pipe->input_filter = ngx_http_proxy_copy_filter;u->pipe->input_ctx = r;u->input_filter_init = ngx_http_proxy_input_filter_init;u->input_filter = ngx_http_proxy_non_buffered_copy_filter;u->input_filter_ctx = r;u->accel = 1;if (!plcf->upstream.request_buffering&& plcf->body_values == NULL && plcf->upstream.pass_request_body&& (!r->headers_in.chunked|| plcf->http_version == NGX_HTTP_VERSION_11)){r->request_body_no_buffering = 1;}// 重要: 读取客户端请求数据// ngx_http_upstream_init 被作为处理器传入rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {return rc;}return NGX_DONE;}
整个流程, 看起来更向是做一些准备工作, 并未发现如何进行转发. 而且其与upstream有非常大的关系, 可能我们理解了proxy 也就理解了 upstream 了. 以上是upstream的创建方式, 也是非常的简单. 所以, 更多工作被放到了 ngx_http_read_client_request_body 中去了.
// http/ngx_http_upstream.cngx_int_tngx_http_upstream_create(ngx_http_request_t *r){ngx_http_upstream_t *u;u = r->upstream;if (u && u->cleanup) {r->main->count++;ngx_http_upstream_cleanup(r);}u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));if (u == NULL) {return NGX_ERROR;}r->upstream = u;u->peer.log = r->connection->log;u->peer.log_error = NGX_ERROR_ERR;#if (NGX_HTTP_CACHE)r->cache = NULL;#endif// header 被设置为-1, 后续作区分处理u->headers_in.content_length_n = -1;u->headers_in.last_modified_time = -1;return NGX_OK;}
4. 请求转发细节实现
上面转到通用http处理模块后,又发生了一些变化。大体流程是:ngx_http_read_client_request_body -> ngx_http_upstream_init -> ngx_http_upstream_init_request -> ngx_http_upstream_connect -> ngx_http_upstream_send_request -> ngx_http_upstream_send_request_body -> ngx_handle_write_event -> 写数据到目标服务器 -> 异步等待目标服务器响应 -> 响应客户端 。
// http/ngx_http_request_body.c// 通用读取请求并处理流程ngx_int_tngx_http_read_client_request_body(ngx_http_request_t *r,ngx_http_client_body_handler_pt post_handler){size_t preread;ssize_t size;ngx_int_t rc;ngx_buf_t *b;ngx_chain_t out;ngx_http_request_body_t *rb;ngx_http_core_loc_conf_t *clcf;r->main->count++;if (r != r->main || r->request_body || r->discard_body) {r->request_body_no_buffering = 0;post_handler(r);return NGX_OK;}if (ngx_http_test_expect(r) != NGX_OK) {rc = NGX_HTTP_INTERNAL_SERVER_ERROR;goto done;}rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));if (rb == NULL) {rc = NGX_HTTP_INTERNAL_SERVER_ERROR;goto done;}/** set by ngx_pcalloc():** rb->bufs = NULL;* rb->buf = NULL;* rb->free = NULL;* rb->busy = NULL;* rb->chunked = NULL;*/rb->rest = -1;rb->post_handler = post_handler;r->request_body = rb;if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) {r->request_body_no_buffering = 0;// header为-1, proxy 会直接走此处, 即转身 upstream 处理post_handler(r);return NGX_OK;}...}// http/ngx_http_upstream.cvoidngx_http_upstream_init(ngx_http_request_t *r){ngx_connection_t *c;c = r->connection;ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,"http init upstream, client timer: %d", c->read->timer_set);#if (NGX_HTTP_V2)if (r->stream) {ngx_http_upstream_init_request(r);return;}#endifif (c->read->timer_set) {ngx_del_timer(c->read);}if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {if (!c->write->active) {if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT)== NGX_ERROR){ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}}}ngx_http_upstream_init_request(r);}// http/ngx_http_upstream.cstatic voidngx_http_upstream_init_request(ngx_http_request_t *r){ngx_str_t *host;ngx_uint_t i;ngx_resolver_ctx_t *ctx, temp;ngx_http_cleanup_t *cln;ngx_http_upstream_t *u;ngx_http_core_loc_conf_t *clcf;ngx_http_upstream_srv_conf_t *uscf, **uscfp;ngx_http_upstream_main_conf_t *umcf;if (r->aio) {return;}u = r->upstream;#if (NGX_HTTP_CACHE)if (u->conf->cache) {ngx_int_t rc;rc = ngx_http_upstream_cache(r, u);if (rc == NGX_BUSY) {r->write_event_handler = ngx_http_upstream_init_request;return;}r->write_event_handler = ngx_http_request_empty_handler;if (rc == NGX_ERROR) {ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (rc == NGX_OK) {rc = ngx_http_upstream_cache_send(r, u);if (rc == NGX_DONE) {return;}if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {rc = NGX_DECLINED;r->cached = 0;u->buffer.start = NULL;u->cache_status = NGX_HTTP_CACHE_MISS;u->request_sent = 1;}}if (rc != NGX_DECLINED) {ngx_http_finalize_request(r, rc);return;}}#endifu->store = u->conf->store;if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;}if (r->request_body) {u->request_bufs = r->request_body->bufs;}// 创建代理请求, 此处为 ngx_http_proxy_create_request// {len = 3, data = 0x8000a9700 "GET /tohello/getUsers?pageNum=1&pageSize=2 HTTP/1.1\r\nHost"}if (u->create_request(r) != NGX_OK) {ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (ngx_http_upstream_set_local(r, u, u->conf->local) != NGX_OK) {ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (u->conf->socket_keepalive) {u->peer.so_keepalive = 1;}clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);u->output.alignment = clcf->directio_alignment;u->output.pool = r->pool;u->output.bufs.num = 1;u->output.bufs.size = clcf->client_body_buffer_size;if (u->output.output_filter == NULL) {u->output.output_filter = ngx_chain_writer;u->output.filter_ctx = &u->writer;}u->writer.pool = r->pool;if (r->upstream_states == NULL) {r->upstream_states = ngx_array_create(r->pool, 1,sizeof(ngx_http_upstream_state_t));if (r->upstream_states == NULL) {ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}} else {u->state = ngx_array_push(r->upstream_states);if (u->state == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));}// 清理数据cln = ngx_http_cleanup_add(r, 0);if (cln == NULL) {ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}cln->handler = ngx_http_upstream_cleanup;cln->data = r;u->cleanup = &cln->handler;if (u->resolved == NULL) {uscf = u->conf->upstream;} else {#if (NGX_HTTP_SSL)u->ssl_name = u->resolved->host;#endifhost = &u->resolved->host;umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);uscfp = umcf->upstreams.elts;for (i = 0; i < umcf->upstreams.nelts; i++) {uscf = uscfp[i];if (uscf->host.len == host->len&& ((uscf->port == 0 && u->resolved->no_port)|| uscf->port == u->resolved->port)&& ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0){goto found;}}if (u->resolved->sockaddr) {if (u->resolved->port == 0&& u->resolved->sockaddr->sa_family != AF_UNIX){ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"no port in upstream \"%V\"", host);ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (ngx_http_upstream_create_round_robin_peer(r, u->resolved)!= NGX_OK){ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_http_upstream_connect(r, u);return;}if (u->resolved->port == 0) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"no port in upstream \"%V\"", host);ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}temp.name = *host;ctx = ngx_resolve_start(clcf->resolver, &temp);if (ctx == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (ctx == NGX_NO_RESOLVER) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"no resolver defined to resolve %V", host);ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);return;}ctx->name = *host;ctx->handler = ngx_http_upstream_resolve_handler;ctx->data = r;ctx->timeout = clcf->resolver_timeout;u->resolved->ctx = ctx;if (ngx_resolve_name(ctx) != NGX_OK) {u->resolved->ctx = NULL;ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}return;}found:if (uscf == NULL) {ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,"no upstream configuration");ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}u->upstream = uscf;#if (NGX_HTTP_SSL)u->ssl_name = uscf->host;#endif// 初始化连接点数据// 默认为: ngx_http_upstream_init_round_robin_peerif (uscf->peer.init(r, uscf) != NGX_OK) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}u->peer.start_time = ngx_current_msec;if (u->conf->next_upstream_tries&& u->peer.tries > u->conf->next_upstream_tries){u->peer.tries = u->conf->next_upstream_tries;}// 连接到upstream 中, 默认是 round-robinngx_http_upstream_connect(r, u);}// http/ngx_http_upstream.cstatic voidngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u){ngx_int_t rc;ngx_connection_t *c;r->connection->log->action = "connecting to upstream";if (u->state && u->state->response_time == (ngx_msec_t) -1) {u->state->response_time = ngx_current_msec - u->start_time;}u->state = ngx_array_push(r->upstream_states);if (u->state == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));u->start_time = ngx_current_msec;u->state->response_time = (ngx_msec_t) -1;u->state->connect_time = (ngx_msec_t) -1;u->state->header_time = (ngx_msec_t) -1;// 连接无端socket, count++rc = ngx_event_connect_peer(&u->peer);ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"http upstream connect: %i", rc);if (rc == NGX_ERROR) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}u->state->peer = u->peer.name;if (rc == NGX_BUSY) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams");ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE);return;}if (rc == NGX_DECLINED) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}/* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */c = u->peer.connection;c->requests++;c->data = r;// 读写事件处理器设置为 ngx_http_upstream_handlerc->write->handler = ngx_http_upstream_handler;c->read->handler = ngx_http_upstream_handler;u->write_event_handler = ngx_http_upstream_send_request_handler;u->read_event_handler = ngx_http_upstream_process_header;c->sendfile &= r->connection->sendfile;u->output.sendfile = c->sendfile;if (r->connection->tcp_nopush == NGX_TCP_NOPUSH_DISABLED) {c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;}if (c->pool == NULL) {/* we need separate pool here to be able to cache SSL connections */c->pool = ngx_create_pool(128, r->connection->log);if (c->pool == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}}c->log = r->connection->log;c->pool->log = c->log;c->read->log = c->log;c->write->log = c->log;/* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */u->writer.out = NULL;u->writer.last = &u->writer.out;u->writer.connection = c;u->writer.limit = 0;if (u->request_sent) {if (ngx_http_upstream_reinit(r, u) != NGX_OK) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}}if (r->request_body&& r->request_body->buf&& r->request_body->temp_file&& r == r->main){/** the r->request_body->buf can be reused for one request only,* the subrequests should allocate their own temporary bufs*/u->output.free = ngx_alloc_chain_link(r->pool);if (u->output.free == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}u->output.free->buf = r->request_body->buf;u->output.free->next = NULL;u->output.allocated = 1;r->request_body->buf->pos = r->request_body->buf->start;r->request_body->buf->last = r->request_body->buf->start;r->request_body->buf->tag = u->output.tag;}u->request_sent = 0;u->request_body_sent = 0;u->request_body_blocked = 0;// 未处理完成,等待下一次事件通知if (rc == NGX_AGAIN) {ngx_add_timer(c->write, u->conf->connect_timeout);return;}#if (NGX_HTTP_SSL)if (u->ssl && c->ssl == NULL) {ngx_http_upstream_ssl_init_connection(r, u, c);return;}#endif// 发送请求到目标端ngx_http_upstream_send_request(r, u, 1);}// http/ngx_http_upstream.cstatic voidngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u,ngx_uint_t do_write){ngx_int_t rc;ngx_connection_t *c;c = u->peer.connection;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,"http upstream send request");if (u->state->connect_time == (ngx_msec_t) -1) {u->state->connect_time = ngx_current_msec - u->start_time;}// 测试发送数据okif (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}c->log->action = "sending request to upstream";// 发送数据rc = ngx_http_upstream_send_request_body(r, u, do_write);if (rc == NGX_ERROR) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {ngx_http_upstream_finalize_request(r, u, rc);return;}if (rc == NGX_AGAIN) {if (!c->write->ready || u->request_body_blocked) {ngx_add_timer(c->write, u->conf->send_timeout);} else if (c->write->timer_set) {ngx_del_timer(c->write);}if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (c->write->ready && c->tcp_nopush == NGX_TCP_NOPUSH_SET) {if (ngx_tcp_push(c->fd) == -1) {ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,ngx_tcp_push_n " failed");ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;}return;}/* rc == NGX_OK */if (c->write->timer_set) {ngx_del_timer(c->write);}if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {if (ngx_tcp_push(c->fd) == -1) {ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,ngx_tcp_push_n " failed");ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;}if (!u->conf->preserve_output) {u->write_event_handler = ngx_http_upstream_dummy_handler;}// 写事件if (ngx_handle_write_event(c->write, 0) != NGX_OK) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (!u->request_body_sent) {u->request_body_sent = 1;if (u->header_sent) {return;}// 注册读超时定时器,避免等等超时ngx_add_timer(c->read, u->conf->read_timeout);// 如果已响应,立即处理,否则异步等待后续事件通知if (c->read->ready) {ngx_http_upstream_process_header(r, u);return;}}}
大体流程如上,以上同步处理。但比较向目标服务器写数据,等待目标服务器响应走的是异步流程,非阻塞io。所以前面注册了事件监听,将会后在后续事件就绪时再次处理。
5. 异步后续事件接入处理
首次代理处理后,将所有上下文信息,目标服务器等都已准备好了。但目标服务器或者网络会很慢,所以做了异步化处理,走的另一条路线。handler 为 ngx_http_upstream_handler 。
// http/ngx_http_upstream.cstatic voidngx_http_upstream_handler(ngx_event_t *ev){ngx_connection_t *c;ngx_http_request_t *r;ngx_http_upstream_t *u;c = ev->data;r = c->data;u = r->upstream;c = r->connection;ngx_http_set_log_request(c->log, r);ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,"http upstream request: \"%V?%V\"", &r->uri, &r->args);if (ev->delayed && ev->timedout) {ev->delayed = 0;ev->timedout = 0;}// 写就绪、读就绪if (ev->write) {u->write_event_handler(r, u);} else {u->read_event_handler(r, u);}ngx_http_run_posted_requests(c);}// 接收目标服务器返回值并处理// http/ngx_http_upstream.cstatic voidngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u){ssize_t n;ngx_int_t rc;ngx_connection_t *c;c = u->peer.connection;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,"http upstream process header");c->log->action = "reading response header from upstream";// 超时处理if (c->read->timedout) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);return;}if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}if (u->buffer.start == NULL) {u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);if (u->buffer.start == NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}u->buffer.pos = u->buffer.start;u->buffer.last = u->buffer.start;u->buffer.end = u->buffer.start + u->conf->buffer_size;u->buffer.temporary = 1;u->buffer.tag = u->output.tag;if (ngx_list_init(&u->headers_in.headers, r->pool, 8,sizeof(ngx_table_elt_t))!= NGX_OK){ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}if (ngx_list_init(&u->headers_in.trailers, r->pool, 2,sizeof(ngx_table_elt_t))!= NGX_OK){ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}#if (NGX_HTTP_CACHE)if (r->cache) {u->buffer.pos += r->cache->header_start;u->buffer.last = u->buffer.pos;}#endif}for ( ;; ) {// 循环读取目标服务器传回的数据n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);if (n == NGX_AGAIN) {#if 0ngx_add_timer(rev, u->read_timeout);#endifif (ngx_handle_read_event(c->read, 0) != NGX_OK) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}return;}if (n == 0) {ngx_log_error(NGX_LOG_ERR, c->log, 0,"upstream prematurely closed connection");}if (n == NGX_ERROR || n == 0) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);return;}u->state->bytes_received += n;u->buffer.last += n;#if 0u->valid_header_in = 0;u->peer.cached = 0;#endif// 处理header信息// 该prcocess_header由前面做好的设置, ngx_http_proxy_process_status_linerc = u->process_header(r);if (rc == NGX_AGAIN) {if (u->buffer.last == u->buffer.end) {ngx_log_error(NGX_LOG_ERR, c->log, 0,"upstream sent too big header");ngx_http_upstream_next(r, u,NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);return;}continue;}break;}if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);return;}if (rc == NGX_ERROR) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}/* rc == NGX_OK */u->state->header_time = ngx_current_msec - u->start_time;if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) {if (ngx_http_upstream_test_next(r, u) == NGX_OK) {return;}if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {return;}}// 处理header信息if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {return;}// 响应客户端ngx_http_upstream_send_response(r, u);}
比如目标服务器可写时会收到一个系统的io事件,然后触发写动作,然后发送数据到目标服务器。完成之后,注册一个读事件监听,即目标服务器处理完成之后,会响应回来。这时nginx又会收到系统的事件就绪通知,从而处理请求。此时要做的就是将目标服务器响应的数据写到客户端即可。(有可能需要添加一些自定义的信息)
// http/ngx_http_upstream.c// 发送数据到客户端static voidngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u){ssize_t n;ngx_int_t rc;ngx_event_pipe_t *p;ngx_connection_t *c;ngx_http_core_loc_conf_t *clcf;// 发送headerrc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) {ngx_http_upstream_finalize_request(r, u, rc);return;}u->header_sent = 1;if (u->upgrade) {#if (NGX_HTTP_CACHE)if (r->cache) {ngx_http_file_cache_free(r->cache, u->pipe->temp_file);}#endifngx_http_upstream_upgrade(r, u);return;}c = r->connection;if (r->header_only) {if (!u->buffering) {ngx_http_upstream_finalize_request(r, u, rc);return;}if (!u->cacheable && !u->store) {ngx_http_upstream_finalize_request(r, u, rc);return;}u->pipe->downstream_error = 1;}if (r->request_body && r->request_body->temp_file&& r == r->main && !r->preserve_body&& !u->conf->preserve_output){ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd);r->request_body->temp_file->file.fd = NGX_INVALID_FILE;}clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);if (!u->buffering) {#if (NGX_HTTP_CACHE)if (r->cache) {ngx_http_file_cache_free(r->cache, u->pipe->temp_file);}#endifif (u->input_filter == NULL) {u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;u->input_filter = ngx_http_upstream_non_buffered_filter;u->input_filter_ctx = r;}u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;r->write_event_handler =ngx_http_upstream_process_non_buffered_downstream;r->limit_rate = 0;r->limit_rate_set = 1;if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}n = u->buffer.last - u->buffer.pos;if (n) {u->buffer.last = u->buffer.pos;u->state->response_length += n;if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}ngx_http_upstream_process_non_buffered_downstream(r);} else {u->buffer.pos = u->buffer.start;u->buffer.last = u->buffer.start;if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}if (u->peer.connection->read->ready || u->length == 0) {ngx_http_upstream_process_non_buffered_upstream(r, u);}}return;}/* TODO: preallocate event_pipe bufs, look "Content-Length" */#if (NGX_HTTP_CACHE)if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) {ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd);r->cache->file.fd = NGX_INVALID_FILE;}switch (ngx_http_test_predicates(r, u->conf->no_cache)) {case NGX_ERROR:ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;case NGX_DECLINED:u->cacheable = 0;break;default: /* NGX_OK */if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {/* create cache if previously bypassed */if (ngx_http_file_cache_create(r) != NGX_OK) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}}break;}if (u->cacheable) {time_t now, valid;now = ngx_time();valid = r->cache->valid_sec;if (valid == 0) {valid = ngx_http_file_cache_valid(u->conf->cache_valid,u->headers_in.status_n);if (valid) {r->cache->valid_sec = now + valid;}}if (valid) {r->cache->date = now;r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);if (u->headers_in.status_n == NGX_HTTP_OK|| u->headers_in.status_n == NGX_HTTP_PARTIAL_CONTENT){r->cache->last_modified = u->headers_in.last_modified_time;if (u->headers_in.etag) {r->cache->etag = u->headers_in.etag->value;} else {ngx_str_null(&r->cache->etag);}} else {r->cache->last_modified = -1;ngx_str_null(&r->cache->etag);}if (ngx_http_file_cache_set_header(r, u->buffer.start) != NGX_OK) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}} else {u->cacheable = 0;}}ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,"http cacheable: %d", u->cacheable);if (u->cacheable == 0 && r->cache) {ngx_http_file_cache_free(r->cache, u->pipe->temp_file);}if (r->header_only && !u->cacheable && !u->store) {ngx_http_upstream_finalize_request(r, u, 0);return;}#endifp = u->pipe;p->output_filter = ngx_http_upstream_output_filter;p->output_ctx = r;p->tag = u->output.tag;p->bufs = u->conf->bufs;p->busy_size = u->conf->busy_buffers_size;p->upstream = u->peer.connection;p->downstream = c;p->pool = r->pool;p->log = c->log;p->limit_rate = u->conf->limit_rate;p->start_sec = ngx_time();p->cacheable = u->cacheable || u->store;p->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));if (p->temp_file == NULL) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}p->temp_file->file.fd = NGX_INVALID_FILE;p->temp_file->file.log = c->log;p->temp_file->path = u->conf->temp_path;p->temp_file->pool = r->pool;if (p->cacheable) {p->temp_file->persistent = 1;#if (NGX_HTTP_CACHE)if (r->cache && !r->cache->file_cache->use_temp_path) {p->temp_file->path = r->cache->file_cache->path;p->temp_file->file.name = r->cache->file.name;}#endif} else {p->temp_file->log_level = NGX_LOG_WARN;p->temp_file->warn = "an upstream response is buffered ""to a temporary file";}p->max_temp_file_size = u->conf->max_temp_file_size;p->temp_file_write_size = u->conf->temp_file_write_size;#if (NGX_THREADS)if (clcf->aio == NGX_HTTP_AIO_THREADS && clcf->aio_write) {p->thread_handler = ngx_http_upstream_thread_handler;p->thread_ctx = r;}#endifp->preread_bufs = ngx_alloc_chain_link(r->pool);if (p->preread_bufs == NULL) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}p->preread_bufs->buf = &u->buffer;p->preread_bufs->next = NULL;u->buffer.recycled = 1;p->preread_size = u->buffer.last - u->buffer.pos;if (u->cacheable) {p->buf_to_file = ngx_calloc_buf(r->pool);if (p->buf_to_file == NULL) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}p->buf_to_file->start = u->buffer.start;p->buf_to_file->pos = u->buffer.start;p->buf_to_file->last = u->buffer.pos;p->buf_to_file->temporary = 1;}if (ngx_event_flags & NGX_USE_IOCP_EVENT) {/* the posted aio operation may corrupt a shadow buffer */p->single_buf = 1;}/* TODO: p->free_bufs = 0 if use ngx_create_chain_of_bufs() */p->free_bufs = 1;/** event_pipe would do u->buffer.last += p->preread_size* as though these bytes were read*/u->buffer.last = u->buffer.pos;if (u->conf->cyclic_temp_file) {/** we need to disable the use of sendfile() if we use cyclic temp file* because the writing a new data may interfere with sendfile()* that uses the same kernel file pages (at least on FreeBSD)*/p->cyclic_temp_file = 1;c->sendfile = 0;} else {p->cyclic_temp_file = 0;}p->read_timeout = u->conf->read_timeout;p->send_timeout = clcf->send_timeout;p->send_lowat = clcf->send_lowat;p->length = -1;if (u->input_filter_init&& u->input_filter_init(p->input_ctx) != NGX_OK){ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}u->read_event_handler = ngx_http_upstream_process_upstream;r->write_event_handler = ngx_http_upstream_process_downstream;ngx_http_upstream_process_upstream(r, u);}// 发送客户端数据时处理// http/ngx_http_upstream.cstatic voidngx_http_upstream_process_upstream(ngx_http_request_t *r,ngx_http_upstream_t *u){ngx_event_t *rev;ngx_event_pipe_t *p;ngx_connection_t *c;c = u->peer.connection;p = u->pipe;rev = c->read;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,"http upstream process upstream");c->log->action = "reading upstream";if (rev->timedout) {p->upstream_error = 1;ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");} else {if (rev->delayed) {ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,"http upstream delayed");if (ngx_handle_read_event(rev, 0) != NGX_OK) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);}return;}// 管道式读取数据响应数据,即不会一次性输出// 而是源源不断地输出if (ngx_event_pipe(p, 0) == NGX_ABORT) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}}ngx_http_upstream_process_request(r, u);}// http/ngx_http_upstream.c// 响应客户端static voidngx_http_upstream_process_request(ngx_http_request_t *r,ngx_http_upstream_t *u){ngx_temp_file_t *tf;ngx_event_pipe_t *p;p = u->pipe;#if (NGX_THREADS)if (p->writing && !p->aio) {/** make sure to call ngx_event_pipe()* if there is an incomplete aio write*/if (ngx_event_pipe(p, 1) == NGX_ABORT) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);return;}}if (p->writing) {return;}#endifif (u->peer.connection) {if (u->store) {if (p->upstream_eof || p->upstream_done) {tf = p->temp_file;if (u->headers_in.status_n == NGX_HTTP_OK&& (p->upstream_done || p->length == -1)&& (u->headers_in.content_length_n == -1|| u->headers_in.content_length_n == tf->offset)){ngx_http_upstream_store(r, u);}}}#if (NGX_HTTP_CACHE)if (u->cacheable) {if (p->upstream_done) {ngx_http_file_cache_update(r, p->temp_file);} else if (p->upstream_eof) {tf = p->temp_file;if (p->length == -1&& (u->headers_in.content_length_n == -1|| u->headers_in.content_length_n== tf->offset - (off_t) r->cache->body_start)){ngx_http_file_cache_update(r, tf);} else {ngx_http_file_cache_free(r->cache, tf);}} else if (p->upstream_error) {ngx_http_file_cache_free(r->cache, p->temp_file);}}#endifif (p->upstream_done || p->upstream_eof || p->upstream_error) {ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"http upstream exit: %p", p->out);// 输出完成,关闭连接// 没有输出完成的情况,会等待下一次就绪事件,再进行处理// 而无需等待目标服务器完全响应,再返回本次处理// 这就是非阻塞的优势所在if (p->upstream_done|| (p->upstream_eof && p->length == -1)){// 关闭连接ngx_http_upstream_finalize_request(r, u, 0);return;}if (p->upstream_eof) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"upstream prematurely closed connection");}ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);return;}}if (p->downstream_error) {ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"http upstream downstream error");if (!u->cacheable && !u->store && u->peer.connection) {ngx_http_upstream_finalize_request(r, u, NGX_ERROR);}}}
新名词,管理式输出结果到客户端。
说了这么多,我们到底把nginx的代理功能讲清楚了吗?(请找出:header信息是在何时替换掉的呢?)
    1. 解析客户端url, 解析body; 
    2. 连接目标服务器; 
    3. 设置header并发送; 
    4. 设置body并发送; 
    5. 异步等待服务端响应; 
    6. 读取服务端响应; 
    7. 管道式输出数据到客户端;
整体上是没有问题的,但是nginx使用了大量的非阻塞io,即大量的异步处理,所以有超强悍的性能,而长期的生产验证,则给了大家非常好的保证。

腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)
面试:史上最全多线程面试题 !
最新阿里内推Java后端面试题
JVM难学?那是因为你没认真看完这篇文章

关注作者微信公众号 —《JAVA烂猪皮》
了解更多java后端架构知识以及最新面试宝典


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力
作者:等你归去来
出处:https://www.cnblogs.com/yougewe/p/13782502.html
