Nginx之最簡單的反向代理機制分析

注:當前分析基於 Nginx之搭建反向代理實現tomcat分佈式集羣 的配置。html

1. 用到的指令

下面介紹在上面的配置中用到的指令。nginx

upstream 指令

語法:upstream name { ... }

默認值:none

使用環境:http

該指令用於設置一組能夠在 proxy_pass 和 fastcgi_pass 指令中使用的代理服務器,默認的負載均衡方式爲輪詢。示例以下:express

upstream backend {
    server backend1.example.com weight = 5;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
}

server 指令

語法:server name [parameters]

默認值:none

使用環境:upstream

該指令用於指定後端服務器的名稱和參數。服務器的名稱能夠是一個域名、一個 IP 地址、端口號或 UNIX Socket。後端

在後端服務器名稱以後,能夠跟如下參數:數組

  • weight=NUMBER:設置服務器的權重,權重數值越高,被分配到的客戶端請求數越多。若是沒有設置權重,則爲默認權重 1.
  • max_fails=NUMBER:在參數 fail_timeout 指定的時間內對後端服務器請求失敗的次數,若是檢測到後端服務器沒法鏈接及發生服務器錯誤(404錯誤除外),則標記爲失敗。若是沒有設置,則爲默認值 1。設爲數組 0 將關閉這項檢查。
  • fail_timeout=TIME:在經歷參數 max_fails 設置的失敗次數後,暫停的時間。
  • down:標記服務器爲永久離線狀態,用於 ip_hash 指令。
  • backup:僅僅在非 backup 服務器所有宕機或繁忙的時候才啓用。

proxy_pass 指令

將指定請求傳遞到上游服務器,格式爲 URL。緩存

2. 數據結構

2.1 ngx_http_upstream_t

typedef struct ngx_http_upstream_s    ngx_http_upstream_t;

struct ngx_http_upstream_s {
    /*
     * 處理讀事件的回調方法,每個階段都有不一樣的 read_event_handler 
     */
    ngx_http_upstream_handler_pt     read_event_handler;
    /*
     * 處理寫事件的回調方法,每個階段都有不一樣的 write_event_handler
     */
    ngx_http_upstream_handler_pt     write_event_handler;

    /*
     * 表示主動向上游服務器發起的鏈接
     */
    ngx_peer_connection_t            peer;

    /*
     * 當向下遊客戶端轉發響應時(ngx_http_request_t 結構體中的 subrequest_in_memory
     * 標誌位爲 0),若是打開了緩存且認爲上游網速更快(conf 配置中的 buffering 標誌
     * 位爲 1),這時會使用 pipe 成員來轉發響應。在使用這種方式轉發響應時,必須由
     * HTTP 模塊在使用 upstream 機制前構造 pipe 結構體,不然會出現嚴重的 coredump
     * 錯誤.
     */
    ngx_event_pipe_t                *pipe;

    /*
     * request_bufs 以鏈表的方式把 ngx_buf_t 緩存區連接起來,它表示全部須要發送到
     * 上游服務器的請求內容。因此,HTTP 模塊實現的 create_request 回調方法就在於
     * 構造 request_bufs 鏈表
     */
    ngx_chain_t                     *request_bufs;

    /*
     * 定義了向下遊發送響應的方式
     */
    ngx_output_chain_ctx_t           output;
    ngx_chain_writer_ctx_t           writer;

    /*
     * 使用 upstream 機制時的各類配置
     */
    ngx_http_upstream_conf_t        *conf;
    ngx_http_upstream_srv_conf_t    *upstream;
#if (NGX_HTTP_CACHE)
    ngx_array_t                     *caches;
#endif

    /*
     * HTTP 模塊在實現 process_header 方法時,若是但願 upstream 直接轉發響應,
     * 就須要把解析出的響應頭部適配爲 HTTP 的響應頭部,同時須要把包頭中的信息
     * 設置到 headers_in 結構體,這樣,會把 headers_in 中設置的頭部添加到要發
     * 送到下游客戶端的響應頭部 headers_out 中
     */
    ngx_http_upstream_headers_in_t   headers_in;

    /*
     * 用於解析主機域名
     */
    ngx_http_upstream_resolved_t    *resolved;

    ngx_buf_t                        from_client;

    /*
     * 接收上游服務器響應包頭的緩衝區,在不須要把響應直接轉發給客戶端,
     * 或者 buffering 標誌位爲 0 的狀況下轉發包體時,接收包體的緩衝區
     * 仍然使用 buffer。注意,若是沒有自定義 input_filter 方法處理包體,
     * 將會使用 buffer 存儲所有的包體,這時 buffer 必須足夠大,它的大小
     * 由 ngx_http_upstream_conf_t 配置結構體中的 buffer_size 成員決定
     */
    ngx_buf_t                        buffer;
    /*
     * 表示來自上游服務器的響應包體的長度
     */
    off_t                            length;

    /*
     * out_bufs 在兩種場景下有不一樣的意義:1. 當不須要轉發包體,且使用默認
     * 的 input_filter 方法(也就是 ngx_http_upstream_non_buffered_filter 
     * 方法)處理包體時,out_bufs 將會指向響應包體,事實上,out_bufs 鏈表
     * 中會產生多個 ngx_buf_t 緩衝區,每一個緩衝區都指向 buffer 緩存中的一部
     * 分,而這裏的一部分就是每次調用 recv 方法接收到的一段 TCP 流。2. 當
     * 須要轉發響應包體到下游時(buffering 標誌位爲 0,即如下游網速優先),
     * 這個鏈表指向上一次向下遊轉發響應到如今這段時間內接收自上游的緩存響應
     */
    ngx_chain_t                     *out_bufs;
    /*
     * 當須要轉發響應包體到下游時(buffering 標誌位爲 0,即如下游網速優先),
     * 它表示上一次向下遊轉發響應時沒有發送完的內容
     */
    ngx_chain_t                     *busy_bufs;
    /*
     * 這個鏈表將用於回收 out_bufs 中已經發送給下游的 ngx_buf_t 結構體,這
     * 一樣應用在 buffering 標誌位爲 0 即如下游網速優先的場景
     */
    ngx_chain_t                     *free_bufs;

    /*
     * 處理包體前的初始化方法,其中 data 參數用於傳遞用戶數據結構,它實際上
     * 就是下面的 input_filter_ctx 指針
     */
    ngx_int_t                      (*input_filter_init)(void *data);
    /*
     * 處理包體的方法,其中 data 參數用於傳遞用戶數據結構,它實際上就是下面的
     * input_filter_ctx 指針,而 bytes 表示本次接收到的包體長度。返回 NGX_ERROR
     * 時表示處理包體錯誤,請求須要結束,不然都將繼續 upstream 流程
     */
    ngx_int_t                      (*input_filter)(void *data, ssize_t bytes);
    /*
     * 用於傳遞 HTTP 模塊自定義的數據結構,在 input_filter_init 和 input_filter 
     * 方法被回調時會做爲參數傳遞過去
     */
    void                            *input_filter_ctx;

#if (NGX_HTTP_CACHE)
    ngx_int_t                      (*create_key)(ngx_http_request_t *r);
#endif
    /*
     * HTTP 模塊實現的 create_request 方法用於構造發往上游服務器的請求
     */
    ngx_int_t                      (*create_request)(ngx_http_request_t *r);
    /*
     * 與上游服務器的通訊失敗後,若是按照重試規則還須要再次向上遊服務器發起
     * 鏈接,則會調用 reinit_request 方法
     */
    ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);
    /*
     * 解析上游服務器返回響應的包頭,返回 NGX_AGAIN 表示包頭尚未接收完整,
     * 返回 NGX_HTTP_UPSTREAM_INVALID_HEADER 表示包頭不合法,返回 NGX_ERROR
     * 表示出現錯誤,返回 NGX_OK 表示解析到完整的包頭.
     */
    ngx_int_t                      (*process_header)(ngx_http_request_t *r);
    void                           (*abort_request)(ngx_http_request_t *r);
    /*
     * 請求結束時會調用
     */
    void                           (*finalize_request)(ngx_http_request_t *r,
                                         ngx_int_t rc);
    /*
     * 在上游返回的響應出現 Location 或者 Refresh 頭部時表示重定向時,會經過
     * ngx_http_upstream_process_headers 方法調用到可由 HTTP 模塊實現的
     * rewrite_redirect 方法
     */
    ngx_int_t                      (*rewrite_redirect)(ngx_http_request_t *r,
                                         ngx_table_elt_t *h, size_t prefix);
    ngx_int_t                      (*rewrite_cookie)(ngx_http_request_t *r,
                                         ngx_table_elt_t *h);

    ngx_msec_t                       timeout;

    /*
     * 用於表示上游響應的錯誤碼、包體長度等信息
     */
    ngx_http_upstream_state_t       *state;

    ngx_str_t                        method;
    /*
     * schema 和 uri 成員僅在記錄日誌時會用到,除此以外沒有意義
     */
    ngx_str_t                        schema;
    ngx_str_t                        uri;

#if (NGX_HTTP_SSL || NGX_COMPAT)
    ngx_str_t                        ssl_name;
#endif

    ngx_http_cleanup_pt             *cleanup;

    /*
     * 是否指定文件緩存路徑的標誌位
     */
    unsigned                         store:1;
    /*
     * 是否啓用文件緩存
     */
    unsigned                         cacheable:1;
    unsigned                         accel:1;
    /*
     * 是否基於 SSL 協議訪問上游服務器
     */
    unsigned                         ssl:1;
