参考资料<深入理解Nginx>
Nginx可以当做一个强大的反向代理服务器,其反向代理模块是基于upstream方式实现的。
upstream的使用方式
HTTP模块在处理任何一个请求时都有一个ngx_http_request_t结构对象r,而该对象又有一个ngx_http_upstream_t类型的成员upstream。
typedef struct ngx_http_request_s ngx_http_request_t struct ngx_http_request_s { ... ngx_http_upstream_t *upstream; ... };
如果要启用upstream机制,那么关键就在于如何设置r->upstream成员。
1.启用upstream机制
下图列出了使用HTTP模块启用upstream机制的示意图:
下面给出我们的mytest模块的ngx_http_mytest_handler方法中启动upstream机制的大概流程
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) { ... //创建upstream,创建之前r->upstream==NULL; ngx_http_upstream_create(r); ngx_http_upstream_t *u=r->upstream; //设置第三方服务器地址(通过设置resovled成员) u->resolved->sockaddr=..; u->resolved->socklen=...; u->resolved->naddrs=1; //设置upstream的回调方法(后面有这3个方法的描述) u->create_request=...; u->process_header=...; u->finalize_request=...; //启动upstream ngx_http_upstream_init(r); return NGX_DONE; }
2.upstream的回调方法
upstream有最常用的3个回调方法:create_request、process_header、finalize_request。
create_request回调方法
下图演示了create_request的回调场景。该回调方法一般用来创建发送给上游服务器的HTTP请求(通过设置r->upstream->request_buf)。
process_header回调方法
process_header负责解析上游服务器发来的基于TCP的包头。
当process_header回调方法返回NGX_OK后,upstream模块开始把上游的包体直接转发到下游客户端。
finalize_request回调方法
当请求结束后,将会回调finalize_request方法来释放资源。
需要的数据结构
在编写我们的mytest模块之前应该先了解一下该模块需要的一些数据结构
1.ngx_http_upstream_t结构体
typedef ngx_http_upstream_s ngx_http_upstream_t; struct ngx_http_upstream_s { ... //决定发送什么样的请求给上游服务器,在实现create_request方法时需要设置它 ngx_chain_t request_bufs; //upstream访问时的所有限制性参数 ngx_http_upstream_conf_t conf; //通过resolved可以直接指定上游服务器地址 ngx_http_upstream_resolved_t resolved; /* 用于存储接收来自上游服务器的响应内容,由于它会被复用,所以具有多种意义,如:
a.在process_header方法解析上游响应的包头时,buffer中将会保存完整的相应包头
b.当buffering标志位为0时,buffer缓冲区被用于反复接收上游的包体,进而向下游转发 */ ngx_buf_t buffer; //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);
unsigned buffering:1; ... };
2.ngx_http_upstream_conf_t结构体
在我们的mytest模块中所有的请求将共享同一个ngx_http_upstream_conf_结构体。在ngx_http_mytest_create_loc_conf方法中创建跟初始化。
typedef struct{ ngx_http_upstream_conf_t upstream; } ngx_http_mytest_conf_t;
在启动upstream前,先将ngx_http_mytest_conf_t下的upstream成员赋给r->upstream->conf成员。
3.请求上下文
因为Nginx是异步非阻塞的,导致upstream上游服务器的响应包并不是一次性就接收跟解析好的,因此需要上下文才能正确地解析upstream上游服务器的响应包。
在解析HTTP响应行时,可以使用HTTP框架提供的ngx_http_status_t结构:
typedef struct { ngx_uint_t code; ngx_uint_t count; u_char *start; u_char *end; } ngx_http_status_t;
把ngx_http_status_t结构放到上下文中,并在process_header解析响应行时使用
typedef struct { ngx_http_status_t status; ngx_str_t backendServer; } ngx_http_mytest_ctx_t;
mytest模块的完整代码
mytest模块指定的上游服务器是www.baidu.com。在nginx.conf应该这样配置
location /test {
mytest;
}
如果我们的请求是/test?lumia,该模块将会把它转化为www.baidu.com的搜索请求/s?wd=lumia,然后返回结果给客户端。
1 #include <ngx_config.h> 2 #include <ngx_core.h> 3 #include <ngx_http.h> 4 5 6 //所有的请求都将共享同一个ngx_http_upstream_conf_t结构体 7 typedef struct{ 8 ngx_http_upstream_conf_t upstream; 9 } ngx_http_mytest_conf_t; 10 11 //HTTP请求的上下文 12 typedef struct { 13 ngx_http_status_t status; 14 ngx_str_t backendServer; 15 } ngx_http_mytest_ctx_t; 16 17 //默认设置 18 static ngx_str_t ngx_http_proxy_hide_headers[] = 19 { 20 ngx_string("Date"), 21 ngx_string("Server"), 22 ngx_string("X-Pad"), 23 ngx_string("X-Accel-Expires"), 24 ngx_string("X-Accel-Redirect"), 25 ngx_string("X-Accel-Limit-Rate"), 26 ngx_string("X-Accel-Buffering"), 27 ngx_string("X-Accel-Charset"), 28 ngx_null_string 29 }; 30 31 //部分函数的声明 32 static ngx_int_t 33 mytest_upstream_process_header(ngx_http_request_t *r); 34 static char * 35 ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf); 36 static ngx_int_t 37 ngx_http_mytest_handler(ngx_http_request_t *r); 38 39 //设置配置项 40 static ngx_command_t ngx_http_mytest_commands[]={ 41 { 42 //配置项名称 43 ngx_string("mytest"), 44 //配置项类型(可出现的位置,参数的个数) 45 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, 46 //出现了name中指定的配置项后,将会调用该方法处理配置项的参数 47 ngx_http_mytest, 48 NGX_HTTP_LOC_CONF_OFFSET, 49 0, 50 NULL 51 }, 52 ngx_null_command 53 }; 54 55 //处理出现mytest配置项时处理方法(挂载handler函数) 56 static char * 57 ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf) 58 { 59 //找到mytest配置项所属的配置块 60 ngx_http_core_loc_conf_t *clcf; 61 clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module); 62 /* 63 HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时 64 如果请求的主机域名、URI与mytest配置项所在的配置块相匹配, 65 就将调用我们事先的ngx_http_mytest_handler方法处理这个请求 66 */ 67 clcf->handler=ngx_http_mytest_handler; 68 return NGX_CONF_OK; 69 } 70 71 //创建并初始化mytest模块对应的结构体ngx_http_mytest_conf; 72 static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf) 73 { 74 ngx_http_mytest_conf_t *mycf; 75 mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t)); 76 if(mycf==NULL){ 77 return NULL; 78 } 79 80 //设置ngx_http_upstream_conf_t结构中的各成员 81 mycf->upstream.connect_timeout=60000; 82 mycf->upstream.read_timeout=60000; 83 mycf->upstream.send_timeout=60000; 84 mycf->upstream.store_access=0600; 85 86 mycf->upstream.buffering=0; 87 mycf->upstream.bufs.num=8; 88 mycf->upstream.bufs.size=ngx_pagesize; 89 mycf->upstream.buffer_size=ngx_pagesize; 90 mycf->upstream.busy_buffers_size=2 * ngx_pagesize; 91 mycf->upstream.temp_file_write_size=2 * ngx_pagesize; 92 mycf->upstream.max_temp_file_size=1024 * 1024 *1024; 93 94 mycf->upstream.hide_headers=NGX_CONF_UNSET_PTR; 95 mycf->upstream.pass_headers=NGX_CONF_UNSET_PTR; 96 return mycf; 97 } 98 99 /* 100 upstream模块要求hide_headers不可以为NULL。提供了ngx_http_upstream_hide_headers_hash方法来初始化该成员, 101 但仅可用在合并配置项的方法内,因此该方法用于初始化hide_headers成员 102 */ 103 104 static char *ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf,void *parent,void *child) 105 { 106 ngx_http_mytest_conf_t *prev=(ngx_http_mytest_conf_t *)parent; 107 ngx_http_mytest_conf_t *conf=(ngx_http_mytest_conf_t *)child; 108 109 ngx_hash_init_t hash; 110 hash.max_size=100; 111 hash.bucket_size=1024; 112 hash.name="proxy_headers_hash"; 113 if(ngx_http_upstream_hide_headers_hash(cf,&conf->upstream, 114 &prev->upstream,ngx_http_proxy_hide_headers,&hash)!=NGX_OK) 115 { 116 return NGX_CONF_ERROR; 117 } 118 return NGX_CONF_OK; 119 } 120 121 //HTTP模块的定义 122 static ngx_http_module_t ngx_http_mytest_module_ctx={ 123 NULL, 124 NULL, 125 NULL, 126 NULL, 127 NULL, 128 NULL, 129 ngx_http_mytest_create_loc_conf, 130 ngx_http_mytest_merge_loc_conf 131 }; 132 133 //mytest模块的定义 134 ngx_module_t ngx_http_mytest_module={ 135 NGX_MODULE_V1, 136 //指向ngx_http_module_t结构体 137 &ngx_http_mytest_module_ctx, 138 //用来处理nginx.conf中的配置项 139 ngx_http_mytest_commands, 140 //表示该模块的类型 141 NGX_HTTP_MODULE, 142 NULL, 143 NULL, 144 NULL, 145 NULL, 146 NULL, 147 NULL, 148 NULL, 149 NGX_MODULE_V1_PADDING 150 }; 151 152 153 //创建发往google上游服务器的请求 154 static ngx_int_t 155 mytest_upstream_create_request(ngx_http_request_t *r) 156 { 157 //backendQueryLine为format字符串 158 static ngx_str_t backendQueryLine= 159 ngx_string("GET /s?wd=%V HTTP/1.1 Host:www.baidu.com Connection:close "); 160 ngx_int_t queryLineLen=backendQueryLine.len+r->args.len-2; 161 ngx_buf_t *b=ngx_create_temp_buf(r->pool,queryLineLen); 162 //last指向请求的末尾 163 b->last=b->pos+queryLineLen; 164 ngx_snprintf(b->pos,queryLineLen,(char *)backendQueryLine.data,&r->args); 165 //r->upstream->request_bufs包含要发送给上游服务器的请求 166 r->upstream->request_bufs=ngx_alloc_chain_link(r->pool); 167 if(r->upstream->request_bufs==NULL) 168 return NGX_ERROR; 169 r->upstream->request_bufs->buf=b; 170 r->upstream->request_bufs->next=NULL; 171 172 r->upstream->request_sent=0; 173 r->upstream->header_sent=0; 174 r->header_hash=1; 175 return NGX_OK; 176 } 177 178 //解析HTTP响应行 179 static ngx_int_t 180 mytest_process_status_line(ngx_http_request_t *r) 181 { 182 size_t len; 183 ngx_int_t rc; 184 ngx_http_upstream_t *u; 185 //取出请求的上下文 186 ngx_http_mytest_ctx_t *ctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module); 187 if(ctx==NULL){ 188 return NGX_ERROR; 189 } 190 191 u=r->upstream; 192 //ngx_http_parse_status_line方法可以解析HTTP响应行 193 rc=ngx_http_parse_status_line(r,&u->buffer,&ctx->status); 194 //返回NGX_AGAIN时,表示还没有解析出完整的HTTP响应行 195 if(rc==NGX_AGAIN){ 196 return rc; 197 } 198 //返回NGX_ERROR时,表示没有接收到合法的HTTP响应行 199 if(rc==NGX_ERROR){ 200 ngx_log_error(NGX_LOG_ERR,r->connection->log,0, 201 "upstream sent no valid HTTP/1.0 header"); 202 r->http_version=NGX_HTTP_VERSION_9; 203 u->state->status=NGX_HTTP_OK; 204 return NGX_OK; 205 } 206 //解析到完整的HTTP响应行 207 if(u->state){ 208 u->state->status=ctx->status.code; 209 } 210 //将解析出的信息设置到r->upstream->headers_in结构体中。 211 u->headers_in.status_n=ctx->status.code; 212 len=ctx->status.end - ctx->status.start; 213 u->headers_in.status_line.len=len; 214 u->headers_in.status_line.data=ngx_pnalloc(r->pool,len); 215 if(u->headers_in.status_line.data==NULL){ 216 return NGX_ERROR; 217 } 218 ngx_memcpy(u->headers_in.status_line.data,ctx->status.start,len); 219 220 //下一步将开始解析HTTP头部。设置process_header回调方法之后在收到的新字符流将由mytest_upstream_process_header解析 221 u->process_header=mytest_upstream_process_header; 222 return mytest_upstream_process_header(r); 223 } 224 225 //解析HTTP响应头 226 static ngx_int_t 227 mytest_upstream_process_header(ngx_http_request_t *r) 228 { 229 ngx_int_t rc; 230 ngx_table_elt_t *h; 231 ngx_http_upstream_header_t *hh; 232 ngx_http_upstream_main_conf_t *umcf; 233 234 umcf=ngx_http_get_module_main_conf(r,ngx_http_upstream_module); 235 236 //循环地解析所有的HTTP头部 237 for(;;){ 238 //HTTP框架提供的ngx_http_parse_header_line方法,用于解析HTTP头部 239 rc=ngx_http_parse_header_line(r,&r->upstream->buffer,1); 240 //返回NGX_OK时,表示解析出一行HTTP头部 241 if(rc==NGX_OK){ 242 //向headers_in.headers这个ngx_list_t链表中添加HTTP头部 243 h=ngx_list_push(&r->upstream->headers_in.headers); 244 if(h==NULL){ 245 return NGX_ERROR; 246 } 247 //构造刚刚添加的headers链表中的HTTP头部 248 h->hash=r->header_hash; 249 h->key.len=r->header_name_end - r->header_name_start; 250 h->value.len=r->header_end - r->header_start; 251 //分配存放HTTP头部的内存空间 252 h->key.data=ngx_pnalloc(r->pool, 253 h->key.len+1+h->value.len+1+h->key.len); 254 if(h->key.data==NULL){ 255 return NGX_ERROR; 256 } 257 h->value.data=h->key.data + h->key.len + 1; 258 h->lowcase_key=h->key.data+h->key.len+1+h->value.len+1; 259 260 ngx_memcpy(h->key.data,r->header_name_start,h->key.len); 261 h->key.data[h->key.len]='