《深入理解Nginx》閱讀與實踐(三):使用upstream/subrequest訪問第三方服務

  本文是對陶輝《深入理解Nginx》第5章內容的梳理以及實現,代碼和註釋基本出自此書。

 

一、upstream:以向nginx服務器的請求轉化爲向google服務器的搜索請求爲例

(一)模塊框架

  首先要明確的是,這裏是編寫一個使用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\r\nHost: www.google.com\r\nConnection:close\r\n\r\n");
    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] = '\0';
            ngx_memcpy(h->value.data, r->header_start, h->value.len);
            h->value.data[h->value.len] = '\0';

            if (h->key.len == r->lowcase_index)
            {
                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);
            }
            else
            {
                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
            }

            
            hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                               h->lowcase_key, h->key.len);

            if (hh && hh->handler(r, h, hh->offset) != NGX_OK)
            {
                return NGX_ERROR;
            }
            continue;
        }

        if (rc == NGX_HTTP_PARSE_HEADER_DONE)
        {
            if (r->upstream->headers_in.server == NULL)
            {
                h = ngx_list_push(&r->upstream->headers_in.headers);
                if (h == NULL)
                {
                    return NGX_ERROR;
                }
                h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');

                ngx_str_set(&h->key, "Server");
                ngx_str_null(&h->value);
                h->lowcase_key = (u_char *) "server";
            }

            if (r->upstream->headers_in.date == NULL)
            {
                h = ngx_list_push(&r->upstream->headers_in.headers);
                if (h == NULL)
                {
                    return NGX_ERROR;
                }

                h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');

                ngx_str_set(&h->key, "Date");
                ngx_str_null(&h->value);
                h->lowcase_key = (u_char *) "date";
            }

            return NGX_OK;
        }

        if (rc == NGX_AGAIN)
        {
            return NGX_AGAIN;
        }

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "upstream sent invalid header");

        return NGX_HTTP_UPSTREAM_INVALID_HEADER;
    }
}
mytest_process_status_line()處理響應行,mytest_upstream_process_header()處理包頭
static void
mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_log_error(NGX_LOG_DEBUG,r->connection->log,0,
            "mytest_upstream_finalize_request");
}
mytest_upstream_finalize_request()釋放資源

   值得注意的是mytest_upstream_create_request()中計算queryLineLen中有一項-2。這是因爲格式控制符"%V"是會被替換成要輸出的變量的,在len成員裏計算了它的長度,需要減去。這種處理不要忽略,在subrequest的mytest_post_handler()中也出現了類似的處理。

 

(五)修改ngx_http_mytest_handler()

  完成的工作是關聯HTTP上下文與請求、填寫upstream配置結構體和調用ngx_http_upstream_init()啓動upstream。

static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r)
{
    ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(r,ngx_http_mytest_module);
    if(myctx == NULL)
    {
        myctx = ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));
    }
    if(myctx == NULL)
    {
        return NGX_ERROR;
    }
    ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
    if(ngx_http_upstream_create(r)!=NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log,0, "ngx_http_upstream_create() failed");
        return NGX_ERROR;
    }

    ngx_http_mytest_conf_t *mycf = (ngx_http_mytest_conf_t *) ngx_http_get_module_loc_conf(r,ngx_http_mytest_module);
    ngx_http_upstream_t *u = r->upstream;
    u->conf = &mycf->upstream;
    u->buffering = mycf->upstream.buffering;

    u->resolved = (ngx_http_upstream_resolved_t*)ngx_pcalloc(r->pool,sizeof(ngx_http_upstream_resolved_t));
    if(u->resolved == NULL) {
        ngx_log_error(NGX_LOG_ERR,r->connection->log,0,"ngx_pcalloc resolved error.%s.",strerror(errno));
        return NGX_ERROR;
    }

    static struct sockaddr_in backendSockAddr;
    struct hostent *pHost = gethostbyname((char*)"www.google.com");
    if(pHost == NULL)
    {
        ngx_log_error(NGX_LOG_ERR,r->connection->log,0,"gethostbyname fail.%s",strerror(errno));
        return NGX_ERROR;
    }

    backendSockAddr.sin_family = AF_INET;
    backendSockAddr.sin_port = htons((in_port_t)80);
    char* pDmsIP = inet_ntoa(*(struct in_addr*)(pHost->h_addr_list[0]));
    backendSockAddr.sin_addr.s_addr = inet_addr(pDmsIP);
    myctx->backendServer.data = (u_char*)pDmsIP;
    myctx->backendServer.len = strlen(pDmsIP);

    u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;
    u->resolved->socklen = sizeof(struct sockaddr_in);
    u->resolved->naddrs = 1;

    u->create_request = mytest_upstream_create_request;
    u->process_header = mytest_process_status_line;
    u->finalize_request = mytest_upstream_finalize_request;

    r->main->count++;
    ngx_http_upstream_init(r);
    return NGX_DONE;
}
ngx_http_mytest_handler()

 

