初探nginx HTTP處理流程

作者:景羅

基本概念:

  Nginx作爲一款開源的、高性能的HTTP服務器和反向代理服務器而聞名,本文基於nginx-1.15.0,將爲讀者簡要介紹其HTTP處理流程。

  通常nginx配置文件如下所示:

worker_processes  1;

events {
    worker_connections  1024;
}

http{
    access_log  logs/access.log  main;
    
    server {
        listen       80;
        server_name  example.com;
        
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
        }
    }
}
  • Nginx採用master-worker編模式,master初始化配置,創建Socket並監聽端口,啓動並管理worker進程,worker進程負責接收客戶端請求並提供服務;其中程worker_processes配置的就是worker進程的數目;
  • events指令塊用於配置事件處理相關,比如worker_connections用於配置每個worker進程最大維護的socket鏈接數目;
  • http指令塊用於配置http請求處理相關,比如access_log用於配置access日誌文件路徑;
  • server指令塊用於配置virtual server,通常會在一臺機器配置多個virtual server,監聽不同端口號,映射到不同文件目錄;比如listen可配置監聽端口號;
  • location指令塊配置不同路徑請求處理方式,比如proxy_pass可配置將請求按照http協議格式轉發給上游,fastcgi_pass可配置將請求按照fastcgi協議轉發給fpm處理。

  Nginx高度模塊化,每個模塊實現某一具體功能,比如ngx_http_limit_req_module模塊實現按請求速率限流功能,ngx_http_fastcgi_module模塊實現fastcgi協議通信功能。每個模塊都需要解析配置文件中相關配置,每個模塊需要解析的所有配置都定義爲ngx_command_t數組。

  例如ngx_http_module模塊,其ngx_command_t數定義如下:

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ...
};
 
static ngx_command_t  ngx_http_commands[] = {
 
    { ngx_string("http"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_http_block,
      ...
     },
};
  • name指令名稱,解析配置文件時按照名稱能匹配查找;
  • type指令類型,NGX_CONF_NOARGS標識該配置無參數,NGX_CONF_BLOCK該配置是一個配置塊,NGX_MAIN_CONF表示配置可以出現在哪些位置(NGX_MAIN_CONF、NGX_HTTP_SRV_CONF、NGX_HTTP_LOC_CONF);
  • set指令處理函數;

初始化服務器

  http指令塊用於配置http請求處理相關,解析http指令的處理函數爲ngx_http_block,實現如下:

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //解析main配置
    //解析server配置
    //解析location配置
 
    //初始化HTTP處理流程所需的handler
 
    //初始化listening
    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}

  函數ngx_http_block主要解析http塊內部的main配置、server配置與location配置;同時會初始化HTTP處理流程所需的handler;以及初始化所有監聽端口。

  函數ngx_http_optimize_servers將所有配置的IP端口進一步解析,並存儲在conf->cycle->listening字段,這是一個數組,後續操作會遍歷此數組,創建socket並監聽。

  conf->cycle->listening數組元素類型爲ngx_listening_t,創建該ngx_listening_t對象時,同時會設置其處理handler爲函數ngx_http_init_connection,當接受到客戶端鏈接請求時,會調用此handler。

  那麼什麼時候啓動監聽呢?全局搜索關鍵字cycle->listening可以找到。main方法會調用ngx_init_cycle,其完成了服務器初始化的大部分工作,其中就包括啓動監聽(ngx_open_listening_sockets):

ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
    for (i = 0; i < cycle->listening.nelts; i++) {
        
        s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
        
        bind(s, ls[i].sockaddr, ls[i].socklen);
        
        listen(s, ls[i].backlog);
    }
}

  假設nginx使用epoll處理所有socket事件,那麼什麼時候將監聽事件添加到epoll呢?同樣全局搜索關鍵字cycle->listening可以找到。ngx_event_core_module模塊是事件處理核心模塊,初始化此模塊時會執行ngx_event_process_init函數,從而將監聽事件添加到epoll:

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {
        //設置讀事件處理handler
        rev->handler = ngx_event_accept;
         
        ngx_add_event(rev, NGX_READ_EVENT, 0);
    }
}

  注意到向epoll添加讀事件時,設置該讀事件處理函數爲ngx_event_accept,即接收到客戶端socket連接請求事件時會調用該處理函數。

HTTP請求解析

