《深入理解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_mytest_create_loc_conf()和ngx_http_mytest_merge_loc_conf()

  另外根据作者网页上的源码,需要补充上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;
}
mytest_upstream_create_request()构造请求
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] = '