#if (NGX_HTTP_CACHE)
    unsigned                         cache_status:3;
#endif

    /*
     * 向下遊轉發上游的響應包體時,是否開啓更大的內存及臨時磁盤文件用於
     * 緩存來不及發送到下游的響應包體.
     */
    unsigned                         buffering:1;
    unsigned                         keepalive:1;
    unsigned                         upgrade:1;

    /*
     * request_sent 表示是否已經向上遊服務器發送了請求,當 request_sent 爲 
     * 1 時,表示 upstream 機制已經向上遊服務器發送了所有或者部分的請求。
     * 事實上,這個標誌位更多的是爲了使用 ngx_output_chain 方法發送請求,
     * 由於該方法發送請求時會自動把未發送完的 request_bufs 鏈表記錄下來,
     * 爲了防止反覆發送重複請求,必須有 request_sent 標誌位記錄是否調用過
     * ngx_output_chain 方法
     */
    unsigned                         request_sent:1;
    unsigned                         request_body_sent:1;
    /*
     * 將上游服務器的響應劃分爲包頭和包尾,若是把響應直接轉發給客戶端,
     * header_sent 標誌位表示包頭是否發送,header_sent 爲 1 時表示已經
     * 把包頭轉發給客戶端了。若是不轉發響應到客戶端,則 header_sent 
     * 沒有意義.
     */
    unsigned                         header_sent:1;
};

2.2 ngx_http_upstream_conf_t

typedef struct {
    /*
     * 當在 ngx_http_upstream_t 結構體中沒有實現 resolved 成員時,upstream 這個
     * 結構體纔會生效,它會定義上游服務器的配置
     */
    ngx_http_upstream_srv_conf_t    *upstream;

    /*
     * 創建 TCP 鏈接的超時時間,實際上就是寫事件添加到定時器中設置的超時時間
     */
    ngx_msec_t                       connect_timeout;
    /*
     * 發送請求的超時時間。一般就是寫事件添加到定時器中設置的超時時間
     */
    ngx_msec_t                       send_timeout;
    /*
     * 接收響應的超時時間。一般就是讀事件添加到定時器中設置的超時時間
     */
    ngx_msec_t                       read_timeout;
    ngx_msec_t                       next_upstream_timeout;

    /*
     * TCP 的 SO_SNOLOWAT 選項,表示發送緩衝區的下限
     */
    size_t                           send_lowat;
    /*
     * 定義了接收頭部的緩衝區分配的內存大小(ngx_http_upstream_t 中的 buffer 
     * 緩衝區),當不轉發響應給下游或者在 buffering 標誌位爲 0 的狀況下轉發
     * 響應時,它一樣表示接收包體的緩衝區大小
     */
    size_t                           buffer_size;
    size_t                           limit_rate;

    /*
     * 僅當 buffering 標誌位爲 1,而且向下遊轉發響應時生效。它會設置到 
     * ngx_event_pipe_t 結構體的 busy_size 成員中
     */
    size_t                           busy_buffers_size;
    /*
     * 在 buffering 標誌位爲 1 時,若是上游速度快於下游速度,將有可能把來自上游的
     * 響應存儲到臨時文件中,而 max_temp_file_size 指定了臨時文件的最大長度。實際
     * 上,它將限制 ngx_event_pipe_t 結構體中的 temp_file 
     */
    size_t                           max_temp_file_size;
    /*
     * 表示將緩衝區中的響應寫入臨時文件時一次寫入字符流的最大長度
     */
    size_t                           temp_file_write_size;

    size_t                           busy_buffers_size_conf;
    size_t                           max_temp_file_size_conf;
    size_t                           temp_file_write_size_conf;

    /*
     * 以緩存響應的方式轉發上游服務器的包體時所使用的內存大小
     */
    ngx_bufs_t                       bufs;

    /*
     * 針對 ngx_http_upstream_t 結構體中保存解析完的包頭的 headers_in 成員,
     * ignore_headers 能夠按照二進制位使得 upstream 在轉發包頭時跳過對某些
     * 頭部的處理。做爲 32 位整型,理論上 ignore_headers 最多能夠表示 32 個
     * 須要跳過不予處理的頭部
     */
    ngx_uint_t                       ignore_headers;
    /*
     * 以二進制位來表示一些錯誤碼,若是處理上游響應時發現這些錯誤碼,那麼在
     * 沒有將響應轉發給下游客戶端時,將會選擇下一個上游服務器來重發請求
     */
    ngx_uint_t                       next_upstream;
    /*
     * 在 buffering 標誌位爲 1 的狀況下轉發響應時,將有可能把響應存放到臨時文件
     * 中。在 ngx_http_upstream_t 中的 store 標誌位爲 1 時,store_access 表示
     * 所建立的目錄、文件的權限.
     */
    ngx_uint_t                       store_access;
    ngx_uint_t                       next_upstream_tries;
    /*
     * 決定轉發響應方式的標誌位,buffering 爲 1 時表示打開緩存,這時認爲上游
     * 的網速快於下游的網速,會盡可能地在內存或者磁盤中緩存來自上游的響應;若是
     * buffering 爲 0,僅會開闢一塊固定大小的內存塊做爲緩存來轉發響應
     */
    ngx_flag_t                       buffering;
    ngx_flag_t                       request_buffering;
    ngx_flag_t                       pass_request_headers;
    ngx_flag_t                       pass_request_body;

    /*
     * 標誌位,爲 1 時表示與上游服務器交互時將不檢查 Nginx 與下游客戶端間的鏈接
     * 是否斷開。也就是說,即便下游客戶端主動關閉了鏈接,也不會中斷與上游服務器
     * 間的交互.
     */
    ngx_flag_t                       ignore_client_abort;
    /*
     * 當解析上游響應的包頭時,若是解析後設置到 headers_in 結構體中的 status_n 
     * 錯誤碼大於 400,則會試圖把它與 error_page 中指定的錯誤碼相匹配,若是匹配
     * 上,則發送 error_page 中指定的響應,不然繼續返回上游服務器的錯誤碼.
     */
    ngx_flag_t                       intercept_errors;
    /*
     * buffering 標誌位爲 1 的狀況下轉發響應時纔有意義。這時,若是 cyclic_temp_file 
     * 爲 1,則會試圖複用臨時文件中已經使用過的空間。不建議將 cyclic_temp_file 
     * 設爲 1.
     */
    ngx_flag_t                       cyclic_temp_file;
    ngx_flag_t                       force_ranges;

    /*
     * 在 buffering 標誌位爲 1 的狀況下轉發響應時,存放臨時文件的路徑
     */
    ngx_path_t                      *temp_path;

    /*
     * 不轉發的頭部。其實是經過 ngx_http_upstream_hide_hash 方法,
     * 根據 hide_headers 和 pass_headers 動態數組構造出的須要隱藏的
     * HTTP 頭部散列表
     */
    ngx_hash_t                       hide_headers_hash;
    /*
     * 當轉發上游響應頭部(ngx_http_upstream_t 中 headers_in 結構體中的頭部)
     * 給下游客戶端時,若是不但願某些頭部轉發給下游,就設置到 hide_headers
     * 動態數組中
     */
    ngx_array_t                     *hide_headers;
    /*
     * 當轉發上游響應頭部(ngx_http_upstream_t 中的 headers_in 結構體中的頭部)
     * 給下游客戶端時,upstream 機制默認不會轉發如 "Data"、"Server" 之類的頭部,
     * 若是確實但願直接轉發它們到下游,就設置到 pass_headers 動態數組中
     */
    ngx_array_t                     *pass_headers;

    /*
     * 鏈接上游服務器時使用的本機地址
     */
    ngx_http_upstream_local_t       *local;

#if (NGX_HTTP_CACHE)
    ngx_shm_zone_t                  *cache_zone;
    ngx_http_complex_value_t        *cache_value;

    ngx_uint_t                       cache_min_uses;
    ngx_uint_t                       cache_use_stale;
    ngx_uint_t                       cache_methods;

    off_t                            cache_max_range_offset;

    ngx_flag_t                       cache_lock;
    ngx_msec_t                       cache_lock_timeout;
    ngx_msec_t                       cache_lock_age;

    ngx_flag_t                       cache_revalidate;
    ngx_flag_t                       cache_convert_head;
    ngx_flag_t                       cache_background_update;

    ngx_array_t                     *cache_valid;
    ngx_array_t                     *cache_bypass;
    ngx_array_t                     *cache_purge;
    ngx_array_t                     *no_cache;
#endif

    /*
     * 當 ngx_http_upstream_t 中的 store 標誌位爲 1 時,若是須要將上游的響應
     * 存放到文件中,store_lengths 將表示存放路徑的長度,而 store_values 
     * 表示存放路徑
     */
    ngx_array_t                     *store_lengths;
    ngx_array_t                     *store_values;

#if (NGX_HTTP_CACHE)
    signed                           cache:2;
#endif
    signed                           store:2;
    /*
     * 上面的 intercept_errors 標誌位定義了 400 以上的錯誤碼將會與 error_page
     * 比較後再行處理,實際上這個規則是能夠有一個例外狀況,若是將 intercept_404
     * 標誌位設爲 1,當上遊返回 404 時會直接轉發這個錯誤碼給下游,而不會去與
     * errpr_page 進行比較.
     */
    unsigned                         intercept_404:1;
    /*
     * 爲 1 時,將會根據 ngx_http_upstream_t 中 headers_in 結構體裏的 X-Accel-Buffering
     * 頭部(它的值會是 yes 和 no)來改變 buffering 標誌位,當其值爲 yes 時,buffering
     * 標誌位爲 1。所以,change_buffering 爲 1 時將有可能根據上游服務器返回的響應頭部,
     * 動態地決定是以上游網速優先仍是如下游網速優先
     */
    unsigned                         change_buffering:1;

#if (NGX_HTTP_SSL || NGX_COMPAT)
    ngx_ssl_t                       *ssl;
    ngx_flag_t                       ssl_session_reuse;

    ngx_http_complex_value_t        *ssl_name;
    ngx_flag_t                       ssl_server_name;
    ngx_flag_t                       ssl_verify;
#endif

    ngx_str_t                        module;

    NGX_COMPAT_BEGIN(2)
    NGX_COMPAT_END
} ngx_http_upstream_conf_t;

