• 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
    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]='';
    262             ngx_memcpy(h->value.data,r->header_start,h->value.len);
    263             h->value.data[h->value.len]='';
    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
  • 相关阅读:
    [Leetcode]Container With Most Water随记
    [Leetcode]leetcode1-10题随记
    随机梯度下降的逻辑回归算法(SGDLR)
    IRP 与 派遣函数
    RtlInitUnicodeString、IoCreateDevice、IoCreateSymbolicLink、IoDeleteDevice 四个 API 驱动函数的使用
    基类 的薄弱之处
    类 的重载(Overloads)与隐藏(Shadows)
    VS 2013驱动开发 + Windbg + VM双机调试(亲测+详解)
    类 的继承性(Inherits)与 重写(Overrides)
    VB.NET 结构(Structure)和类(Class)的区别
  • 原文地址:https://www.cnblogs.com/runnyu/p/4890469.html
Copyright © 2020-2023  润新知