基礎結構體

  結構體ngx_connection_t存儲socket連接相關信息;nginx預先創建若干個ngx_connection_t對象,存儲在全局變量ngx_cycle->free_connections,稱之爲連接池;當新生成socket時,會嘗試從連接池中獲取空閒connection連接,如果獲取失敗,則會直接關閉此socket。

  指令worker_connections用於配置連接池最大連接數目,配置在events指令塊中,由ngx_event_core_module解析。

events {
   use epoll;
   worker_connections  60000;
}

  當nginx作爲HTTP服務器時(從用戶的角度,http 1.1協議下,瀏覽器默認使用兩個併發連接),最大客戶端數目maxClient=worker_processes X worker_connections/2;當nginx作爲反向代理服務器時,最大客戶端數目maxClient=worker_processes X worker_connections/4。其worker_processes爲用戶配置的worker進程數目。

  結構體ngx_connection_t定義如下:

struct ngx_connection_s {
    //空閒連接池中,data指向下一個連接,形成鏈表;取出來使用時,data指向請求結構體ngx_http_request_s
    void               *data;
    //讀寫事件結構體
    ngx_event_t        *read;
    ngx_event_t        *write;
 
    ngx_socket_t        fd;   //socket fd
 
    ngx_recv_pt         recv; //socket接收數據函數指針
    ngx_send_pt         send; //socket發送數據函數指針
 
    ngx_buf_t          *buffer; //輸入緩衝區
 
    struct sockaddr    *sockaddr; //客戶端地址
    socklen_t           socklen;
 
    ngx_listening_t    *listening; //監聽的ngx_listening_t對象
 
    struct sockaddr    *local_sockaddr; //本地地址
    socklen_t           local_socklen;
 
    …………
}

  這裏需要重點關注幾個字段:

  • data:void類型爲指針;當該鏈接空閒時,data指向下一個空閒鏈接,以此形成鏈表;當該鏈接被分配之後,data指向對應的請求結構體ngx_http_request_s;
  • read和write:讀寫事件結構體,類型爲ngx_event_t;事件結構體中需要重點關注handler字段,標識爲事件處理函數;
  • recv和send指向socket接收/發送數據函數;

  結構體ngx_http_request_t存儲整個HTTP請求處理流程所需的所有信息,字段非常多,這裏只進行簡要說明:

struct ngx_http_request_s {
    //鏈接
    ngx_connection_t                 *connection;
 
    //讀寫事件處理handler
    ngx_http_event_handler_pt         read_event_handler;
    ngx_http_event_handler_pt         write_event_handler;
 
    //請求頭緩衝區
    ngx_buf_t                        *header_in;
 
    //解析後的請求頭
    ngx_http_headers_in_t             headers_in;
     
    //請求體結構體
    ngx_http_request_body_t          *request_body;
 
    //請求行
    ngx_str_t                         request_line;
    //解析後的若干請求行
    ngx_uint_t                        method;
    ngx_uint_t                        http_version;
    ngx_str_t                         uri;
    ngx_str_t                         args;
 
    …………
}
  • connection指向底層對應的ngx_connection_t鏈接對象;
  • read_event_handler和write_event_handler指向HTTP請求讀寫事件處理函數;
  • headers_in存儲解析後的請求頭;
  • request_body請求體結構體;
  • request_line接受到的請求行;
  • method和http_version等爲解析後的如干請求行;

  請求行與請求體解析相對比較簡單,這裏重點講述請求頭的解析,解析後的請求頭信息都存儲在ngx_http_headers_in_t結構體中。

  ngx_http_request.c文件中定義了所有的HTTP頭部,存儲在ngx_http_headers_in數組,數組的每個元素是一個ngx_http_header_t結構體,主要包含三個字段,頭部名稱、頭部解析後字段存儲在ngx_http_headers_in_t的偏移量,解析頭部的處理函數。

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
                 ngx_http_process_host },
 
    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
                 ngx_http_process_connection },
    …………
}
 
typedef struct {
    ngx_str_t                         name;
    ngx_uint_t                        offset;
    ngx_http_header_handler_pt        handler;
} ngx_http_header_t;

  解析請求頭時,只需從ngx_http_headers_in數組中查找請求頭ngx_http_header_t對象,調用處理函數handler,存儲到r->headers_in對應字段即可。以解析Connection頭部爲例,ngx_http_process_connection實現如下:

static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset)
{
    if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
 
    } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
    }
 
    return NGX_OK;
}

  輸入參數offset在此處並沒有什麼作用。注意到第二個輸入參數類型爲ngx_table_elt_t,存儲了當前請求頭的鍵值對信息:

typedef struct {
    ngx_uint_t        hash;  //請求頭key的hash值
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;  //請求頭key轉爲小寫字符串(可以看到HTTP請求頭解析時key不區分大小寫)
} ngx_table_elt_t;

  再思考一個問題,從ngx_http_headers_in數組中查找請求頭對應ngx_http_header_t對象時,需要遍歷,每個元素都需要進行字符串比較,效率低下。因此nginx將ngx_http_headers_in數組轉換爲哈希表,哈希表的鍵即爲請求頭的key,方法ngx_http_init_headers_in_hash實現了數組到哈希表的轉換,轉換後的哈希表存儲在cmcf->headers_in_hash字段。

  基礎結構體關係示意圖如下所示:

image

解析HTTP請求

  "初始化服務器"小節提到,在創建socket啓動監聽時,會添加可讀事件到epoll,事件處理函數爲ngx_event_accept,用於接收socket連接,分配connection連接,並調用ngx_listening_t對象的處理函數(ngx_http_init_connection)。

void ngx_event_accept(ngx_event_t *ev)
{
    s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, SOCK_NONBLOCK);
 
    ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
 
    c = ngx_get_connection(s, ev->log);
 
    ls->handler(c);
}
  • 客戶端socket連接成功時,都需要分配connection連接,如果分配失敗則會直接關閉此socket;
  • 而每個worker進程連接池的最大連接數目是固定的,當不存在空閒連接時,此worker進程accept的所有socket都會被拒絕;
  • 多個worker進程通過搶鎖競爭是否註冊監聽端口的事件;而當ngx_accept_disabled大於0時,會直接放棄此次競爭,同時ngx_accept_disabled減1。
  • 通過ngx_accept_disabled計算方式可以看到,當worker進程的空閒連接過少時,可以減少其搶鎖成功的次數;

  socket連接成功後,nginx會等待客戶端發送HTTP請求,默認會有60秒的超時時間,即60秒內沒有接收到客戶端請求時,斷開此連接,打印錯誤日誌。函數ngx_http_init_connection用於設置讀事件處理函數,以及超時定時器。

void ngx_http_init_connection(ngx_connection_t *c)
{
    c->read = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
 
    ngx_add_timer(rev, c->listening->post_accept_timeout);
}

  全局搜索post_accept_timeout字段,可以查找到,該字段值可通過配置文件中的client_header_timeout修改(可在http配置塊或者server配置塊中設置)。

  函數ngx_http_wait_request_handler爲解析HTTP請求的入口函數,實現如下:

static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
    //讀事件已經超時
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }
 
    n = c->recv(c, b->last, size);
 
    //創建請求對象ngx_http_request_t,HTTP請求整個處理過程都有用;
    c->data = ngx_http_create_request(c);
 
    //設置讀事件處理函數(此次請求行可能沒有讀取完)
    rev->handler = ngx_http_process_request_line; 
    
    ngx_http_process_request_line(rev);
}

  注意到當讀事件超時時,nginx會直接關閉該鏈接;函數ngx_http_create_request創建並初始化ngx_http_request_t對象;解析請求行處理函數爲ngx_http_process_request_line。

  解析請求行與請求頭的代碼較爲繁瑣,重點在於讀取socket數據,解析字符串,這裏不做詳述。HTTP請求解析過程主要函數調用如下圖所示:

image

  注意,解析完成請求行與請求頭,nginx就開始處理HTTP請求,並沒有等到解析完請求體再處理。處理請求入口爲ngx_http_process_request。

HTTP請求處理階段

