本文是對陶輝《深刻理解Nginx》第5章內容的梳理以及實現,代碼和註釋基本出自此書。html
首先要明確的是,這裏是編寫一個使用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。nginx
模塊處理的請求是ngx_http_request_t結構對象r,它包含了一個ngx_http_upstream_t類型的成員upstream。當upstream非NULL時,將會根據其中設置的內容定製訪問第三方服務的方式。而這個請求的處理是由upstream模塊來完成的。從這裏開始,要注意區分請求r中的upstream成員和Nginx提供的upstream模塊不是一回事,而是由前者來指導後者的工做。前者的設定與開啓(即告知upstream模塊須要進行處理,經過ngx_http_upstream_init()實現)是由咱們編寫的的第三方模塊(本文中是mytest)來完成的。數組
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_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 };
首先定義ngx_http_mytest_ctx_t結構體用於保存process_header()方法的解析狀態,注意結構體的第二個成員原書沒有寫,須要補充上。dom
typedef struct { ngx_http_status_t status; ngx_str_t backendServer; } ngx_http_mytest_ctx_t;
原書上3個回調函數的代碼以下,詳細的註釋請參考原書:ide
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; }
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; } }
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_create_request()中計算queryLineLen中有一項-2。這是由於格式控制符"%V"是會被替換成要輸出的變量的,在len成員裏計算了它的長度,須要減去。這種處理不要忽略,在subrequest的mytest_post_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; }
啓動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由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; }
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); }
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()中的-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深刻理解》做者陶輝提供的支持頁面下載。