概述
Nginx 提供了两种全异步方式与第三方服务进行通信:upstream 和subrequest。upstream 在与第三方服务器交互时(包括建立TCP 连接、发送请求、接收响应、关闭TCP 连接),不会阻塞Nginx 进程处理其他请求。subrequest 只是分解复杂请求的一种设计模式,它可以把原始请求分解为多个子请求,使得诸多请求协同完成一个用户请求,并且每个请求只关注一个功能。subrequest 访问第三方服务最终也是基于upstream 实现的。
upstream 被定义为访问上游服务器,它把Nginx 定义为反代理服务器,首要功能是透传,其次才是以TCP 获取第三方服务器的内容。Nginx 的HTTP 反向代理模块是基于 upstream 方式实现的。subrequest 是子请求,也就是说subrequest 将会为用户创建子请求,即将一个复杂的请求分解为多个子请求,每个子请求负责一种功能项,而最初的原始请求负责构成并发送响应给用户。当subrequest 访问第三服务时,首先派生出子请求访问上游服务器,父请求在完全取得上游服务器的响应后再决定如何处理来自客户端的请求。
因此,若希望把是第三方服务的内容原封不动地返回给用户时,则使用 upstream 方式。若访问第三方服务是为了获取某些信息,再根据这些信息来构造响应并发给用户,则应使用 subrequest 方式。
upstream 使用方式
upstream 模块不产生自己的内容,而是通过请求后端服务器得到内容。Nginx 内部封装了请求并取得响应内容的整个过程,所以upstream 模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。
ngx_http_request_t 结构体
首先了解 upstream 是如何嵌入到一个请求中,这里必须从请求结构体 ngx_http_request_t 入手,在该结构体中具有一个ngx_http_upstream_t 结构体类型的成员upstream。请求结构体 ngx_http_request_t 定义在文件 src/http/ngx_http_request.h 中如下:
struct ngx_http_request_s {
uint32_t signature; /* "HTTP" */
/* 当前请求所对应的客户端连接 */
ngx_connection_t *connection;
/*
* 以下四个成员是保存模块对应的上下文结构指针;
* ctx 对应的是自定义的上下文结构指针数组,若是HTTP框架,则存储所有HTTP模块上下文结构;
* main_conf 对应的是main级别配置结构体的指针数组;
* srv_conf 对应的是srv级别配置结构体的指针数组;
* loc_conf 对应的是loc级别配置结构体的指针数组;
*/
void **ctx;
void **main_conf;
void **srv_conf;
void **loc_conf;
/*
* 以下两个是处理http请求;
* 当http头部接收完毕,第一次在业务上处理http请求时,http框架提供的处理方法是ngx_http_process_request;
* 若该方法无法一次性处理完该请求的全部业务时,当控制权归还给epoll事件模块后,若该请求再次被回调时,
* 此时,将通过ngx_http_request_handler方法进行处理,而这个方法中对于可读或可写事件的处理就是由函数
* read_event_handler或write_event_handler 来处理请求;
*/
ngx_http_event_handler_pt read_event_handler;
ngx_http_event_handler_pt write_event_handler;
#if (NGX_HTTP_CACHE)
ngx_http_cache_t *cache;
#endif
/* 若使用upstream机制,则需要以下的结构体 */
ngx_http_upstream_t *upstream;
ngx_array_t *upstream_states;
/* of ngx_http_upstream_state_t */
/* 当前请求的内存池 */
ngx_pool_t *pool;
/* 主要用于接收http请求头部内容的缓冲区 */
ngx_buf_t *header_in;
/*
* 调用函数ngx_http_request_headers 接收并解析http请求头部完毕后,
* 则把解析完成的每一个http头部加入到结构体headers_in的成员headers链表中,
* 同时初始化该结构体的其他成员;
*/
ngx_http_headers_in_t headers_in;
/*
* http模块将待发送的http响应的信息存放在headers_out中,
* 并期望http框架将headers_out中的成员序列化为http响应包体发送个客户端;
*/
ngx_http_headers_out_t headers_out;
/* 接收请求包体的数据结构 */
ngx_http_request_body_t *request_body;
/* 延迟关闭连接的时间 */
time_t lingering_time;
/* 当前请求初始化的时间 */
time_t start_sec;
/* 相对于start_sec的毫秒偏移量 */
ngx_msec_t start_msec;
/*
* 以下的 9 个成员是函数ngx_http_process_request_line在接收、解析http请求行时解析出的信息 */
ngx_uint_t method; /* 方法名称 */
ngx_uint_t http_version; /* 协议版本 */
ngx_str_t request_line; /* 请求行 */
ngx_str_t uri; /* 客户请求中的uri */
ngx_str_t args; /* uri 中的参数 */
ngx_str_t exten; /* 客户请求的文件扩展名 */
ngx_str_t unparsed_uri; /* 没经过URI 解码的原始请求 */
ngx_str_t method_name; /* 方法名称字符串 */
ngx_str_t http_protocol;/* 其data成员指向请求中http的起始地址 */
/*
* 存储待发送给客户的http响应;
* out保存着由headers_out序列化后的表示http头部的TCP流;
* 调用ngx_http_output_filter方法后,out还保存这待发送的http包体;
*/
ngx_chain_t *out;
/*
* 当前请求可能是用户请求,或是派生的子请求;
* main标识一序列相关的派生子请求的原始请求;
* 即通过main与当前请求的地址对比来判断是用户请求还是派生子请求;
*/
ngx_http_request_t *main;
/*
* 当前请求的父亲请求,但不一定是原始请求 */
ngx_http_request_t *parent;
/* 以下两个是与subrequest子请求相关的功能 */
ngx_http_postponed_request_t *postponed;
ngx_http_post_subrequest_t *post_subrequest;
/* 连接子请求的链表 */
ngx_http_posted_request_t *posted_requests;
/*
* 全局结构体ngx_http_phase_engine_t定义了一个ngx_http_phase_handler_t回调方法的数组;
* 而这里的phase_handler作为该数组的序列号表示指定数组中的回调方法,相当于数组的下标;
*/
ngx_int_t phase_handler;
/*
* 表示NGX_HTTP_CONTENT_PHASE阶段提供给http模块请求的一种方式,它指向http模块实现的请求处理方法 */
ngx_http_handler_pt content_handler;
/*
* 在NGX——HTTP_CONTENT_PHASE阶段需要判断请求是否具有访问权限时,
* 可通过access_code来传递http模块的handler回调方法的返回值来判断,
* 若为0表示具备权限,否则不具备;
*/
ngx_uint_t access_code;
ngx_http_variable_value_t *variables;
#if (NGX_PCRE)
ngx_uint_t ncaptures;
int *captures;
u_char *captures_data;
#endif
/* 限制当前请求的发送的速率 */
size_t limit_rate;
size_t limit_rate_after;
/* http响应的长度,不包括http响应头部 */
/* used to learn the Apache compatible response length without a header */
size_t header_size;
/* http请求的长度,包括http请求头部、http请求包体 */
off_t request_length;
/* 表示错误状态标志 */
ngx_uint_t err_status;
/* http 连接 */
ngx_http_connection_t *http_connection;
#if (NGX_HTTP_SPDY)
ngx_http_spdy_stream_t *spdy_stream;
#endif
/* http日志处理函数 */
ngx_http_log_handler_pt log_handler;
/* 释放资源 */
ngx_http_cleanup_t *cleanup;
/* 以下都是一些标志位 */
/* 派生子请求 */
unsigned subrequests:8;
/* 作为原始请求的引用计数,每派生一个子请求,原始请求的成员count会增加1 */
unsigned count:8;
/* 阻塞标志位,仅用于aio */
unsigned blocked:8;
/* 标志位:为1表示当前请求是异步IO方式 */
unsigned aio:1;
unsigned http_state:4;
/* URI with "/." and on Win32 with "//" */
unsigned complex_uri:1;
/* URI with "%" */
unsigned quoted_uri:1;
/* URI with "+" */
unsigned plus_in_uri:1;
/* URI with " " */
unsigned space_in_uri:1;
unsigned invalid_header:1;
unsigned add_uri_to_alias:1;
unsigned valid_location:1;
unsigned valid_unparsed_uri:1;
/* 标志位:为1表示URI已经被重写 */
unsigned uri_changed:1;
/* 表示URI被重写的次数 */
unsigned uri_changes:4;
unsigned request_body_in_single_buf:1;
unsigned request_body_in_file_only:1;
unsigned request_body_in_persistent_file:1;
unsigned request_body_in_clean_file:1;
unsigned request_body_file_group_access:1;
unsigned request_body_file_log_level:3;
/* 决定是否转发响应,若该标志位为1,表示不转发响应,否则转发响应 */
unsigned subrequest_in_memory:1;
unsigned waited:1;
#if (NGX_HTTP_CACHE)
unsigned cached:1;
#endif
#if (NGX_HTTP_GZIP)
unsigned gzip_tested:1;
unsigned gzip_ok:1;
unsigned gzip_vary:1;
#endif
unsigned proxy:1;
unsigned bypass_cache:1;
unsigned no_cache:1;
/*
* instead of using the request context data in
* ngx_http_limit_conn_module and ngx_http_limit_req_module
* we use the single bits in the request structure
*/
unsigned limit_conn_set:1;
unsigned limit_req_set:1;
#if 0