HTTP請求處理的11個階段

  nginx將HTTP請求處理流程分爲11個階段,絕大多數HTTP模塊都會將自己的handler添加到某個階段(將handler添加到全局唯一的數組phases中),nginx處理HTTP請求時會挨個調用每個階段的handler。需要注意的是其中有4個階段不能添加自定義handler。11個階段定義如下:

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0, 
  
    NGX_HTTP_SERVER_REWRITE_PHASE,  //server塊中配置了rewrite指令,重寫url
  
    NGX_HTTP_FIND_CONFIG_PHASE,   //查找匹配的location配置;不能自定義handler;
    NGX_HTTP_REWRITE_PHASE,       //location塊中配置了rewrite指令,重寫url
    NGX_HTTP_POST_REWRITE_PHASE,  //檢查是否發生了url重寫,如果有,重新回到FIND_CONFIG階段;不能自定義handler;
  
    NGX_HTTP_PREACCESS_PHASE,     //訪問控制,比如限流模塊會註冊handler到此階段
  
    NGX_HTTP_ACCESS_PHASE,        //訪問權限控制,比如基於ip黑白名單的權限控制,基於用戶名密碼的權限控制等
    NGX_HTTP_POST_ACCESS_PHASE,   //根據訪問權限控制階段做相應處理;不能自定義handler;
  
    NGX_HTTP_TRY_FILES_PHASE,     //只有配置了try_files指令,纔會有此階段;不能自定義handler;
    NGX_HTTP_CONTENT_PHASE,       //內容產生階段,返回響應給客戶端
  
    NGX_HTTP_LOG_PHASE            //日誌記錄
} ngx_http_phases;
  • NGX_HTTP_POST_READ_PHASE:第一個階段,ngx_http_realip_module模塊會註冊handler到該階段(nginx作爲代理服務器時有用,後端以此獲取客戶端原始IP),而該模塊默認不會開啓,需要通過--with-http_realip_module啓動;
  • NGX_HTTP_SERVER_REWRITE_PHASE:server塊中配置了rewrite指令時,該階段會重寫url;
  • NGX_HTTP_FIND_CONFIG_PHASE:查找匹配的location配置;該階段不能自定義handler;
  • NGX_HTTP_REWRITE_PHASE:location塊中配置了rewrite指令時,該階段會重寫url;
  • NGX_HTTP_POST_REWRITE_PHASE:該階段會檢查是否發生了url重寫,如果有,重新回到FIND_CONFIG階段,否則直接進入下一個階段;該階段不能自定義handler;
  • NGX_HTTP_PREACCESS_PHASE:訪問控制,比如限流模塊ngx_http_limit_req_module會註冊handler到該階段;
  • NGX_HTTP_ACCESS_PHASE:訪問權限控制,比如基於ip黑白名單的權限控制,基於用戶名密碼的權限控制等;
  • NGX_HTTP_POST_ACCESS_PHASE:該階段會根據訪問權限控制階段做相應處理,不能自定義handler;
  • NGX_HTTP_TRY_FILES_PHASE:只有配置了try_files指令,纔會有此階段,不能自定義handler;
  • NGX_HTTP_CONTENT_PHASE:內容產生階段,返回響應給客戶端;ngx_http_fastcgi_module模塊就處於該階段;
  • NGX_HTTP_LOG_PHASE:該階段會記錄日誌;

  nginx使用結構體ngx_module_s表示一個模塊,其中字段ctx,是一個指向模塊上下文結構體的指針(上下文結構體的字段都是一些函數指針);nginx的HTTP模塊上下文結構體大多都有字段postconfiguration,負責註冊本模塊的handler到某個處理階段。11個階段在解析完成http配置塊指令後初始化。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //解析http配置塊
 
    //初始化11個階段的phases數組,注意多個模塊可能註冊到同一個階段,因此phases是一個二維數組
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    //遍歷所有HTTP模塊,註冊handler
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[m]->ctx;
 
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
 
    //將二維數組轉換爲一維數組,從而遍歷執行數組所有handler
    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}
  • 多個模塊可能註冊handler到同一個階段,因此phases是一個二維數組;
  • for循環遍歷所有HTTP類型的模塊,調用其postconfiguration函數,註冊handler到相應階段;
  • ngx_http_init_phase_handlers函數會將二維數組phase轉換爲一維數組,後續遍歷執行該數組所有handler;
  • 以限流模塊ngx_http_limit_req_module模塊爲例,postconfiguration方法簡單實現如下:
static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf)
{
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
     
    *h = ngx_http_limit_req_handler;
    //ngx_http_limit_req_module模塊的限流方法;nginx處理HTTP請求時,都會調用此方法判斷應該繼續執行還是拒絕請求
  
    return NGX_OK;
}

  GDB調試,斷點到ngx_http_block方法執行所有HTTP模塊註冊handler之後,打印phases數組

p cmcf->phases[*].handlers
p *(ngx_http_handler_pt*)cmcf->phases[*].handlers.elts

11個階段註冊的handler如下圖所示:

image

11個階段初始化

  上面提到HTTP的11個處理階段handler存儲在phases數組,但由於多個模塊可能註冊handler到同一個階段,使得phases是一個二維數組,因此需要轉換爲一維數組,轉換後存儲在cmcf->phase_engine字段,phase_engine的類型爲ngx_http_phase_engine_t,定義如下:

typedef struct {
    ngx_http_phase_handler_t  *handlers;   //一維數組,存儲所有handler
    ngx_uint_t                 server_rewrite_index;  //記錄NGX_HTTP_SERVER_REWRITE_PHASE階段handler的索引值
    ngx_uint_t                 location_rewrite_index; //記錄NGX_HTTP_REWRITE_PHASE階段handler的索引值
} ngx_http_phase_engine_t;
 
struct ngx_http_phase_handler_t {
    ngx_http_phase_handler_pt  checker;  //執行handler之前的校驗函數
    ngx_http_handler_pt        handler;
    ngx_uint_t                 next;   //下一個待執行handler的索引(通過next實現handler跳轉執行)
};
 
//cheker函數指針類型定義
typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r, ngx_http_phase_handler_t *ph);
//handler函數指針類型定義
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
  • handlers字段爲存儲所有handler的一維數組;
  • server_rewrite_index字段記錄NGX_HTTP_SERVER_REWRITE_PHASE階段handler的索引值;
  • location_rewrite_index字段記錄NGX_HTTP_REWRITE_PHASE階段handler的索引值;
  • ngx_http_phase_handler_t結構體中的checker字段爲執行handler之前的校驗函數;next字段爲下一個待執行handler的索引(通過next實現handler跳轉執行);
  • 數組轉換功能由函數ngx_http_init_phase_handlers實現,代碼邏輯比較長但是相對簡單,這裏不做過多詳述;

  GDB打印出轉換後的數組如下圖所示,第一列是cheker字段,第二列是handler字段,箭頭表示next跳轉;圖中有個返回的箭頭,即NGX_HTTP_POST_REWRITE_PHASE階段可能返回到NGX_HTTP_FIND_CONFIG_PHASE;原因在於只要NGX_HTTP_REWRITE_PHASE階段產生了url重寫,就需要重新查找匹配location。

image

處理HTTP請求

  上面提到HTTP請求的處理入口函數是ngx_http_process_request,其主要調用ngx_http_core_run_phases實現11個階段的執行流程;

  ngx_http_core_run_phases遍歷預先設置好的cmcf->phase_engine.handlers數組,調用其checker函數,邏輯如下:

void ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ph = cmcf->phase_engine.handlers;
 
    //phase_handler初始爲0,表示待處理handler的索引;cheker內部會根據ph->next字段修改phase_handler
    while (ph[r->phase_handler].checker) {
 
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
 
        if (rc == NGX_OK) {
            return;
        }
    }
}

  checker內部就是調用handler,並設置下一步要執行handler的索引;比如說ngx_http_core_generic_phase實現如下:

ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewrite phase: %ui", r->phase_handler);
    rc = ph->handler(r);
    if (rc == NGX_OK) {
        r->phase_handler = ph->next;
        return NGX_AGAIN;
    }
}

內容產生階段

  內容產生階段NGX_HTTP_CONTENT_PHASE是HTTP請求處理的第10個階段,一般情況有3個模塊註冊handler到此階段:ngx_http_static_module、ngx_http_autoindex_module和ngx_http_index_module。

  但是當我們配置了proxy_pass和fastcgi_pass時,情況會有所不同。

  使用proxy_pass配置上游時,ngx_http_proxy_module模塊會設置其處理函數到配置類conf;使用fastcgi_pass配置時,ngx_http_fastcgi_module會設置其處理函數到配置類conf。例如:

static char * ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t   *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
 
    clcf->handler = ngx_http_fastcgi_handler;
}

  階段NGX_HTTP_FIND_CONFIG_PHASE查找匹配的location,並獲取此ngx_http_core_loc_conf_t對象,將其handler賦值給ngx_http_request_t對象的content_handler字段(內容產生階段處理函數)。

  而在執行內容產生階段的checker函數時,會檢測執行content_handler指向的函數;查看ngx_http_core_content_phase函數實現(內容產生階段的checker函數):

ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    if (r->content_handler) {  //如果請求對象的content_handler字段不爲空,則調用
        r->write_event_handler = ngx_http_request_empty_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);  //否則執行內容產生階段handler
}

總結

  nginx處理HTTP請求的流程較爲複雜,因此本文只是簡單提供了一條線索:分析了nginx服務器啓動監聽的過程,HTTP請求的解析過程,11個階段的初始化與調用過程。至於HTTP解析處理的詳細流程,還需要讀者去探索。

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