<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ### subrequest原理解析[](http://tengine.taobao.org/book/chapter_12.html#subrequest-99 "永久鏈接至標題") 子請求并不是http標準里面的概念,它是在當前請求中發起的一個新的請求,它擁有自己的ngx_http_request_t結構,uri和args。一般來說使用subrequest的效率可能會有些影響,因為它需要重新從server rewrite開始走一遍request處理的PHASE,但是它在某些情況下使用能帶來方便,比較常用的是用subrequest來訪問一個upstream的后端,并給它一個ngx_http_post_subrequest_t的回調handler,這樣有點類似于一個異步的函數調用。對于從upstream返回的數據,subrequest允許根據創建時指定的flag,來決定由用戶自己處理(回調handler中)還是由upstream模塊直接發送到out put filter。簡單的說一下subrequest的行為,nginx使用subrequest訪問某個location,產生相應的數據,并插入到nginx輸出鏈的相應位置(創建subrequest時的位置),下面用nginx代碼內的addition模塊(默認未編譯進nginx核心,請使用–with-http_addition_module選項包含此模塊)來舉例說明一下: [](http:// "點擊提交Issue,反饋你的意見...") location /main.htm { # content of main.htm: main add_before_body /hello.htm; add_after_body /world.htm; } location /hello.htm { #content of hello.htm: hello } location /world.htm { #content of world.htm: world } 訪問/main.htm,將得到如下響應: [](http:// "點擊提交Issue,反饋你的意見...") hellomainworld 上面的add_before_body指令發起一個subrequest來訪問/hello.htm,并將產生的內容(hello)插入主請求響應體的開頭,add_after_body指令發起一個subrequest訪問/world.htm,并將產生的內容(world)附加在主請求響應體的結尾。addition模塊是一個filter模塊,但是subrequest既可以在phase模塊中使用,也可以在filter模塊中使用。 在進行源碼解析之前,先來想想如果是我們自己要實現subrequest的上述行為,該如何來做?subrequest還可能有自己的subrequest,而且每個subrequest都不一定按照其創建的順序來輸出數據,所以簡單的采用鏈表不好實現,于是進一步聯想到可以采用樹的結構來做,主請求即為根節點,每個節點可以有自己的子節點,遍歷某節點表示處理某請求,自然的可以想到這里可能是用后根(序)遍歷的方法,沒錯,實際上Igor采用樹和鏈表結合的方式實現了subrequest的功能,但是由于節點(請求)產生數據的順序不是固定按節點創建順序(左->右),而且可能分多次產生數據,不能簡單的用后根(序)遍歷。Igor使用了2個鏈表的結構來實現,第一個是每個請求都有的postponed鏈表,一般情況下每個鏈表節點保存了該請求的一個子請求,該鏈表節點定義如下: [](http:// "點擊提交Issue,反饋你的意見...") struct ngx_http_postponed_request_s { ngx_http_request_t *request; ngx_chain_t *out; ngx_http_postponed_request_t *next; }; 可以看到它有一個request字段,可以用來保存子請求,另外還有一個ngx_chain_t類型的out字段,實際上一個請求的postponed鏈表里面除了保存子請求的節點,還有保存該請求自己產生的數據的節點,數據保存在out字段;第二個是posted_requests鏈表,它掛載了當前需要遍歷的請求(節點), 該鏈表保存在主請求(根節點)的posted_requests字段,鏈表節點定義如下: [](http:// "點擊提交Issue,反饋你的意見...") struct ngx_http_posted_request_s { ngx_http_request_t *request; ngx_http_posted_request_t *next; }; 在ngx_http_run_posted_requests函數中會順序的遍歷主請求的posted_requests鏈表: [](http:// "點擊提交Issue,反饋你的意見...") void ngx_http_run_posted_requests(ngx_connection_t *c) { ... for ( ;; ) { /* 連接已經斷開,直接返回 */ if (c->destroyed) { return; } r = c->data; /* 從posted_requests鏈表的隊頭開始遍歷 */ pr = r->main->posted_requests; if (pr == NULL) { return; } /* 從鏈表中移除即將要遍歷的節點 */ r->main->posted_requests = pr->next; /* 得到該節點中保存的請求 */ r = pr->request; ctx = c->log->data; ctx->current_request = r; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http posted request: \"%V?%V\"", &r->uri, &r->args); /* 遍歷該節點(請求) */ r->write_event_handler(r); } } ngx_http_run_posted_requests函數的調用點后面會做說明。 了解了一些實現的原理,來看代碼就簡單多了,現在正式進行subrequest的源碼解析, 首先來看一下創建subrequest的函數定義: [](http:// "點擊提交Issue,反饋你的意見...") ngx_int_t ngx_http_subrequest(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, ngx_http_post_subrequest_t *ps, ngx_uint_t flags) 參數r為當前的請求,uri和args為新的要發起的uri和args,當然args可以為NULL,psr為指向一個ngx_http_request_t指針的指針,它的作用就是獲得創建的子請求,ps的類型為ngx_http_post_subrequest_t,它的定義如下: [](http:// "點擊提交Issue,反饋你的意見...") typedef struct { ngx_http_post_subrequest_pt handler; void *data; } ngx_http_post_subrequest_t; typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r, void *data, ngx_int_t rc); 它就是之前說到的回調handler,結構里面的handler類型為ngx_http_post_subrequest_pt,它是函數指針,data為傳遞給handler的額外參數。再來看一下ngx_http_subrequest函數的最后一個是flags,現在的源碼中實際上只有2種類型的flag,分別為NGX_HTTP_SUBREQUEST_IN_MEMORY和NGX_HTTP_SUBREQUEST_WAITED,第一個就是指定文章開頭說到的子請求的upstream處理數據的方式,第二個參數表示如果該子請求提前完成(按后序遍歷的順序),是否設置將它的狀態設為done,當設置該參數時,提前完成就會設置done,不設時,會讓該子請求等待它之前的子請求處理完畢才會將狀態設置為done。 進入ngx_http_subrequest函數內部看看: [](http:// "點擊提交Issue,反饋你的意見...") { ... /* 解析flags, subrequest_in_memory在upstream模塊解析完頭部, 發送body給downsstream時用到 */ sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0; sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0; sr->unparsed_uri = r->unparsed_uri; sr->method_name = ngx_http_core_get_method; sr->http_protocol = r->http_protocol; ngx_http_set_exten(sr); /* 主請求保存在main字段中 */ sr->main = r->main; /* 父請求為當前請求 */ sr->parent = r; /* 保存回調handler及數據,在子請求執行完,將會調用 */ sr->post_subrequest = ps; /* 讀事件handler賦值為不做任何事的函數,因為子請求不用再讀數據或者檢查連接狀態; 寫事件handler為ngx_http_handler,它會重走phase */ sr->read_event_handler = ngx_http_request_empty_handler; sr->write_event_handler = ngx_http_handler; /* ngx_connection_s的data字段比較關鍵,它保存了當前可以向out chain輸出數據的請求, 具體意義后面會做詳細介紹 */ if (c->data == r && r->postponed == NULL) { c->data = sr; } /* 默認共享父請求的變量,當然你也可以根據需求在創建完子請求后,再創建子請求獨立的變量集 */ sr->variables = r->variables; sr->log_handler = r->log_handler; pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t)); if (pr == NULL) { return NGX_ERROR; } pr->request = sr; pr->out = NULL; pr->next = NULL; /* 把該子請求掛載在其父請求的postponed鏈表的隊尾 */ if (r->postponed) { for (p = r->postponed; p->next; p = p->next) { /* void */ } p->next = pr; } else { r->postponed = pr; } /* 子請求為內部請求,它可以訪問internal類型的location */ sr->internal = 1; /* 繼承父請求的一些狀態 */ sr->discard_body = r->discard_body; sr->expect_tested = 1; sr->main_filter_need_in_memory = r->main_filter_need_in_memory; sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; tp = ngx_timeofday(); r->start_sec = tp->sec; r->start_msec = tp->msec; r->main->subrequests++; /* 增加主請求的引用數,這個字段主要是在ngx_http_finalize_request調用的一些結束請求和 連接的函數中使用 */ r->main->count++; *psr = sr; /* 將該子請求掛載在主請求的posted_requests鏈表隊尾 */ return ngx_http_post_request(sr, NULL); } 到這時,子請求創建完畢,一般來說子請求的創建都發生在某個請求的content handler或者某個filter內,從上面的函數可以看到子請求并沒有馬上被執行,只是被掛載在了主請求的posted_requests鏈表中,那它什么時候可以執行呢?之前說到posted_requests鏈表是在ngx_http_run_posted_requests函數中遍歷,那么ngx_http_run_posted_requests函數又是在什么時候調用?它實際上是在某個請求的讀(寫)事件的handler中,執行完該請求相關的處理后被調用,比如主請求在走完一遍PHASE的時候會調用ngx_http_run_posted_requests,這時子請求得以運行。 這時實際還有1個問題需要解決,由于nginx是多進程,是不能夠隨意阻塞的(如果一個請求阻塞了當前進程,就相當于阻塞了這個進程accept到的所有其他請求,同時該進程也不能accept新請求),一個請求可能由于某些原因需要阻塞(比如訪問io),nginx的做法是設置該請求的一些狀態并在epoll中添加相應的事件,然后轉去處理其他請求,等到該事件到來時再繼續處理該請求,這樣的行為就意味著一個請求可能需要多次執行機會才能完成,對于一個請求的多個子請求來說,意味著它們完成的先后順序可能和它們創建的順序是不一樣的,所以必須有一種機制讓提前完成的子請求保存它產生的數據,而不是直接輸出到out chain,同時也能夠讓當前能夠往out chain輸出數據的請求及時的輸出產生的數據。作者Igor采用ngx_connection_t中的data字段,以及一個body filter,即ngx_http_postpone_filter,還有ngx_http_finalize_request函數中的一些邏輯來解決這個問題。 下面用一個圖來做說明,下圖是某時刻某個主請求和它的所有子孫請求的樹結構: [![http://tengine.taobao.org/book/_images/chapter-12-1.png](http://tengine.taobao.org/book/_images/chapter-12-1.png)](http://tengine.taobao.org/book/_images/chapter-12-1.png) 圖中的root節點即為主請求,它的postponed鏈表從左至右掛載了3個節點,SUB1是它的第一個子請求,DATA1是它產生的一段數據,SUB2是它的第2個子請求,而且這2個子請求分別有它們自己的子請求及數據。ngx_connection_t中的data字段保存的是當前可以往out chain發送數據的請求,文章開頭說到發到客戶端的數據必須按照子請求創建的順序發送,這里即是按后序遍歷的方法(SUB11->DATA11->SUB12->DATA12->(SUB1)->DATA1->SUB21->SUB22->(SUB2)->(ROOT)),上圖中當前能夠往客戶端(out chain)發送數據的請求顯然就是SUB11,如果SUB12提前執行完成,并產生數據DATA121,只要前面它還有節點未發送完畢,DATA121只能先掛載在SUB12的postponed鏈表下。這里還要注意一下的是c->data的設置,當SUB11執行完并且發送完數據之后,下一個將要發送的節點應該是DATA11,但是該節點實際上保存的是數據,而不是子請求,所以c->data這時應該指向的是擁有改數據節點的SUB1請求。 下面看下源碼具體是怎樣實現的,首先是ngx_http_postpone_filter函數: [](http:// "點擊提交Issue,反饋你的意見...") static ngx_int_t ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in) { ... /* 當前請求不能往out chain發送數據,如果產生了數據,新建一個節點, 將它保存在當前請求的postponed隊尾。這樣就保證了數據按序發到客戶端 */ if (r != c->data) { if (in) { ngx_http_postpone_filter_add(r, in); return NGX_OK; } ... return NGX_OK; } /* 到這里,表示當前請求可以往out chain發送數據,如果它的postponed鏈表中沒有子請求,也沒有數據, 則直接發送當前產生的數據in或者繼續發送out chain中之前沒有發送完成的數據 */ if (r->postponed == NULL) { if (in || c->buffered) { return ngx_http_next_filter(r->main, in); } /* 當前請求沒有需要發送的數據 */ return NGX_OK; } /* 當前請求的postponed鏈表中之前就存在需要處理的節點,則新建一個節點,保存當前產生的數據in, 并將它插入到postponed隊尾 */ if (in) { ngx_http_postpone_filter_add(r, in); } /* 處理postponed鏈表中的節點 */ do { pr = r->postponed; /* 如果該節點保存的是一個子請求,則將它加到主請求的posted_requests鏈表中, 以便下次調用ngx_http_run_posted_requests函數,處理該子節點 */ if (pr->request) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http postpone filter wake \"%V?%V\"", &pr->request->uri, &pr->request->args); r->postponed = pr->next; /* 按照后序遍歷產生的序列,因為當前請求(節點)有未處理的子請求(節點), 必須先處理完改子請求,才能繼續處理后面的子節點。 這里將該子請求設置為可以往out chain發送數據的請求。 */ c->data = pr->request; /* 將該子請求加入主請求的posted_requests鏈表 */ return ngx_http_post_request(pr->request, NULL); } /* 如果該節點保存的是數據,可以直接處理該節點,將它發送到out chain */ if (pr->out == NULL) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http postpone filter NULL output", &r->uri, &r->args); } else { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http postpone filter output \"%V?%V\"", &r->uri, &r->args); if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) { return NGX_ERROR; } } r->postponed = pr->next; } while (r->postponed); return NGX_OK; } 再來看ngx_http_finalzie_request函數: [](http:// "點擊提交Issue,反饋你的意見...") void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ... /* 如果當前請求是一個子請求,檢查它是否有回調handler,有的話執行之 */ if (r != r->main && r->post_subrequest) { rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc); } ... /* 子請求 */ if (r != r->main) { /* 該子請求還有未處理完的數據或者子請求 */ if (r->buffered || r->postponed) { /* 添加一個該子請求的寫事件,并設置合適的write event hander, 以便下次寫事件來的時候繼續處理,這里實際上下次執行時會調用ngx_http_output_filter函數, 最終還是會進入ngx_http_postpone_filter進行處理 */ if (ngx_http_set_write_handler(r) != NGX_OK) { ngx_http_terminate_request(r, 0); } return; } ... pr = r->parent; /* 該子請求已經處理完畢,如果它擁有發送數據的權利,則將權利移交給父請求, */ if (r == c->data) { r->main->count--; if (!r->logged) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->log_subrequest) { ngx_http_log_request(r); } r->logged = 1; } else { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "subrequest: \"%V?%V\" logged again", &r->uri, &r->args); } r->done = 1; /* 如果該子請求不是提前完成,則從父請求的postponed鏈表中刪除 */ if (pr->postponed && pr->postponed->request == r) { pr->postponed = pr->postponed->next; } /* 將發送權利移交給父請求,父請求下次執行的時候會發送它的postponed鏈表中可以 發送的數據節點,或者將發送權利移交給它的下一個子請求 */ c->data = pr; } else { /* 到這里其實表明該子請求提前執行完成,而且它沒有產生任何數據,則它下次再次獲得 執行機會時,將會執行ngx_http_request_finalzier函數,它實際上是執行 ngx_http_finalzie_request(r,0),也就是什么都不干,直到輪到它發送數據時, ngx_http_finalzie_request函數會將它從父請求的postponed鏈表中刪除 */ r->write_event_handler = ngx_http_request_finalizer; if (r->waited) { r->done = 1; } } /* 將父請求加入posted_request隊尾,獲得一次運行機會 */ if (ngx_http_post_request(pr, NULL) != NGX_OK) { r->main->count++; ngx_http_terminate_request(r, 0); return; } return; } /* 這里是處理主請求結束的邏輯,如果主請求有未發送的數據或者未處理的子請求, 則給主請求添加寫事件,并設置合適的write event hander, 以便下次寫事件來的時候繼續處理 */ if (r->buffered || c->buffered || r->postponed || r->blocked) { if (ngx_http_set_write_handler(r) != NGX_OK) { ngx_http_terminate_request(r, 0); } return; } ... } [](http:// "點擊提交Issue,反饋你的意見...")
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看