nginx 访问第三方服务(1)
nginx提供了两种全异步方式来与第三方服务通信,分别是upstream和subrequest。
upstream:nginx为代理服务器,作消息透传。将第三方服务的内容原封不动的返回给用户。
subrequest:为客户请求创建子请求。访问第三方服务只是为了获取某些信息,再根据这些信息构造响应返回给用户。
upstream的使用方法
ngx_http_request_t中有一个ngx_http_upstream_t类型的成员upstream
struct ngx_http_request_s { ... ngx_http_upstream_t *upstream;
... }
nginx模块启动upstream机制的示意图
upstream执行的一般流程如下:
ngx_http_upstream_t结构体如下,重点关注已经写好注释的几个成员:
struct ngx_http_upstream_s { ngx_http_upstream_handler_pt read_event_handler; ngx_http_upstream_handler_pt write_event_handler; ngx_peer_connection_t peer; ngx_event_pipe_t *pipe; //决定发送什么样的请求给上游服务器 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_headers_in_t headers_in; //指定上游服务器地址 ngx_http_upstream_resolved_t *resolved; //存储上游服务器发来的响应 ngx_buf_t buffer; off_t length; ngx_chain_t *out_bufs; ngx_chain_t *busy_bufs; ngx_chain_t *free_bufs; ngx_int_t (*input_filter_init)(void *data); ngx_int_t (*input_filter)(void *data, ssize_t bytes); void *input_filter_ctx; #if (NGX_HTTP_CACHE) ngx_int_t (*create_key)(ngx_http_request_t *r); #endif //构造发往上游服务器的请求 ngx_int_t (*create_request)(ngx_http_request_t *r); ngx_int_t (*reinit_request)(ngx_http_request_t *r); //收到上游服务器响应后的回调方法 ngx_int_t (*process_header)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); //销毁upstream请求时调用 void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc); 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; ngx_str_t schema; ngx_str_t uri; ngx_http_cleanup_pt *cleanup; unsigned store:1; unsigned cacheable:1; unsigned accel:1; unsigned ssl:1; #if (NGX_HTTP_CACHE) unsigned cache_status:3; #endif //向客户端转发上游服务器包体时才有用,buffering=1表示使用多个缓冲区及磁盘文件转发上游包体,buffering=0表示只使用缓冲区buffer转发数据 unsigned buffering:1; unsigned keepalive:1; unsigned request_sent:1; unsigned header_sent:1; };
upstream有3种方式处理上游的包体,这由ngx_http_request_t中的subrequest_in_memory决定:
(1)subrequest_in_memory=1,upstream不转发响应包体,由input_filter方法处理
(2)subrequest_in_memory=0,buffering=1 以多个缓冲区和磁盘文件转发包体
(3)subrequest_in_memory=0,buffering=0 以固定的大小的缓冲区转发包体
upstream限制性参数ngx_http_upstream_conf_t,其中3个超时时间是必须设置的,如下:
typedef struct { ...//连接上游服务器的超时时间 ngx_msec_t connect_timeout; //发送TCP包到上游服务器的超时时间 ngx_msec_t send_timeout; //接收TCP包到上游服务器的超时时间 ngx_msec_t read_timeout;
... }ngx_http_upstream_conf_t
第三方服务器地址设置
typedef struct { ngx_str_t host; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ //地址个数 ngx_uint_t naddrs; in_addr_t *addrs; //地址 struct sockaddr *sockaddr; socklen_t socklen; ngx_resolver_ctx_t *ctx; } ngx_http_upstream_resolved_t;
设置回调方法
包括3个必须实现的回调方法
ngx_int_t (*create_request)(ngx_http_request_t *r); ngx_int_t (*process_header)(ngx_http_request_t *r); void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);
5个可选的回调方法
ngx_int_t (*input_filter_init)(void *data); ngx_int_t (*input_filter)(void *data, ssize_t bytes); ngx_int_t (*reinit_request)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix);
启动upstream机制
直接执行ngx_http_upstream_init即可启动upstream机制,例如:
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) { ... r->main->count++; ngx_http_upstream_init(r); return NGX_DONE; }
通过返回NGX_DONE让HTTP框架停在执行请求的下一个阶段。这里执行r->main->count++是在告诉HTTP框架将当前请求的引用计数加1,暂时不要销毁该请求。
回调方法执行场景时序图如下:
reinit_request回调方法
reinit_request被回调的原因只有1个:向上游服务器建立TCP连接失败,根据conf中的参数再次重连上游服务器,而这时就会调用reinit_request方法。
finalize_request回调方法
使用ngx_http_upstream_init启动upstream机制后,在各种原因导致该请求被销毁前,都会调用finalize_request中的方法。此方法必须实现。
process_header回调方法
用于解析上游服务器返回的基于HTTP的响应头部的,如果process_header返回NGX_AGAIN,意味着还没收到完整的头部,下次仍会调用process_header;如果返回NGX_OK,process_header不会再次调用
input_filter_init和input_filter回调方法
都用于处理上游服务器的响应包体,因为处理响应包体前可能需要做一些初始化工作,可以放在input_filter_init中。input_filter用于实际处理包体。以下场景比较适合重新实现input_filter_init和input_filter回调方法
(1)转发上游响应到下游时,需要做一些特殊处理
(2)无须在上、下游之间转发响应时,并不想完全接受到响应包体后才开始处理,希望能接受一部分就处理一部分,然后释放部分内存。