3. upstream 的解析

static ngx_command_t  ngx_http_upstream_commands[] = {

    { ngx_string("upstream"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
      ngx_http_upstream,
      0,
      0,
      NULL },

    { ngx_string("server"),
      NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
      ngx_http_upstream_server,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL },

      ngx_null_command
};

3.1 ngx_http_upstream

假設 upstream{} 的配置以下:tomcat

upstream rong {
    server 192.168.56.101:8080;
    server 192.168.56.101:8081;
}

則調用該函數進行解析:服務器

static char *
ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
    char                          *rv;
    void                          *mconf;
    ngx_str_t                     *value;
    ngx_url_t                      u;
    ngx_uint_t                     m;
    ngx_conf_t                     pcf;
    ngx_http_module_t             *module;
    ngx_http_conf_ctx_t           *ctx, *http_ctx;
    ngx_http_upstream_srv_conf_t  *uscf;

    ngx_memzero(&u, sizeof(ngx_url_t));

    value = cf->args->elts;
    u.host = value[1];
    u.no_resolve = 1;
    u.no_port = 1;

    /* 先從 upstreams 數組中檢測是否已有相同的項存在,若沒有則新建立一個
     * ngx_http_upstream_srv_conf_t,該結構體表明一個上游服務器,
     * 而後將其添加到 upstreams 數組中 */
    uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
                                         |NGX_HTTP_UPSTREAM_WEIGHT
                                         |NGX_HTTP_UPSTREAM_MAX_CONNS
                                         |NGX_HTTP_UPSTREAM_MAX_FAILS
                                         |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
                                         |NGX_HTTP_UPSTREAM_DOWN
                                         |NGX_HTTP_UPSTREAM_BACKUP);
    if (uscf == NULL) {
        return NGX_CONF_ERROR;
    }

    /* 對於 http{} 中的每個 block,都要構建屬於該 block 的 ngx_http_conf_ctx_t,
     * 該結構體中包含三個指針數組成員:main_conf、srv_conf、loc_conf,存放着該
     * block 解析到的全部配置 */
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    http_ctx = cf->ctx;
    /* 當前 block 的 main_conf 指向上一級的 block 的 main_conf,對於 upstream,
     * 上一級的 block 即爲 http{} */
    ctx->main_conf = http_ctx->main_conf;

    /* 下面是分配建立屬於該 upstream{} 的 srv_conf 和 loc_conf */

    /* the upstream{}'s srv_conf */

    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    /* 將上面建立的 ngx_http_upstream_srv_conf_t 的首地址存放到當前 upstream 模塊
     * 在 srv_conf 數組中的索引處
     * 注:當前 ngx_http_upstream_module 模塊沒有實現 create_srv_conf 函數,所以
     * 下面調用的 create_srv_conf 不會致使這裏的值被覆蓋了 */
    ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;

    uscf->srv_conf = ctx->srv_conf;


    /* the upstream{}'s loc_conf */

    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->loc_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;

        if (module->create_srv_conf) {
            mconf = module->create_srv_conf(cf);
            if (mconf == NULL) {
                return NGX_CONF_ERROR;
            }

            ctx->srv_conf[cf->cycle->modules[m]->ctx_index] = mconf;
        }

        if (module->create_loc_conf) {
            mconf = module->create_loc_conf(cf);
            if (mconf == NULL) {
                return NGX_CONF_ERROR;
            }

            ctx->loc_conf[cf->cycle->modules[m]->ctx_index] = mconf;
        }
    }

    /* 建立 servers 數組,該數組中每個元素都爲 ngx_http_upstream_server_t 
     * 類型的結構體,該結構體表明 upstream{} 中的 server 指令 */
    uscf->servers = ngx_array_create(cf->pool, 4,
                                     sizeof(ngx_http_upstream_server_t));
    if (uscf->servers == NULL) {
        return NGX_CONF_ERROR;
    }


    /* parse inside upstream{} */

    pcf = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_HTTP_UPS_CONF;
    
    /* 這裏開始解析 upstream{}  */
    rv = ngx_conf_parse(cf, NULL);

    *cf = pcf;

    if (rv != NGX_CONF_OK) {
        return rv;
    }

    /* 若 upstream{} 中沒有配置 server 指令,則代表發生錯誤了 */
    if (uscf->servers->nelts == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "no servers are inside upstream");
        return NGX_CONF_ERROR;
    }

    return rv;
}

3.1.1 ngx_http_upstream_add

ngx_http_upstream_srv_conf_t *
ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
{
    ngx_uint_t                      i;
    ngx_http_upstream_server_t     *us;
    ngx_http_upstream_srv_conf_t   *uscf, **uscfp;
    ngx_http_upstream_main_conf_t  *umcf;

    if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {

        if (ngx_parse_url(cf->pool, u) != NGX_OK) {
            if (u->err) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "%s in upstream \"%V\"", u->err, &u->url);
            }

            return NULL;
        }
    }

    /* 獲取 ngx_http_upstream_module 模塊在 main 級別的配置結構體 */
    umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);

    /* upstreams 是一個數組,每個數組元素的類型爲 ngx_http_upstream_srv_conf_t */
    uscfp = umcf->upstreams.elts;

    /* 遍歷該數組,檢測是否已有相同的存在,如果,則返回該已添加到 upstreams 數組中的
     * ngx_http_upstream_srv_conf_t 結構體 */
    for (i = 0; i < umcf->upstreams.nelts; i++) {

        if (uscfp[i]->host.len != u->host.len
            || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
               != 0)
        {
            continue;
        }

        if ((flags & NGX_HTTP_UPSTREAM_CREATE)
             && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE))
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "duplicate upstream \"%V\"", &u->host);
            return NULL;
        }

        if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && !u->no_port) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "upstream \"%V\" may not have port %d",
                               &u->host, u->port);
            return NULL;
        }

        if ((flags & NGX_HTTP_UPSTREAM_CREATE) && !uscfp[i]->no_port) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                          "upstream \"%V\" may not have port %d in %s:%ui",
                          &u->host, uscfp[i]->port,
                          uscfp[i]->file_name, uscfp[i]->line);
            return NULL;
        }

        if (uscfp[i]->port && u->port
            && uscfp[i]->port != u->port)
        {
            continue;
        }

        if (flags & NGX_HTTP_UPSTREAM_CREATE) {
            uscfp[i]->flags = flags;
            uscfp[i]->port = 0;
        }

        return uscfp[i];
    }

    /* 若upstreams數組中沒有,則新建立一個 ngx_http_upstream_srv_conf_t */
    uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
    if (uscf == NULL) {
        return NULL;
    }

    uscf->flags = flags;
    /* 該代理服務器的對應的域名,對於上面的示例則爲 rong */
    uscf->host = u->host;
    /* 配置文件的絕對路徑 */
    uscf->file_name = cf->conf_file->file.name.data;
    /* 記錄 upstream {} 在配置文件中的行號 */
    uscf->line = cf->conf_file->line;
    uscf->port = u->port;
    uscf->no_port = u->no_port;

    if (u->naddrs == 1 && (u->port || u->family == AF_UNIX)) {
        uscf->servers = ngx_array_create(cf->pool, 1,
                                         sizeof(ngx_http_upstream_server_t));
        if (uscf->servers == NULL) {
            return NULL;
        }

        us = ngx_array_push(uscf->servers);
        if (us == NULL) {
            return NULL;
        }

        ngx_memzero(us, sizeof(ngx_http_upstream_server_t));

        us->addrs = u->addrs;
        us->naddrs = 1;
    }

    /* 將該新構建的 ngx_http_upstream_srv_conf_t 添加到 upstreams 數組中
     * 每個 ngx_http_upstream_srv_conf_t 表明一個代理服務器 */
    uscfp = ngx_array_push(&umcf->upstreams);
    if (uscfp == NULL) {
        return NULL;
    }

    *uscfp = uscf;

    return uscf;
}

3.2 ngx_http_upstream_server

server 指令就是爲 upstream 定義一個服務器地址(帶有端口號的域名、IP 地址,或者是 UNIX 套接字)和一個可選的參數。參數以下:cookie

  • weight: 設置一個服務器的優先級優先於其餘服務器,默認爲 1.
  • max_fails:設置在 fail_timeout 時間以內嘗試對一個服務器鏈接的最大次數,若是超過這個次數,那麼就會被標記爲 down。
  • fail_timeout:在這個指定的時間內服務器必須提供響應,若是在這個時間內沒有收到響應,那麼服務器將會被標記爲 down 狀態。
  • max_conns:該服務器的最大鏈接數。
  • backup:一旦其餘服務器宕機,那麼有該標記的機器就會接收請求。
  • down:標記爲一個服務器再也不接受任何請求。
