博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Nginx:访问第三方服务
阅读量:4455 次
发布时间:2019-06-07

本文共 16525 字,大约阅读时间需要 55 分钟。

参考资料<深入理解Nginx>

Nginx可以当做一个强大的反向代理服务器,其反向代理模块是基于upstream方式实现的。

 

upstream的使用方式

HTTP模块在处理任何一个请求时都有一个ngx_http_request_t结构对象r,而该对象又有一个ngx_http_upstream_t类型的成员upstream。

typedef struct ngx_http_request_s ngx_http_request_tstruct 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_tngx_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 
2 #include
3 #include
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_conf131 };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_PADDING150 };151 152 153 //创建发往google上游服务器的请求154 static ngx_int_t155 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_t180 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_t227 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 void319 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_t330 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_t346 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 //启动upstream393 ngx_http_upstream_init(r);394 //必须返回NGX_DONE395 return NGX_DONE;396 397 }
View Code

 

转载于:https://www.cnblogs.com/runnyu/p/4890469.html

你可能感兴趣的文章
Web
查看>>
那些容易忽略的事(1) -变量与运算符+
查看>>
九度oj 题目1252:回文子串
查看>>
(十一)tina | openwrt关闭调试串口(DEBUG UART)
查看>>
angularjs 使用angular-sortable-view实现拖拽效果(包括拖动完成后的方法使用)
查看>>
2015生命之旅---南京、南通、上海之行
查看>>
高精度练习之乘法(codevs_3117)
查看>>
小Z爱划水
查看>>
Qt Font
查看>>
2014年生日
查看>>
扫描目录下的文件并拼接在一起
查看>>
ELK 分布式日志处理 10.12
查看>>
Java虚拟机详解05----垃圾收集器及GC参数
查看>>
7. 单位,移动布局
查看>>
inux中bin与sbin目录的作用及区别介绍
查看>>
USACO 3.1 Contact
查看>>
Office之什么是高内聚低耦合
查看>>
一些奇怪的问题求回答
查看>>
这些年踩过的坑
查看>>
iOS开发拓展篇——如何把项目托管到GitHub
查看>>