Nginx:访问第三方服务

参考资料<深入理解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\r\nHost:www.baidu.com\r\nConnection:close\r\n\r\n");
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]='\0';
262             ngx_memcpy(h->value.data,r->header_start,h->value.len);
263             h->value.data[h->value.len]='\0';
264             
265             if(h->key.len==r->lowcase_index){
266                 ngx_memcpy(h->lowcase_key,r->lowcase_header,h->key.len);
267             }else{
268                 ngx_strlow(h->lowcase_key,h->key.data,h->key.len);
269             }
270             
271             //upstream模块会对一些HTTP头部做特殊处理
272             hh=ngx_hash_find(&umcf->headers_in_hash,h->hash,
273                              h->lowcase_key,h->key.len);
274             if(hh&&hh->handler(r,h,hh->offset)!=NGX_OK){
275                 return NGX_OK;
276             }
277             continue;
278         }
279         //返回NGX_HTTP_PARSE_HEADER_DONE时,表示响应中所有的HTTP头部全部解析完毕
280         if(rc==NGX_HTTP_PARSE_HEADER_DONE){
281             //根据HTTP协议规定添加两个头部
282             if(r->upstream->headers_in.server==NULL){
283                 h=ngx_list_push(&r->upstream->headers_in.headers);
284                 if(h==NULL){
285                     return NGX_ERROR;
286                 }
287                 h->hash=ngx_hash(ngx_hash(ngx_hash(ngx_hash(
288                                  ngx_hash('s','e'),'r'),'v'),'e'),'r');
289                 ngx_str_set(&h->key,"Server");
290                 ngx_str_null(&h->value);
291                 h->lowcase_key=(u_char *)"server";
292             }
293             if(r->upstream->headers_in.date==NULL){
294                 h=ngx_list_push(&r->upstream->headers_in.headers);
295                 if(h==NULL){
296                     return NGX_ERROR;
297                 }
298                 h->hash=ngx_hash(ngx_hash(ngx_hash('d','a'),'t'),'e');
299                 ngx_str_set(&h->key,"Date");
300                 ngx_str_null(&h->value);
301                 h->lowcase_key=(u_char *)"date";
302             }
303             return NGX_OK;
304         }
305         //如果返回NGX_AGAIN,表示还没有解析到完整的HTTP头部
306         if(rc==NGX_AGAIN){
307             return NGX_AGAIN;
308         }
309         //其他返回值都是非法的
310         ngx_log_error(NGX_LOG_ERR,r->connection->log,0,
311                       "upstream sent invalid header");
312         return NGX_HTTP_UPSTREAM_INVALID_HEADER;
313         
314     }
315 }
316 
317 //释放资源
318 static void
319 mytest_upstream_finalize_request(ngx_http_request_t *r,ngx_int_t rc)
320 {
321     ngx_log_error(NGX_LOG_DEBUG,r->connection->log,0,
322                   "mytest_upstream_finalize_request");
323 }
324 
325 
326 //handler函数
327 static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
328 {
329     //首先建立HTTP上下文结构ngx_http_mytest_ctx_t
330     ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module);
331     if(myctx==NULL){
332         myctx=ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));
333         if(myctx==NULL){
334             return NGX_ERROR;
335         }
336         //将新建的上下文与请求关联起来
337         ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
338     }
339     //初始化r->upstream成员
340     if(ngx_http_upstream_create(r)!=NGX_OK){
341         ngx_log_error(NGX_LOG_ERR,r->connection->log,0,
342                       "ngx_http_upstream create() failed");
343         return NGX_ERROR;
344     }
345     //得到配置结构体ngx_http_mytest_conf_t
346     ngx_http_mytest_conf_t *mycf=(ngx_http_mytest_conf_t *)
347                                 ngx_http_get_module_loc_conf(r,ngx_http_mytest_module);
348     ngx_http_upstream_t *u=r->upstream;
349     //用配置文件中的结构体来赋给r->upstream->conf成员
350     u->conf=&mycf->upstream;
351     //决定转发包体时使用的缓冲区
352     u->buffering=mycf->upstream.buffering;
353     
354     //初始化resolved结构体,用来保存上游服务器的地址
355     u->resolved=(ngx_http_upstream_resolved_t *)ngx_pcalloc(r->pool,sizeof(ngx_http_upstream_resolved_t));
356     if(u->resolved==NULL){
357         ngx_log_error(NGX_LOG_ERR,r->connection->log,0,
358                       "ngx_pcalloc resolved error. %s.",strerror(errno));
359         return NGX_ERROR;
360     }
361     
362     //设置上游服务器地址
363     static struct sockaddr_in backendSockAddr;
364     struct hostent *pHost=gethostbyname((char *)"www.baidu.com");
365     if(pHost==NULL){
366         ngx_log_error(NGX_LOG_ERR,r->connection->log,0,
367                       "gethostbyname fail. %s",strerror(errno));
368         return NGX_ERROR;
369     }
370     
371     //访问上游服务器的80端口
372     backendSockAddr.sin_family=AF_INET;
373     backendSockAddr.sin_port=htons((in_port_t)80);
374     char *pDmsIP=inet_ntoa(*(struct in_addr *)(pHost->h_addr_list[0]));
375     backendSockAddr.sin_addr.s_addr=inet_addr(pDmsIP);
376     myctx->backendServer.data=(u_char *)pDmsIP;
377     myctx->backendServer.len=strlen(pDmsIP);
378     
379     //将地址设置到resolved成员中
380     u->resolved->sockaddr=(struct sockaddr *)&backendSockAddr;
381     u->resolved->socklen=sizeof(struct sockaddr_in);
382     u->resolved->naddrs=1;
383     
384     //设置3个必须实现的回调方法
385     u->create_request=mytest_upstream_create_request;
386     u->process_header=mytest_process_status_line;
387     u->finalize_request=mytest_upstream_finalize_request;
388     
389     //告诉HTTP框架暂时不要销毁请求
390     r->main->count++;
391     
392     //启动upstream
393     ngx_http_upstream_init(r);
394     //必须返回NGX_DONE
395     return NGX_DONE;
396     
397 }
View Code

 

posted @ 2015-10-19 15:34  Runnyu  阅读(2497)  评论(0编辑  收藏  举报