static char *
ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_upstream_srv_conf_t  *uscf = conf;

    time_t                       fail_timeout;
    ngx_str_t                   *value, s;
    ngx_url_t                    u;
    ngx_int_t                    weight, max_conns, max_fails;
    ngx_uint_t                   i;
    ngx_http_upstream_server_t  *us;

    /* 從 servers 數組中取出一個已經分配好的 ngx_http_upstream_server_t 
     * 該數組中每個 ngx_http_upstream_server_t 結構體表明一個 server */
    us = ngx_array_push(uscf->servers);
    if (us == NULL) {
        return NGX_CONF_ERROR;
    }

    ngx_memzero(us, sizeof(ngx_http_upstream_server_t));

    value = cf->args->elts;

    /* 默認權重爲 1,值越大表示該服務器的優先級越高 */
    weight = 1;
    max_conns = 0;
    max_fails = 1;
    fail_timeout = 10;

    /* 若該 server 指令有第 3 個以上的參數,則進行解析 */
    for (i = 2; i < cf->args->nelts; i++) {

        if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
                goto not_supported;
            }

            weight = ngx_atoi(&value[i].data[7], value[i].len - 7);

            if (weight == NGX_ERROR || weight == 0) {
                goto invalid;
            }

            continue;
        }

        if (ngx_strncmp(value[i].data, "max_conns=", 10) == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_CONNS)) {
                goto not_supported;
            }

            max_conns = ngx_atoi(&value[i].data[10], value[i].len - 10);

            if (max_conns == NGX_ERROR) {
                goto invalid;
            }

            continue;
        }

        if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
                goto not_supported;
            }

            max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);

            if (max_fails == NGX_ERROR) {
                goto invalid;
            }

            continue;
        }

        if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
                goto not_supported;
            }

            s.len = value[i].len - 13;
            s.data = &value[i].data[13];

            fail_timeout = ngx_parse_time(&s, 1);

            if (fail_timeout == (time_t) NGX_ERROR) {
                goto invalid;
            }

            continue;
        }

        if (ngx_strcmp(value[i].data, "backup") == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
                goto not_supported;
            }

            us->backup = 1;

            continue;
        }

        if (ngx_strcmp(value[i].data, "down") == 0) {

            if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
                goto not_supported;
            }

            us->down = 1;

            continue;
        }

        goto invalid;
    }

    ngx_memzero(&u, sizeof(ngx_url_t));

    /* 該服務器的 url */
    u.url = value[1];
    /* 若沒有指定端口,則該服務器的默認端口爲 80 */
    u.default_port = 80;

    /* 解析該 url */
    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
        if (u.err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "%s in upstream \"%V\"", u.err, &u.url);
        }

        return NGX_CONF_ERROR;
    }

    us->name = u.url;
    us->addrs = u.addrs;
    us->naddrs = u.naddrs;
    us->weight = weight;
    us->max_conns = max_conns;
    us->max_fails = max_fails;
    us->fail_timeout = fail_timeout;

    return NGX_CONF_OK;

invalid:

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "invalid parameter \"%V\"", &value[i]);

    return NGX_CONF_ERROR;

not_supported:

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "balancing method does not support parameter \"%V\"",
                       &value[i]);

    return NGX_CONF_ERROR;
}

3.2.1 ngx_parse_url

ngx_int_t
ngx_parse_url(ngx_pool_t *pool, ngx_url_t *u)
{
    u_char  *p;
    size_t   len;

    p = u->url.data;
    len = u->url.len;

    /* unix */
    if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) {
        return ngx_parse_unix_domain_url(pool, u);
    }

    /* IPv6 */
    if (len && p[0] == '[') {
        return ngx_parse_inet6_url(pool, u);
    }

    /* IPv4 */
    return ngx_parse_inet_url(pool, u);
}

3.2.2 ngx_parse_inet_url

假設當前 server 指定的 url 爲: "192.168.56.101:8080"網絡

static ngx_int_t
ngx_parse_inet_url(ngx_pool_t *pool, ngx_url_t *u)
{
    u_char               *p, *host, *port, *last, *uri, *args;
    size_t                len;
    ngx_int_t             n;
    struct sockaddr_in   *sin;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6  *sin6;
#endif
    
    u->socklen = sizeof(struct sockaddr_in);
    sin = (struct sockaddr_in *) &u->sockaddr;
    sin->sin_family = AF_INET;

    u->family = AF_INET;

    host = u->url.data;

    last = host + u->url.len;

    port = ngx_strlchr(host, last, ':');

    uri = ngx_strlchr(host, last, '/');

    args = ngx_strlchr(host, last, '?');

    if (args) {
        if (uri == NULL || args < uri) {
            uri = args;
        }
    }

    if (uri) {
        if (u->listen || !u->uri_part) {
            u->err = "invalid host";
            return NGX_ERROR;
        }

        u->uri.len = last - uri;
        u->uri.data = uri;

        last = uri;

        if (uri < port) {
            port = NULL;
        }
    }

    /* 存在端口號 */
    if (port) {
        port++;

        len = last - port;

        /* 將端口號轉化爲整型值 */
        n = ngx_atoi(port, len);

        if (n < 1 || n > 65535) {
            u->err = "invalid port";
            return NGX_ERROR;
        }

        u->port = (in_port_t) n;
        /* 將主機字節序的端口號轉化爲網絡字節序 */
        sin->sin_port = htons((in_port_t) n);

        /* 存放字符串形式的端口號 */
        u->port_text.len = len;
        u->port_text.data = port;

        last = port - 1;

    } else {
        if (uri == NULL) {

            if (u->listen) {

                /* test value as port only */

                n = ngx_atoi(host, last - host);

                if (n != NGX_ERROR) {

                    if (n < 1 || n > 65535) {
                        u->err = "invalid port";
                        return NGX_ERROR;
                    }

                    u->port = (in_port_t) n;
                    sin->sin_port = htons((in_port_t) n);

                    u->port_text.len = last - host;
                    u->port_text.data = host;

                    u->wildcard = 1;

                    return NGX_OK;
                }
            }
        }

        u->no_port = 1;
        u->port = u->default_port;
        sin->sin_port = htons(u->default_port);
    }

    len = last - host;

    if (len == 0) {
        u->err = "no host";
        return NGX_ERROR;
    }

    u->host.len = len;
    u->host.data = host;

    if (u->listen && len == 1 && *host == '*') {
        sin->sin_addr.s_addr = INADDR_ANY;
        u->wildcard = 1;
        return NGX_OK;
    }

    sin->sin_addr.s_addr = ngx_inet_addr(host, len);

    if (sin->sin_addr.s_addr != INADDR_NONE) {

        if (sin->sin_addr.s_addr == INADDR_ANY) {
            u->wildcard = 1;
        }

        u->naddrs = 1;

        u->addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));
        if (u->addrs == NULL) {
            return NGX_ERROR;
        }

        sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));
        if (sin == NULL) {
            return NGX_ERROR;
        }

        ngx_memcpy(sin, &u->sockaddr, sizeof(struct sockaddr_in));

        u->addrs[0].sockaddr = (struct sockaddr *) sin;
        u->addrs[0].socklen = sizeof(struct sockaddr_in);

        p = ngx_pnalloc(pool, u->host.len + sizeof(":65535") - 1);
        if (p == NULL) {
            return NGX_ERROR;
        }

        u->addrs[0].name.len = ngx_sprintf(p, "%V:%d",
                                           &u->host, u->port) - p;
        u->addrs[0].name.data = p;

        return NGX_OK;
    }

    if (u->no_resolve) {
        return NGX_OK;
    }

    if (ngx_inet_resolve_host(pool, u) != NGX_OK) {
        return NGX_ERROR;
    }

    u->family = u->addrs[0].sockaddr->sa_family;
    u->socklen = u->addrs[0].socklen;
    ngx_memcpy(&u->sockaddr, u->addrs[0].sockaddr, u->addrs[0].socklen);

    switch (u->family) {

#if (NGX_HAVE_INET6)
    case AF_INET6:
        sin6 = (struct sockaddr_in6 *) &u->sockaddr;

        if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
            u->wildcard = 1;
        }

        break;
#endif

    default: /* AF_INET */
        sin = (struct sockaddr_in *) &u->sockaddr;

        if (sin->sin_addr.s_addr == INADDR_ANY) {
            u->wildcard = 1;
        }

        break;
    }

    return NGX_OK;
}

4. proxy_pass 的解析

該指令指定請求被傳遞到上游服務器,格式爲 ULR。