(六)測試

  啓動nginx,在瀏覽器中輸入http://localhost:8080/test?lumia,可以看到返回的是http://www.google.com.hk/search?q=lumia。

  (8080是作者提供的下載源碼中nginx.conf設置的偵聽端口號;使用hk是由於被重定向了)

 

(七)附註

  1.URL中的問號"?"代表什麼?它在參數傳遞時有什麼用?

  答:GET方法中的參數請求以問號開始。換句話說,這個"?"後面跟隨的是GET方法的參數。

 

  2.HTTP響應行、HTTP頭部、HTTP包體的區分

  (下面的請求和應答例子來自於維基百科)

  客戶端請求:

GET / HTTP/1.1
Host:www.google.com

  (末尾有一個空行。第一行指定方法、資源路徑、協議版本;第二行是在1.1版裏必帶的一個header作用指定主機)

  服務器應答:

HTTP/1.1 200 OK
Content-Length: 3059
Server: GWS/2.0
Date: Sat, 11 Jan 2003 02:44:04 GMT
Content-Type: text/html
Cache-control: private
Set-Cookie: PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S=SMCc_HRPCQiqy
X9j; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Connection: keep-alive
...

  在這個包頭中,第一行就是HTTP響應行,HTTP/1.1表示支持的版本,200是HTTP狀態碼,表示處理成功,OK是對狀態碼200的一個簡短描述。

  根據RFC2616,可能使用“狀態行”來描述會更好一些?畢竟本文中處理它的函數是mytest_process_status_line(),而且《TCP/IP詳解(卷三)》也翻譯爲“狀態行”。下面用“狀態行”代替。

Status-Line

   The first line of a Response message is the Status-Line, consisting of the protocol version followed by a numeric status code and its associated textual phrase, with each element separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.
    Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF

   除了第一行,其餘部分中有一部分是HTTP響應頭部,即Response Header Fields,它們提供無法放入狀態行的信息。RFC2616很清楚的表明,狀態行和HTTP頭部是兩回事:

 The response-header fields allow the server to pass additional information about the response which cannot beplaced in the Status- Line. These header fields give information about the server and about further access to the resource identified by the Request-URI.
       response-header = Accept-Ranges           ; Section 14.5
                       | Age                     ; Section 14.6
                       | ETag                    ; Section 14.19
                       | Location                ; Section 14.30
                       | Proxy-Authenticate      ; Section 14.33
                       | Retry-After             ; Section 14.37
                       | Server                  ; Section 14.38
                       | Vary                    ; Section 14.44
                       | WWW-Authenticate        ; Section 14.47

  在響應頭部後,可能還有實體(Entity),而它又分爲實體頭部(Entity Header Fields)和實體主體(Entity Body),這在RFC2616是進行區分的:

7.1 Entity Header Fields
Entity-header fields define metainformation about the entity-body or,if no body is present, about the resourceidentified by the request. Someof this metainformation is OPTIONAL; some might be REQUIRED by portions of 
this specification.

       entity-header  = Allow                    ; Section 14.7
                      | Content-Encoding         ; Section 14.11
                      | Content-Language         ; Section 14.12
                      | Content-Length           ; Section 14.13
                      | Content-Location         ; Section 14.14
                      | Content-MD5              ; Section 14.15
                      | Content-Range            ; Section 14.16
                      | Content-Type             ; Section 14.17
                      | Expires                  ; Section 14.21
                      | Last-Modified            ; Section 14.29
                      | extension-header

       extension-header = message-header

  閱讀《深入理解Nginx》第3.6.3節可以看出,Nginx是將Response Header Fields和Entity Header Fields合稱爲HTTP頭部一併處理的。

  可見,造成理解混亂的原因可能是RFC2616進行區分的Response Header Fields和Entity Header Fields兩部分被Nginx一步處理所致。

  最後再看看實體主體,可以視之爲HTTP傳送的正文:

7.2 Entity Body

   The entity-body (if any) sent with an HTTP request or response is in a format and encoding defined by the entity-header fields.

  這樣,就把這幾個名詞的脈絡理清楚了。

 

二、subrequest:以向nginx發出請求轉化爲向新浪股票數據發出請求爲例

(一)subrequest工作流程

  subrequest由HTTP框架提供,可以把原始請求分解爲許多子請求。

  閱讀原書5.4和5.5節,可以把使用subrequest的流程概括爲:

[HTTP請求需要調用mytest模塊處理] -> [mytest模塊創建子請求] -> [發送並等待上游服務器處理子請求的響應]

-> (可選)[postpone模塊將待轉發相應包體放入鏈表並等待發送 ]

->[執行子請求處理完畢的回調方法ngx_http_post_subrequest_pt]

->[執行父請求被重新激活後的回調方法mytest_post_handler]

  這部分使用了代理模塊,但在這裏不做詳細介紹。下面的代碼中沒有使用postpone。

 

(二)配置項設置

  由於子請求需要訪問新浪的服務器,並且URL爲http://hq.sinajs.cn/list=s_sh000001,因此設置爲

location /list {                            
    //上游服務器地址                                      
    proxy_pass http://hq.sinajs.cn; 
//不希望第三方服務對HTTP包體進行gzip壓縮                                    
    proxy_set_header Accept-Encoding "";                                          
}                                                                                 
 

  同時,用戶訪問nginx服務器mytest模塊的URI依然要進行配置

location /query {                                                                 
    mytest;                                                                       
}

 

(三)請求上下文

  新浪服務器返回的數據格式是這樣的:

var hq_str_s_sh000001="上證指數,2070.369,-15.233,-0.73,1023439,8503131";

  因此把只用來保存請求回調方法中的股票數據的請求上下文定義如下:

typedef struct {     
    ngx_str_t   stock[6];    
} ngx_http_mytest_ctx;

 

(四)回調方法的實現

static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r,
                                                void *data, ngx_int_t rc)
{
    //當前請求r是子請求,它的parent成員就指向父請求
    ngx_http_request_t          *pr = r->parent;
    //注意,上下文是保存在父請求中的(參見5.6.5節),所以要由pr中取上下文。
//其實有更簡單的方法,即參數data就是上下文,初始化subrequest時
//我們就對其進行設置了的,這裏僅爲了說明如何獲取到父請求的上下文
    ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(pr, ngx_http_mytest_module);

    pr->headers_out.status = r->headers_out.status;
    //如果返回NGX_HTTP_OK(也就是200)意味着訪問新浪服務器成功,接着將
//開始解析http包體
    if (r->headers_out.status == NGX_HTTP_OK)
    {
        int flag = 0;

        //在不轉發響應時,buffer中會保存着上游服務器的響應。特別是在使用
//反向代理模塊訪問上游服務器時,如果它使用upstream機制時沒有重定義
//input_filter方法,upstream機制默認的input_filter方法會試圖
//把所有的上游響應全部保存到buffer緩衝區中
        ngx_buf_t* pRecvBuf = &r->upstream->buffer;

        //以下開始解析上游服務器的響應,並將解析出的值賦到上下文結構體
//myctx->stock數組中
        for (; pRecvBuf->pos != pRecvBuf->last; pRecvBuf->pos++)
        {
            if (*pRecvBuf->pos == ',' || *pRecvBuf->pos == '\"')
            {
                if (flag > 0)
                {
                    myctx->stock[flag - 1].len = pRecvBuf->pos - myctx->stock[flag - 1].data;
                }
                flag++;
                myctx->stock[flag - 1].data = pRecvBuf->pos + 1;
            }

            if (flag > 6)
                break;
        }
    }


    //這一步很重要,設置接下來父請求的回調方法
    pr->write_event_handler = mytest_post_handler;

    return NGX_OK;
}
mytest_subrequest_post_handler()子請求結束時的回調方法
static void
mytest_post_handler(ngx_http_request_t * r)
{
    printf("mytest_post_handler");

    //如果沒有返回200則直接把錯誤碼發回用戶
    if (r->headers_out.status != NGX_HTTP_OK)
    {
        ngx_http_finalize_request(r, r->headers_out.status);
        return;
    }

    //當前請求是父請求,直接取其上下文
    ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);

    //定義發給用戶的http包體內容,格式爲:
//stock[…],Today current price: …, volumn: …
    ngx_str_t output_format = ngx_string("stock[%V],Today current price: %V, volumn: %V");

    //計算待發送包體的長度
    int bodylen = output_format.len + myctx->stock[0].len
                  + myctx->stock[1].len + myctx->stock[4].len - 6;
    r->headers_out.content_length_n = bodylen;

    //在內存池上分配內存保存將要發送的包體
    ngx_buf_t* b = ngx_create_temp_buf(r->pool, bodylen);
    ngx_snprintf(b->pos, bodylen, (char*)output_format.data,
                 &myctx->stock[0], &myctx->stock[1], &myctx->stock[4]);
    b->last = b->pos + bodylen;
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;
    //設置Content-Type,注意漢字編碼新浪服務器使用了GBK
    static ngx_str_t type = ngx_string("text/plain; charset=GBK");
    r->headers_out.content_type = type;
    r->headers_out.status = NGX_HTTP_OK;

    r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED;
    ngx_int_t ret = ngx_http_send_header(r);
    ret = ngx_http_output_filter(r, &out);

    //注意,這裏發送完響應後必須手動調用ngx_http_finalize_request
//結束請求,因爲這時http框架不會再幫忙調用它
    ngx_http_finalize_request(r, ret);
}
mytest_post_handler()父請求的的回調方法
static void
mytest_post_handler(ngx_http_request_t * r)
{
    printf("mytest_post_handler");

    //如果沒有返回200則直接把錯誤碼發回用戶
    if (r->headers_out.status != NGX_HTTP_OK)
    {
        ngx_http_finalize_request(r, r->headers_out.status);
        return;
    }

    //當前請求是父請求,直接取其上下文
    ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);

    //定義發給用戶的http包體內容,格式爲:
//stock[…],Today current price: …, volumn: …
    ngx_str_t output_format = ngx_string("stock[%V],Today current price: %V, volumn: %V");

    //計算待發送包體的長度
    int bodylen = output_format.len + myctx->stock[0].len
                  + myctx->stock[1].len + myctx->stock[4].len - 6;
    r->headers_out.content_length_n = bodylen;

    //在內存池上分配內存保存將要發送的包體
    ngx_buf_t* b = ngx_create_temp_buf(r->pool, bodylen);
    ngx_snprintf(b->pos, bodylen, (char*)output_format.data,
                 &myctx->stock[0], &myctx->stock[1], &myctx->stock[4]);
    b->last = b->pos + bodylen;
    b->last_buf = 1;

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;
    //設置Content-Type,注意漢字編碼新浪服務器使用了GBK
    static ngx_str_t type = ngx_string("text/plain; charset=GBK");
    r->headers_out.content_type = type;
    r->headers_out.status = NGX_HTTP_OK;

    r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED;
    ngx_int_t ret = ngx_http_send_header(r);
    ret = ngx_http_output_filter(r, &out);

    //注意,這裏發送完響應後必須手動調用ngx_http_finalize_request
//結束請求,因爲這時http框架不會再幫忙調用它
    ngx_http_finalize_request(r, ret);
}
mytest_post_handler()用於創建子請求

  mytest_post_handler()中的-6的含義與上文upstream部分mytest_upstream_create_request()中計算queryLineLen的-2類似,不再重述。

 

(五)測試

  模塊安裝後並開啓nginx後,輸入下面的內容即可看到返回的內容(nginx.conf設置偵聽端口號爲8080):

http://localhost:8080/query?s_sh000001

  另外,如果發現沒有響應,請在當前環境(如虛擬機)中嘗試直連http://hq.sinajs.cn/list=s_sh000001以保證網絡連通性。

 

  本文完整源代碼請到《Nginx深入理解》作者陶輝提供的支持頁面下載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章