《深入理解Nginx》阅读与实践(三):使用upstream和subrequest访问第三方服务 一、upstream:以向nginx服务器的请求转化为向google服务器的搜索请求为例
本文是对陶辉《深入理解Nginx》第5章内容的梳理以及实现,代码和注释基本出自此书。
(一)模块框架
首先要明确的是,这里是编写一个使用upstream的模块,而不是编写upstream模块。因此,和HelloWorld类似,模块结构体ngx_http_mytest_module、模块上下文结构体ngx_http_mytest_module_ctx、数组ngx_http_mytest_command[]、方法ngx_http_mytest()和ngx_http_mytest_handler()的框架是不可少而且又十分相似的。如果忘记了它们之间的关系,请回顾原书或《深入理解Nginx》阅读与实践(一):Nginx安装配置与HelloWorld。
模块处理的请求是ngx_http_request_t结构对象r,它包含了一个ngx_http_upstream_t类型的成员upstream。当upstream非NULL时,将会根据其中设置的内容定制访问第三方服务的方式。而这个请求的处理是由upstream模块来完成的。从这里开始,要注意区分请求r中的upstream成员和Nginx提供的upstream模块不是一回事,而是由前者来指导后者的工作。前者的设定与开启(即告知upstream模块需要进行处理,通过ngx_http_upstream_init()实现)是由我们编写的的第三方模块(本文中是mytest)来完成的。
(二)upstream设置
upstream工作方式的配置可以通过填写由ngx_http_upstream_create(ngx_http_request_t *r)所传入的请求r中的ngx_http_upstream_t结构体来完成。这个函数成功返回时,即把请求r中的upstream设置为非NULL。ngx_http_upstream_t结构体主要包含了一下几个成员,由于原书对这里模块编写所需要用到的成员已做详细介绍(暂时用不到的成员在12章介绍),这里只做一个部分的概括:
typedef ngx_http_upstream_s ngx_http_upstream_t; sturct ngx_http_upstream_s { ... ngx_chain_t request_bufs;//发给上游服务器的请求,由create_request()完成 ngx_http_upstream_conf_t conf;//超时时间等限制性参数 ngx_http_upstream_resolved_t resolved;//用于直接指定的上游服务器地址 //设定方法请见mytest模块的ngx_http_mytest_handler()方法 /* 3个必须实现的回调方法 */ ngx_int_t (*create_request)(ngx_http_request_t *r);//构造向上游服务器发送的请求内容。调用mytest时,只调用一次 ngx_int_t (*process_header)(ngx_http_request_t *r);//收到上游服务器后对包头进行处理的方法 void (*finalize_request) (ngx_http_request_t *r, ngx_int_t rc);//销毁upstream请求时调用 /* 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功能时,除了需要按HelloWorld编写自己的模块和提供处理配置项的方法ngx_http_mytest_create_loc_conf()、ngx_http_mytest_merge_loc_conf()外,还需要填写ngx_http_upstream_t结构体并实现3个必备的回调方法。要注意的是,这些回调方法都是由模块的编写者提供、再由upstream模块来调用的。
(三)配置项获取
原书例子是将访问的URL请求/test?lumia转化成对www.google.com的搜索请求/search?q=lumia。为了简化,大部分参数采用硬编码的形式nginx.conf的添加的内容和以前一样:
location /test {
mytest;
}
typedef struct { ngx_http_upstream_conf_t upstream; } ngx_http_mytest_conf_t; static void* ngx_http_mytest_create_loc_conf(ngx_conf_t *cf) { ngx_http_mytest_conf_t *mycf; mycf = (ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t)); if(mycf == NULL) { return NULL; } mycf->upstream.connect_timeout = 60000; mycf->upstream.send_timeout = 60000; mycf->upstream.read_timeout = 60000; mycf->upstream.store_access = 0600; mycf->upstream.buffering = 0; mycf->upstream.bufs.num = 8; mycf->upstream.bufs.size =ngx_pagesize; mycf->upstream.buffer_size = ngx_pagesize; mycf->upstream.busy_buffers_size = 2*ngx_pagesize; mycf->upstream.max_temp_file_size = 1024*1024*1024; mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR; mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR; return mycf; } static char* ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_mytest_conf_t *prev = (ngx_http_mytest_conf_t *)parent; ngx_http_mytest_conf_t *conf = (ngx_http_mytest_conf_t *)child; //ngx_conf_merge_str_value(conf->my_str,prev->my_str,"defaultstr"); ngx_hash_init_t hash; hash.max_size = 100; hash.bucket_size = 1024; hash.name = "proxy_headers_hash"; if(ngx_http_upstream_hide_headers_hash(cf,&conf->upstream, &prev->upstream,ngx_http_proxy_hide_headers,&hash)!=NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; }
另外根据作者网页上的源码,需要补充上ngx_http_proxy_hide_headers作为默认设置:
static ngx_str_t ngx_http_proxy_hide_headers[] = { ngx_string("Date"), ngx_string("Server"), ngx_string("X-Pad"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string };
(四)3个必备的回调函数的实现
首先定义ngx_http_mytest_ctx_t结构体用于保存process_header()方法的解析状态,注意结构体的第二个成员原书没有写,需要补充上。
typedef struct { ngx_http_status_t status; ngx_str_t backendServer; } ngx_http_mytest_ctx_t;
原书上3个回调函数的代码如下,详细的注释请参考原书:
static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r) { static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1 Host: www.google.com Connection:close "); ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2; ngx_buf_t *b = ngx_create_temp_buf(r->pool,queryLineLen); if(b==NULL) return NGX_ERROR; b->last = b->pos + queryLineLen; ngx_snprintf(b->pos,queryLineLen, (char*)backendQueryLine.data, &r->args); r->upstream->request_bufs = ngx_alloc_chain_link(r->pool); if(r->upstream->request_bufs == NULL) return NGX_ERROR; r->upstream->request_bufs->buf = b; r->upstream->request_bufs->next = NULL; r->upstream->request_sent = 0; r->upstream->header_sent = 0; r->header_hash = 1; return NGX_OK; }
static ngx_int_t mytest_process_status_line(ngx_http_request_t *r) { size_t len; ngx_int_t rc; ngx_http_upstream_t *u; ngx_http_mytest_ctx_t* ctx = ngx_http_get_module_ctx(r,ngx_http_mytest_module); if(ctx == NULL) { return NGX_ERROR; } u = r->upstream; rc = ngx_http_parse_status_line(r,&u->buffer,&ctx->status); if(rc == NGX_AGAIN) { return rc; } if(rc == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR,r->connection->log,0,"upstream sent no valid HTTP/1.0 header"); r->http_version = NGX_HTTP_VERSION_9; u->state->status = NGX_HTTP_OK; return NGX_OK; } if(u->state) { u->state->status = ctx->status.code; } u->headers_in.status_n = ctx->status.code; len = ctx->status.end - ctx->status.start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pnalloc(r->pool,len); if(u->headers_in.status_line.data == NULL) { return NGX_ERROR; } ngx_memcpy(u->headers_in.status_line.data, ctx->status.start,len); u->process_header = mytest_upstream_process_header; return mytest_upstream_process_header(r); } static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for ( ;; ) { rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if (rc == NGX_OK) { h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if (h->key.data == NULL) { return NGX_ERROR; } h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len] = '