static ngx_command_t  ngx_http_proxy_commands[] = {
    { ngx_string("proxy_pass"),
      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
      ngx_http_proxy_pass,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
      
    ...
};

當在配置文件中檢測到有 proxy_pass 指令時,會調用 ngx_http_proxy_pass 函數進行解析:

static char *
ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_proxy_loc_conf_t *plcf = conf;

    size_t                      add;
    u_short                     port;
    ngx_str_t                  *value, *url;
    ngx_url_t                   u;
    ngx_uint_t                  n;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_script_compile_t   sc;

    if (plcf->upstream.upstream || plcf->proxy_lengths) {
        return "is duplicate";
    }

    /* 獲取 ngx_http_core_module 模塊 loc 級別的配置結構體 */
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    /* 設置 handler 的回調函數 */
    clcf->handler = ngx_http_proxy_handler;

    /* 該 Location 的名稱 */
    if (clcf->name.data[clcf->name.len - 1] == '/') {
        clcf->auto_redirect = 1;
    }

    /* value = "proxy_pass" */
    value = cf->args->elts;

    /* proxy_pass 的值,即要將請求傳遞給上游服務器的 url */
    url = &value[1];

    /* 檢測是否有腳本變量,返回腳本變量的個數 */
    n = ngx_http_script_variables_count(url);

    if (n) {

        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

        sc.cf = cf;
        sc.source = url;
        sc.lengths = &plcf->proxy_lengths;
        sc.values = &plcf->proxy_values;
        sc.variables = n;
        sc.complete_lengths = 1;
        sc.complete_values = 1;

        if (ngx_http_script_compile(&sc) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

#if (NGX_HTTP_SSL)
        plcf->ssl = 1;
#endif

        return NGX_CONF_OK;
    }

    if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) {
        add = 7;
        port = 80;

    } else if (ngx_strncasecmp(url->data, (u_char *) "https://", 8) == 0) {

#if (NGX_HTTP_SSL)
        plcf->ssl = 1;

        add = 8;
        port = 443;
#else
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "https protocol requires SSL support");
        return NGX_CONF_ERROR;
#endif

    } else {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid URL prefix");
        return NGX_CONF_ERROR;
    }

    ngx_memzero(&u, sizeof(ngx_url_t));

    /* 去掉 "http://" 或 "https://" 後的長度 */
    u.url.len = url->len - add;
    u.url.data = url->data + add;
    u.default_port = port;
    u.uri_part = 1;
    u.no_resolve = 1;

    /* 根據該 proxy_pass 指定的 url 名稱,從 ngx_http_upstream_main_conf_t 
     * 結構體的 upstreams 數組中找到該 url 對應的 upstream */
    plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
    if (plcf->upstream.upstream == NULL) {
        return NGX_CONF_ERROR;
    }

    plcf->vars.schema.len = add;
    plcf->vars.schema.data = url->data;
    plcf->vars.key_start = plcf->vars.schema;

    ngx_http_proxy_set_vars(&u, &plcf->vars);

    /* 當前 location{} 的名稱 */
    plcf->location = clcf->name;

    if (clcf->named
#if (NGX_PCRE)
        || clcf->regex
#endif
        || clcf->noname)
    {
        if (plcf->vars.uri.len) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "\"proxy_pass\" cannot have URI part in "
                               "location given by regular expression, "
                               "or inside named location, "
                               "or inside \"if\" statement, "
                               "or inside \"limit_except\" block");
            return NGX_CONF_ERROR;
        }

        plcf->location.len = 0;
    }

    plcf->url = *url;

    return NGX_CONF_OK;
}

4.1 ngx_http_proxy_set_vars

static void
ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v)
{
    if (u->family != AF_UNIX) {

        if (u->no_port || u->port == u->default_port) {

            v->host_header = u->host;

            if (u->default_port == 80) {
                ngx_str_set(&v->port, "80");

            } else {
                ngx_str_set(&v->port, "443");
            }

        } else {
            v->host_header.len = u->host.len + 1 + u->port_text.len;
            v->host_header.data = u->host.data;
            v->port = u->port_text;
        }

        v->key_start.len += v->host_header.len;

    } else {
        ngx_str_set(&v->host_header, "localhost");
        ngx_str_null(&v->port);
        v->key_start.len += sizeof("unix:") - 1 + u->host.len + 1;
    }

    v->uri = u->uri;
}

5. ngx_http_init_connection

對於 http 鏈接事件,當監聽的客戶端鏈接請求並接受鏈接後,第一個調用的函數都爲該 ngx_http_init_connection 函數。該函數構建了該服務器與客戶端之間的鏈接 ngx_connection_t 結構體,並將讀事件添加到定時器和 epoll 事件監控機制中。當監聽到客戶端發送的數據到達時,即會調用回調函數 ngx_http_wait_request_handler 進行處理。

void
ngx_http_init_connection(ngx_connection_t *c)
{
    ngx_uint_t              i;
    ngx_event_t            *rev;
    struct sockaddr_in     *sin;
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_log_ctx_t     *ctx;
    ngx_http_connection_t  *hc;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6    *sin6;
    ngx_http_in6_addr_t    *addr6;
#endif
    
    /* 爲當前的 HTTP 鏈接建立一個 ngx_http_connection_t 結構體,該結構體
     * 表明當前的 HTTP 鏈接 */
    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
    if (hc == NULL) {
        ngx_http_close_connection(c);
        return;
    }
    
    /* 將 data 指針指向表示當前 HTTP 鏈接的 ngx_http_connection_t */
    c->data = hc;

    /* find the server configuration for the address:port */

    /* listening:這個鏈接對應的 ngx_listening_t 監聽對象,此鏈接由 listening
     * 監聽端口的事件創建.
     * servers: 對於 HTTP 模塊,該指針指向 ngx_http_port_t 結構體,該結構體
     * 實際保存着當前監聽端口的地址信息.
     */
    port = c->listening->servers;

    /* 若該端口對應主機上的多個地址 */
    if (port->naddrs > 1) {

        /*
         * there are several addresses on this port and one of them
         * is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
         * is required to determine a server address
         */

        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }

        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        case AF_INET6:
            sin6 = (struct sockaddr_in6 *) c->local_sockaddr;

            addr6 = port->addrs;

            /* the last address is "*" */

            for (i = 0; i < port->naddrs - 1; i++) {
                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
                    break;
                }
            }

            hc->addr_conf = &addr6[i].conf;

            break;
#endif

        default: /* AF_INET */
            sin = (struct sockaddr_in *) c->local_sockaddr;

            addr = port->addrs;

            /* the last address is "*" */

            for (i = 0; i < port->naddrs - 1; i++) {
                if (addr[i].addr == sin->sin_addr.s_addr) {
                    break;
                }
            }

            hc->addr_conf = &addr[i].conf;

            break;
        }

    } else {

        /* 本機的監聽端口對應的 sockaddr 結構體 */
        switch (c->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
        case AF_INET6:
            addr6 = port->addrs;
            hc->addr_conf = &addr6[0].conf;
            break;
#endif

        default: /* AF_INET */
            addr = port->addrs;
            hc->addr_conf = &addr[0].conf;
            break;
        }
    }

    /* the default server configuration for the address:port */
    hc->conf_ctx = hc->addr_conf->default_server->ctx;

    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
    if (ctx == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    ctx->connection = c;
    ctx->request = NULL;
    ctx->current_request = NULL;

    c->log->connection = c->number;
    c->log->handler = ngx_http_log_error;
    c->log->data = ctx;
    c->log->action = "waiting for request";

    c->log_error = NGX_ERROR_INFO;

    /* 鏈接對應的讀事件 */
    rev = c->read;
    /* 爲該鏈接的讀事件設置回調處理函數 */
    rev->handler = ngx_http_wait_request_handler;
    /* 爲該鏈接的寫事件設置回調處理函數,該函數爲一個空函數,什麼也不作 */
    c->write->handler = ngx_http_empty_handler;

#if (NGX_HTTP_V2)
    if (hc->addr_conf->http2) {
        rev->handler = ngx_http_v2_init;
    }
#endif

#if (NGX_HTTP_SSL)
    {
    ngx_http_ssl_srv_conf_t  *sscf;

    sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module);

    if (sscf->enable || hc->addr_conf->ssl) {

        c->log->action = "SSL handshaking";

        if (hc->addr_conf->ssl && sscf->ssl.ctx == NULL) {
            ngx_log_error(NGX_LOG_ERR, c->log, 0,
                          "no \"ssl_certificate\" is defined "
                          "in server listening on SSL port");
            ngx_http_close_connection(c);
            return;
        }

        hc->ssl = 1;

        rev->handler = ngx_http_ssl_handshake;
    }
    }
#endif

    if (hc->addr_conf->proxy_protocol) {
        hc->proxy_protocol = 1;
        c->log->action = "reading PROXY protocol";
    }

    /* 
     * 標誌位,爲1時表示當前事件已經準備就緒,也就是說,容許這個事件的消費者模塊
     * 處理這個事件。在HTTP框架中,常常會檢查事件的ready標誌位以肯定是否能夠接收
     * 請求或者發送響應 */
    if (rev->ready) {
        /* the deferred accept(), iocp */

        /* 爲 1,表示開啓了負載均衡機制,此時不會馬上執行該讀事件,而是將當前的
         * 讀事件添加到 ngx_posted_events 延遲執行隊列中 */
        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        /* 若沒有開啓負載均衡機制,則直接處理該讀事件 */
        rev->handler(rev);
        return;
    }

    /* 將讀事件添加到定時器中,超時時間爲 post_accept_timeout 毫秒 
     * post_accept_timeout 在配置文件中沒有配置的話,默認爲 60000
     * 毫秒 */
    ngx_add_timer(rev, c->listening->post_accept_timeout);
    /* 將該鏈接添加到可重用雙向鏈表的頭部 */
    ngx_reusable_connection(c, 1);

    /* 將該讀事件添加到事件驅動模塊中,這樣當該事件對應的 TCP 鏈接上
     * 一旦出現可讀事件(如接收到 TCP 鏈接的另外一端發送來的字節流)就會
     * 回調該事件的 handler 方法 */
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

6. ngx_http_wait_request_handler

當監聽到服務器與客戶端之間的套接字可讀,即客戶端發送數據給服務器時,即會調用該 ngx_http_wait_request_handler 函數進行處理。

static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    u_char                    *p;
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_connection_t          *c;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    /* 事件相關的對象。一般 data 都是指向 ngx_connection_t 鏈接對象。
     * 開啓文件異步 I/O 時,它可能會指向 ngx_event_aio_t 結構體 */
    c = rev->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");

    /* 檢查該讀事件是否已經超時,若超時,則關閉該鏈接 */
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }

    /* 標誌位,爲 1 時表示鏈接關閉 */
    if (c->close) {
        ngx_http_close_connection(c);
        return;
    }

    /* 由 ngx_http_init_connection 函數知,此時該 data 指針指向
     * ngx_http_connection_t 結構體 */
    hc = c->data;
    /* 獲取該 server{} 對應的配置項結構體 */
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    size = cscf->client_header_buffer_size;

    /* 用於接收、緩存客戶端發來的字節流,每一個事件消費模塊可自由決定從鏈接池中
     * 分配多大的空間給 buffer 這個接收緩存字段。例如,在 HTTP 模塊中,它的大小
     * 決定於 client_header_buffer_size 配置項 */
    b = c->buffer;

    /* 若沒有爲當前鏈接的接收/發送緩存分配內存 */
    if (b == NULL) {
        /* 分配一個 size 大小的臨時緩存(表示該緩存中的數據在內存中且
         * 該緩存中的數據能夠被修改) */
        b = ngx_create_temp_buf(c->pool, size);
        if (b == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        c->buffer = b;

    } else if (b->start == NULL) {

        b->start = ngx_palloc(c->pool, size);
        if (b->start == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }

    /* 調用接收字節流的回調函數 ngx_unix_recv 接收客戶端發送的數據 */
    n = c->recv(c, b->last, size);

    if (n == NGX_AGAIN) {

        if (!rev->timer_set) {
            ngx_add_timer(rev, c->listening->post_accept_timeout);
            ngx_reusable_connection(c, 1);
        }

        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }

        /*
         * We are trying to not hold c->buffer's memory for an idle connection.
         */

        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
            b->start = NULL;
        }

        return;
    }

    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }

    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client closed connection");
        ngx_http_close_connection(c);
        return;
    }

    /* last 指向緩存中有效數據的末尾 */
    b->last += n;

    if (hc->proxy_protocol) {
        hc->proxy_protocol = 0;

        p = ngx_proxy_protocol_read(c, b->pos, b->last);

        if (p == NULL) {
            ngx_http_close_connection(c);
            return;
        }

        b->pos = p;

        if (b->pos == b->last) {
            c->log->action = "waiting for request";
            b->pos = b->start;
            b->last = b->start;
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }
    }

    c->log->action = "reading client request line";

    /* 將該鏈接從 reusable_connections_queue 可重用雙向鏈表中刪除 */
    ngx_reusable_connection(c, 0);

    /* 爲當前客戶端鏈接建立並初始化一個 ngx_http_request_t 結構體
     * 並將 c->data 指向該結構體 */
    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }

    /* 設置該讀事件的回調處理函數 */
    rev->handler = ngx_http_process_request_line;
    /* 開始解析該客戶端請求的請求行 */
    ngx_http_process_request_line(rev);
}

該函數主要是接收該客戶端發送的數據,而後調用 ngx_http_create_request 函數爲該客戶端的請求建立一個 ngx_http_request_t 結構體,用於專門處理這次客戶端的請求。

6.1 ngx_http_create_request

ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{
    ngx_pool_t                 *pool;
    ngx_time_t                 *tp;
    ngx_http_request_t         *r;
    ngx_http_log_ctx_t         *ctx;
    ngx_http_connection_t      *hc;
    ngx_http_core_srv_conf_t   *cscf;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_core_main_conf_t  *cmcf;

    /* 處理請求的次數加 1 */
    c->requests++;

    /* 在該函數返回前,data 仍是指向 ngx_http_connection_t 結構體 */
    hc = c->data;

    /* 獲取當前 server{} 下的配置結構體 */
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);

    /* 爲該客戶端請求分配一個內存池 */
    pool = ngx_create_pool(cscf->request_pool_size, c->log);
    if (pool == NULL) {
        return NULL;
    }

    /* 從內存池 pool 中爲 ngx_http_request_t 結構體分配內存 */
    r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
    if (r == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    
    /* 該請求的內存池,在 ngx_http_free_request 方法中銷燬。
     * 它與 ngx_connection_t 中的內存池意義不一樣,當請求釋放時,TCP 鏈接
     * 可能並無關閉,這時請求的內存池會銷燬,但 ngx_connection_t 的
     * 內存池並不會銷燬. */
    r->pool = pool;

    /* 表明當前 HTTP 鏈接 */
    r->http_connection = hc;
    r->signature = NGX_HTTP_MODULE;
    /* 指向這個請求對應的客戶端鏈接 */
    r->connection = c;

    /* 存放請求對應的存放 main 級別配置結構體的指針數組 */
    r->main_conf = hc->conf_ctx->main_conf;
    /* 存放請求對應的存放 srv 級別配置結構體的指針數組 */
    r->srv_conf = hc->conf_ctx->srv_conf;
    /* 存放請求對應的存放 loc 級別配置結構體的指針數組 */
    r->loc_conf = hc->conf_ctx->loc_conf;

    /* 在接收完 HTTP 頭部,第一次在業務上處理 HTTP 請求時,HTTP 框架提供的
     * 處理方法是 ngx_http_process_request。但若是該方法沒法一次處理完該
     * 請求的所有業務,在歸還控制權到 epoll 事件模塊後,該請求回調時,
     * 將經過 ngx_http_request_handler 方法來處理,而這個方法中對於可讀
     * 事件的處理就是調用 read_event_handler 處理請求,也就是說,HTTP 模塊
     * 但願在底層處理請求的讀事件時,從新實現 read_event_handler 方法 */
    r->read_event_handler = ngx_http_block_reading;

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    ngx_set_connection_log(r->connection, clcf->error_log);

    /* header_in: 存儲讀取到的 HTTP 頭部數據 */
    r->header_in = hc->busy ? hc->busy->buf : c->buffer;

    /* headers_out: HTTP 模塊會把想要發送的 HTTP 響應信息放到 headers_out 中,
     * 指望 HTTP 框架將 headers_out 中的成員序列化爲 HTTP 響應包發送給用戶 */
    if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
                      sizeof(ngx_table_elt_t))
        != NGX_OK)
    {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

    if (ngx_list_init(&r->headers_out.trailers, r->pool, 4,
                      sizeof(ngx_table_elt_t))
        != NGX_OK)
    {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

    /* 存放指向全部的 HTTP 模塊的上下文結構體的指針數組 */
    r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
    if (r->ctx == NULL) {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
                                        * sizeof(ngx_http_variable_value_t));
    if (r->variables == NULL) {
        ngx_destroy_pool(r->pool);
        return NULL;
    }

#if (NGX_HTTP_SSL)
    if (c->ssl) {
        r->main_filter_need_in_memory = 1;
    }
#endif

    /* 當前請求既多是用戶發來的請求,也多是派生出的子請求,而 main
     * 則標識一系列相關的派生子請求的原始請求,通常可經過 main 和當前
     * 請求的地址是否相等來判斷當前請求是否爲用戶發來的原始請求 */
    r->main = r;
    /* 表示當前請求的引用次數。例如,在使用 subrequest 功能時,依附在
     * 這個請求上的子請求數目會返回到 count 上,每增長一個子請求,count
     * 數就要加 1. 其中任何一個子請求派生出新的子請求時,對應的原始請求
     *(main 指針指向的請求)的 count 值都要加 1。又如,當咱們接收 HTTP 
     * 包體時,因爲這也是一個異步調用,全部 count 上也須要加 1,這樣在結束
     * 請求時,就不會在 count 引用計數未清零時銷燬請求。
     *
     * 在 HTTP 模塊中每進行一類新的操做,包括爲一個請求添加新的事件,或者把
     * 一些已經由定時器、epoll 中移除的事件從新加入其中,都須要把這個請求的
     * 引用計數加 1,這是由於須要讓 HTTP 框架知道,HTTP 模塊對於該請求有
     * 獨立的異步處理機制,將由該 HTTP 模塊決定這個操做何時結束,防止
     * 在這個操做還未結束時 HTTP 框架卻把這個請求銷燬了 */
    r->count = 1;

    tp = ngx_timeofday();
    /* 當前請求初始化時的時間。start_sec是格林威治時間1970年1月1日0:0:0到當前時間的秒數。
     * 若是這個請求是子請求,則該時間是子請求的生成時間;若是這個請求是用戶發來的請求,
     * 則是在創建起TCP鏈接後,第一次接收到可讀事件時的時間 */
    r->start_sec = tp->sec;
    /* 與start_sec配合使用,表示相對於start_sec秒的毫秒偏移量 */
    r->start_msec = tp->msec;

    r->method = NGX_HTTP_UNKNOWN;
    r->http_version = NGX_HTTP_VERSION_10;

    /* ngx_http_process_request_headers 方法在接收、解析完 HTTP 請求的
     * 頭部後,會把解析完的每個HTTP頭部加入到 headers_in 的 headers 鏈表中,
     * 同時會構造 headers_in 中的其餘成員 */
    r->headers_in.content_length_n = -1;
    r->headers_in.keep_alive_n = -1;
    r->headers_out.content_length_n = -1;
    r->headers_out.last_modified_time = -1;

    /* 表示使用 rewrite 重寫 URL 的次數。由於目前最多能夠更改 10 次,
     * 因此 uri_changes 初始化爲 11,而每重寫 URL 一次就把 uri_changes 
     * 減 1,一旦 uri_changes 等於 0,則向用戶返回失敗 */
    r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
    /* 表示容許派生子請求的個數,當前最多可爲 50,所以該值初始化爲 51 */
    r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;

    /* 設置當前請求的狀態爲正在讀取請求的狀態 */
    r->http_state = NGX_HTTP_READING_REQUEST_STATE;

    ctx = c->log->data;
    ctx->request = r;
    ctx->current_request = r;
    r->log_handler = ngx_http_log_error_handler;

#if (NGX_STAT_STUB)
    (void) ngx_atomic_fetch_add(ngx_stat_reading, 1);
    r->stat_reading = 1;
    (void) ngx_atomic_fetch_add(ngx_stat_requests, 1);
#endif

    return r;
}

7. ngx_http_process_request_line

當爲該客戶端的請求建立好 ngx_http_request_t 結構體後,調用 ngx_http_process_request_line 函數處理該請求的請求行。

static void
ngx_http_process_request_line(ngx_event_t *rev)
{
    ssize_t              n;
    ngx_int_t            rc, rv;
    ngx_str_t            host;
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    /* rev->data 指向當前客戶端鏈接對象 ngx_connection_t */
    c = rev->data;
    /* 由前面知,當接收到客戶端的請求數據併爲該請求建立一個 
     * ngx_http_request_t 結構體後,c->data 就從新設置爲指向
     * 該結構體 */
    r = c->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request line");

    /* 檢測該讀事件是否已經超時 */
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    rc = NGX_AGAIN;

    for ( ;; ) {

        if (rc == NGX_AGAIN) {
            /* 讀取客戶端的請求數據到 header_in 指向的緩存中,若該緩存中
             * 已有數據,則直接返回該緩存中數據的大小 */
            n = ngx_http_read_request_header(r);

            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }

        /* 該函數僅解析請求消息中的第一個行,即請求行 */
        rc = ngx_http_parse_request_line(r, r->header_in);

        /* 解析請求行成功 */
        if (rc == NGX_OK) {

            /* the request line has been parsed successfully */

            /* 請求行的大小 */
            r->request_line.len = r->request_end - r->request_start;
            /* 指向接收緩衝區中請求行的起始地址,注意,這裏並無內存分配/拷貝 */
            r->request_line.data = r->request_start;
            r->request_length = r->header_in->pos - r->request_start;

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http request line: \"%V\"", &r->request_line);

            /* 該請求的方法名,GET 或 POST 或其餘 */
            r->method_name.len = r->method_end - r->request_start + 1;
            r->method_name.data = r->request_line.data;

            /* http_protocol.data = "HTTP/1.1" */
            if (r->http_protocol.data) {
                r->http_protocol.len = r->request_end - r->http_protocol.data;
            }

            /* 解析該請求的 uri */
            if (ngx_http_process_request_uri(r) != NGX_OK) {
                return;
            }

            if (r->host_start && r->host_end) {

                host.len = r->host_end - r->host_start;
                host.data = r->host_start;

                rc = ngx_http_validate_host(&host, r->pool, 0);

                if (rc == NGX_DECLINED) {
                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                  "client sent invalid host in request line");
                    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
                    return;
                }

                if (rc == NGX_ERROR) {
                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return;
                }

                if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
                    return;
                }

                r->headers_in.server = host;
            }

            if (r->http_version < NGX_HTTP_VERSION_10) {

                if (r->headers_in.server.len == 0
                    && ngx_http_set_virtual_server(r, &r->headers_in.server)
                       == NGX_ERROR)
                {
                    return;
                }

                ngx_http_process_request(r);
                return;
            }


            /* 初始化該 header_in.headers 鏈表 */
            if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
                              sizeof(ngx_table_elt_t))
                != NGX_OK)
            {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            c->log->action = "reading client request headers";

            /* 上面解析完請求行後,開始處理請求消息的請求頭部 */
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);

            return;
        }

        if (rc != NGX_AGAIN) {

            /* there was error while a request line parsing */

            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);

            if (rc == NGX_HTTP_PARSE_INVALID_VERSION) {
                ngx_http_finalize_request(r, NGX_HTTP_VERSION_NOT_SUPPORTED);

            } else {
                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
            }

            return;
        }

        /* NGX_AGAIN: a request line parsing is still incomplete */

        /* ngx_http_parse_reqeust_line 方法返回NGX_AGAIN,則表示須要接收更多的字符流,
         * 這時須要對header_in緩衝區作判斷,檢查是否還有空閒的內存,若是還有未使用的
         * 內存能夠繼續接收字符流,不然調用ngx_http_alloc_large_header_buffer方法
         * 分配更多的接收緩衝區。究竟是分配多大?這有nginx.conf文件中的
         * large_client_header_buffers 配置項指定 */
        if (r->header_in->pos == r->header_in->end) {

            rv = ngx_http_alloc_large_header_buffer(r, 1);

            if (rv == NGX_ERROR) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            if (rv == NGX_DECLINED) {
                r->request_line.len = r->header_in->end - r->request_start;
                r->request_line.data = r->request_start;

                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent too long URI");
                ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
                return;
            }
        }
    }
}

8. ngx_http_process_request_headers

當處理完請求消息的請求行後,就會調用 ngx_http_process_request_headers 函數開始處理請求消息的請求頭部。

static void
ngx_http_process_request_headers(ngx_event_t *rev)
{
    u_char                     *p;
    size_t                      len;
    ssize_t                     n;
    ngx_int_t                   rc, rv;
    ngx_table_elt_t            *h;
    ngx_connection_t           *c;
    ngx_http_header_t          *hh;
    ngx_http_request_t         *r;
    ngx_http_core_srv_conf_t   *cscf;
    ngx_http_core_main_conf_t  *cmcf;

    c = rev->data;
    r = c->data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request header line");

    /* 檢測該讀事件是否超時 */
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    /* 獲取 main 級別的配置 */
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    rc = NGX_AGAIN;

    /* 在該循環中,將 HTTP 請求頭一個個的解析出來,並添加到 
     * headers_in.header 鏈表中 */
    for ( ;; ) {

        if (rc == NGX_AGAIN) {

            /* 若當前 heder_in 指向的緩存已所有使用完,則須要分配更多的內存 */
            if (r->header_in->pos == r->header_in->end) {

                /* 爲該緩存分配更多的內存 */
                rv = ngx_http_alloc_large_header_buffer(r, 0);

                if (rv == NGX_ERROR) {
                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return;
                }

                if (rv == NGX_DECLINED) {
                    p = r->header_name_start;

                    r->lingering_close = 1;

                    if (p == NULL) {
                        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                      "client sent too large request");
                        ngx_http_finalize_request(r,
                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
                        return;
                    }

                    len = r->header_in->end - p;

                    if (len > NGX_MAX_ERROR_STR - 300) {
                        len = NGX_MAX_ERROR_STR - 300;
                    }

                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                "client sent too long header line: \"%*s...\"",
                                len, r->header_name_start);

                    ngx_http_finalize_request(r,
                                            NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
                    return;
                }
            }

            /* 讀取數據,若 header_in 指向的緩存中仍然有未處理的數據,則
             * 直接返回,不然須要從 socket 中讀取數據 */
            n = ngx_http_read_request_header(r);

            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }

        /* the host header could change the server configuration context */
        /* 獲取當前 server{} 下的配置 */
        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

        /* 該函數是將頭部數據解析出一行 */
        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);

        if (rc == NGX_OK) {

            r->request_length += r->header_in->pos - r->header_name_start;

            if (r->invalid_header && cscf->ignore_invalid_headers) {

                /* there was error while a header line parsing */

                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent invalid header line: \"%*s\"",
                              r->header_end - r->header_name_start,
                              r->header_name_start);
                continue;
            }

            /* a header line has been parsed successfully */
            
            /* 將解析出來的請求頭存入到 headers_in.headers 鏈表中 */
            h = ngx_list_push(&r->headers_in.headers);
            if (h == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            h->hash = r->header_hash;

            /* 頭部名稱,如 "Host" */
            h->key.len = r->header_name_end - r->header_name_start;
            h->key.data = r->header_name_start;
            h->key.data[h->key.len] = '\0';

            /* 該頭部對應的值,如 "192.168.56.101" */
            h->value.len = r->header_end - r->header_start;
            h->value.data = r->header_start;
            h->value.data[h->value.len] = '\0';

            
            h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
            if (h->lowcase_key == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            if (h->key.len == r->lowcase_index) {
                /* r->lowcase_header 存放的上面解析出來的 h->key.data 
                 * 的小寫字符串  */
                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);

            } else {
                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
            }

            /* 在 headers_in_hash 指向的 hash 表中尋找是否與該 lowcase_key 
             * 相同的項 */
            hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
                               h->lowcase_key, h->key.len);

            /* 若能找到,則調用該頭部對應的的處理方法 */
            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
                return;
            }

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header: \"%V: %V\"",
                           &h->key, &h->value);

            continue;
        }

        /* 請求消息的請求頭部解析完成 */
        if (rc == NGX_HTTP_PARSE_HEADER_DONE) {

            /* a whole header has been parsed successfully */

            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header done");

            r->request_length += r->header_in->pos - r->header_name_start;

            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;

            /* 對解析後的 HTTP 頭部字段的一些處理,檢測解析出來的請求頭是否正確 */
            rc = ngx_http_process_request_header(r);

            if (rc != NGX_OK) {
                return;
            }

            /* 在解析並處理 HTTP 的頭部數據後,開始處理該 HTTP 請求 */
            ngx_http_process_request(r);

            return;
        }

        if (rc == NGX_AGAIN) {

            /* a header line parsing is still not complete */

            continue;
        }

        /* rc == NGX_HTTP_PARSE_INVALID_HEADER */

        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client sent invalid header line");

        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return;
    }
}

9. ngx_http_process_request

void
ngx_http_process_request(ngx_http_request_t *r)
{
    ngx_connection_t  *c;

    c = r->connection;

#if (NGX_HTTP_SSL)

    if (r->http_connection->ssl) {
        long                      rc;
        X509                     *cert;
        ngx_http_ssl_srv_conf_t  *sscf;

        if (c->ssl == NULL) {
            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          "client sent plain HTTP request to HTTPS port");
            ngx_http_finalize_request(r, NGX_HTTP_TO_HTTPS);
            return;
        }

        sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);

        if (sscf->verify) {
            rc = SSL_get_verify_result(c->ssl->connection);

            if (rc != X509_V_OK
                && (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc)))
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client SSL certificate verify error: (%l:%s)",
                              rc, X509_verify_cert_error_string(rc));

                ngx_ssl_remove_cached_session(sscf->ssl.ctx,
                                       (SSL_get0_session(c->ssl->connection)));

                ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR);
                return;
            }

            if (sscf->verify == 1) {
                cert = SSL_get_peer_certificate(c->ssl->connection);

                if (cert == NULL) {
                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                  "client sent no required SSL certificate");

                    ngx_ssl_remove_cached_session(sscf->ssl.ctx,
                                       (SSL_get0_session(c->ssl->connection)));

                    ngx_http_finalize_request(r, NGX_HTTPS_NO_CERT);
                    return;
                }

                X509_free(cert);
            }
        }
    }

#endif

    /* 因爲如今已經開始準備調用各 HTTP 模塊處理請求了,再也不存在
     * 接收 HTTP 請求頭部超時的問題,所以須要從定時器中將當前
     * 鏈接的讀事件移除 
     */
    if (c->read->timer_set) {
        ngx_del_timer(c->read);
    }

#if (NGX_STAT_STUB)
    (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
    r->stat_reading = 0;
    (void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
    r->stat_writing = 1;
#endif

    /* 設置該讀、寫事件的回調函數 */
    c->read->handler = ngx_http_request_handler;
    c->write->handler = ngx_http_request_handler;
    /* 
     * 設置 ngx_http_request_t 結構體的 read_event_handler 方法爲
     * ngx_http_block_reading。當再次有讀事件到來時,這個方法能夠
     * 認爲不作任何事,它的意義在於,目前已經開始處理 HTTP 請求,
     * 除非某個 HTTP 模塊從新設置了 read_event_handler 方法,不然
     * 任何讀事件都將得不處處理,也能夠認爲讀事件被阻塞了 
     */
    r->read_event_handler = ngx_http_block_reading;
    
    /* 該函數肯定 phase_handler 的值,即從 ngx_http_phase_engine_t 指定
     * 數組的第幾個回調函數開始執行,而後依次執行 HTTP 的各個階段 */
    ngx_http_handler(r);

    ngx_http_run_posted_requests(c);
}

9.1 ngx_http_handler

void
ngx_http_handler(ngx_http_request_t *r)
{
    ngx_http_core_main_conf_t  *cmcf;

    r->connection->log->action = NULL;

    /* 若是 internal 標誌位爲 1,則表示當前須要作內部跳轉,將要把
     * 結構體中的 phase_handler 序號置爲 server_rewrite_index. */
    if (!r->internal) {
        /* 當 internal 標誌位爲 0 時,表示不須要重定向(如剛開始處理請求時),
         * 將 phase_handler 序號置爲 0,意味着從 ngx_http_phase_engine_t 指定
         * 數組的第一個回調方法開始執行 */
        switch (r->headers_in.connection_type) {
        case 0:
            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
            break;

        case NGX_HTTP_CONNECTION_CLOSE:
            r->keepalive = 0;
            break;

        case NGX_HTTP_CONNECTION_KEEP_ALIVE:
            /* 標誌位,爲 1 表示當前請求是 keepalive 請求,即長鏈接 */
            r->keepalive = 1;
            break;
        }

        /* 延遲關閉標誌位,爲 1 時表示須要延遲關閉。例如,在接收完
         * HTTP 頭部時若是發現包體存在,該標誌位爲設爲 1,而放棄接收
         * 包體時會設爲 0 */
        r->lingering_close = (r->headers_in.content_length_n > 0
                              || r->headers_in.chunked);
        
        /* 置爲 0,表示從 ngx_http_phase_engine_t 指定數組的第一個回調
         * 方法開始執行 */
        r->phase_handler = 0;

    } else {
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
        /* 這裏,把phase_handler序號設爲server_rewrite_index,這意味着
         * 不管以前執行到哪個階段,立刻都要從新從NGX_HTTP_SERVER_REWRITE_PHASE
         * 階段開始再次執行,這是Nginx的請求能夠反覆rewrite重定向的基礎 */
        r->phase_handler = cmcf->phase_engine.server_rewrite_index;
    }

    r->valid_location = 1;
#if (NGX_HTTP_GZIP)
    r->gzip_tested = 0;
    r->gzip_ok = 0;
    r->gzip_vary = 0;
#endif

    r->write_event_handler = ngx_http_core_run_phases;
    /* 開始執行 HTTP 請求的各個階段 */
    ngx_http_core_run_phases(r);
}

10. ngx_http_core_run_phases

/* ngx_http_phase_engine_t結構體就是全部的ngx_http_phase_handler_t組成的數組 */
typedef struct {
    /* handlers是由ngx_http_phase_handler_t構成的數組首地址,它表示一個請求可能
     * 經歷的全部ngx_http_handler_pt處理方法 */
    ngx_http_phase_handler_t        *handlers;

    /* 表示NGX_HTTP_SERVER_REWRITE_PHASE階段第1個ngx_http_phase_handler_t處理方法
     * 在handlers數組中的序號,用於在執行HTTP請求的任何階段中快速跳轉到
     * NGX_HTTP_SERVER_REWRITE_PHASE階段處理請求 */
    ngx_uint_t                       server_rewrite_index;

    /* 表示NGX_HTTP_REWRITE_PHASE階段第1個ngx_http_phase_handler_t處理方法
     * 在handlers數組中的序號,用於在執行HTTP請求的任何階段中快速跳轉到
     * NGX_HTTP_REWRITE_PHASE階段處理請求 */
    ngx_uint_t                       location_rewrite_index;
}ngx_http_phase_engine_t;

void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    
    /* handlers 是由 ngx_http_phase_handler_t 構成的數組首地址,它表示
     * 一個請求可能經歷的全部 ngx_http_handler_pt 處理方法 */
    ph = cmcf->phase_engine.handlers;

    /*
     * 在處理到某一個 HTTP 階段時,HTTP 框架將會在 checker 方法已實現的前提下
     * 首先調用 checket 方法來處理請求,而不會直接調用任何階段中的handler方法,
     * 只有在checket方法中才會去調用handler方法。所以,事實上全部的checker方法
     * 都是由框架中的 ngx_http_core_module 模塊實現的,且普通的 HTTP 模塊沒法
     * 重定義 checket 方法 */
    while (ph[r->phase_handler].checker) {

        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

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

下面 HTTP 各個階段的分析可參考 HTTP 階段執行

執行 HTTP 各個階段中,在 NGX_HTTP_CONTENT_PHASE 階段以前的全部階段均可參考 HTTP 階段執行,當到了 NGX_HTTP_CONTENT_PHASE 階段以後,就會開始進行與 upstream 相關的處理。

11. NGX_HTTP_CONTENT_PHASE

該階段實現的 checker 方法爲 ngx_http_core_content_phase:

ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    size_t     root;
    ngx_int_t  rc;
    ngx_str_t  path;

    /* r->content_handler 是在執行 NGX_HTTP_FIND_CONFIG_PHASE 階段中找到匹配的
     * location 後,調用 ngx_http_update_location_config 函數進行設置的,而該函數
     * 中賦給 r->content_handler 的 clcf->handler 又是在解析 proxy_pass 設置的,
     * 指向的回調函數爲 ngx_http_proxy_handler 函數 */
    if (r->content_handler) {
        /* 指向的回調函數爲一個空函數,什麼也不處理 */
        r->write_event_handler = ngx_http_request_empty_handler;
        /* 所以,這裏會先調用 ngx_http_proxy_handler 函數進行處理 */
        ngx_http_finalize_request(r, r->content_handler(r));
        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "content phase: %ui", r->phase_handler);

    rc = ph->handler(r);

    if (rc != NGX_DECLINED) {
        ngx_http_finalize_request(r, rc);
        return NGX_OK;
    }

    /* rc == NGX_DECLINED */

    ph++;

    if (ph->checker) {
        r->phase_handler++;
        return NGX_AGAIN;
    }

    /* no content handler was found */

    if (r->uri.data[r->uri.len - 1] == '/') {

        if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "directory index of \"%s\" is forbidden", path.data);
        }

        ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
        return NGX_OK;
    }

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");

    ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
    return NGX_OK;
}
相關文章
相關標籤/搜索