成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

初探nginx HTTP處理流程

MasonEast / 1730人閱讀

摘要:作者景羅基本概念作為一款開源的高性能的服務(wù)器和反向代理服務(wù)器而聞名,本文基于,將為讀者簡要介紹其處理流程。其為用戶配置的進(jìn)程數(shù)目。以解析頭部為例,實(shí)現(xiàn)如下輸入?yún)?shù)在此處并沒有什么作用。處理請(qǐng)求入口為。

作者:景羅

基本概念:

??Nginx作為一款開源的、高性能的HTTP服務(wù)器和反向代理服務(wù)器而聞名,本文基于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初始化配置,創(chuàng)建Socket并監(jiān)聽端口,啟動(dòng)并管理worker進(jìn)程,worker進(jìn)程負(fù)責(zé)接收客戶端請(qǐng)求并提供服務(wù);其中worker_processes配置的就是worker進(jìn)程的數(shù)目;

events指令塊用于配置事件處理相關(guān),比如worker_connections用于配置每個(gè)worker進(jìn)程最大維護(hù)的socket鏈接數(shù)目;

http指令塊用于配置http請(qǐng)求處理相關(guān),比如access_log用于配置access日志文件路徑;

server指令塊用于配置virtual server,通常會(huì)在一臺(tái)機(jī)器配置多個(gè)virtual server,監(jiān)聽不同端口號(hào),映射到不同文件目錄;比如listen可配置監(jiān)聽端口號(hào);

location指令塊配置不同路徑請(qǐng)求處理方式,比如proxy_pass可配置將請(qǐng)求按照http協(xié)議格式轉(zhuǎn)發(fā)給上游,fastcgi_pass可配置將請(qǐng)求按照fastcgi協(xié)議轉(zhuǎn)發(fā)給fpm處理。

??Nginx高度模塊化,每個(gè)模塊實(shí)現(xiàn)某一具體功能,比如ngx_http_limit_req_module模塊實(shí)現(xiàn)按請(qǐng)求速率限流功能,ngx_http_fastcgi_module模塊實(shí)現(xiàn)fastcgi協(xié)議通信功能。每個(gè)模塊都需要解析配置文件中相關(guān)配置,每個(gè)模塊需要解析的所有配置都定義為ngx_command_t數(shù)組。

??例如ngx_http_module模塊,其ngx_command_t數(shù)定義如下:

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指令名稱,解析配置文件時(shí)按照名稱能匹配查找;

type指令類型,NGX_CONF_NOARGS標(biāo)識(shí)該配置無參數(shù),NGX_CONF_BLOCK該配置是一個(gè)配置塊,NGX_MAIN_CONF表示配置可以出現(xiàn)在哪些位置(NGX_MAIN_CONF、NGX_HTTP_SRV_CONF、NGX_HTTP_LOC_CONF);

set指令處理函數(shù);

初始化服務(wù)器

??http指令塊用于配置http請(qǐng)求處理相關(guān),解析http指令的處理函數(shù)為ngx_http_block,實(shí)現(xiàn)如下:

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;
    }
}

??函數(shù)ngx_http_block主要解析http塊內(nèi)部的main配置、server配置與location配置;同時(shí)會(huì)初始化HTTP處理流程所需的handler;以及初始化所有監(jiān)聽端口。

??函數(shù)ngx_http_optimize_servers將所有配置的IP端口進(jìn)一步解析,并存儲(chǔ)在conf->cycle->listening字段,這是一個(gè)數(shù)組,后續(xù)操作會(huì)遍歷此數(shù)組,創(chuàng)建socket并監(jiān)聽。

??conf->cycle->listening數(shù)組元素類型為ngx_listening_t,創(chuàng)建該ngx_listening_t對(duì)象時(shí),同時(shí)會(huì)設(shè)置其處理handler為函數(shù)ngx_http_init_connection,當(dāng)接受到客戶端鏈接請(qǐng)求時(shí),會(huì)調(diào)用此handler。

??那么什么時(shí)候啟動(dòng)監(jiān)聽呢?全局搜索關(guān)鍵字cycle->listening可以找到。main方法會(huì)調(diào)用ngx_init_cycle,其完成了服務(wù)器初始化的大部分工作,其中就包括啟動(dòng)監(jiān)聽(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);
    }
}

??假設(shè)nginx使用epoll處理所有socket事件,那么什么時(shí)候?qū)⒈O(jiān)聽事件添加到epoll呢?同樣全局搜索關(guān)鍵字cycle->listening可以找到。ngx_event_core_module模塊是事件處理核心模塊,初始化此模塊時(shí)會(huì)執(zhí)行ngx_event_process_init函數(shù),從而將監(jiān)聽事件添加到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++) {
        //設(shè)置讀事件處理handler
        rev->handler = ngx_event_accept;
         
        ngx_add_event(rev, NGX_READ_EVENT, 0);
    }
}

??注意到向epoll添加讀事件時(shí),設(shè)置該讀事件處理函數(shù)為ngx_event_accept,即接收到客戶端socket連接請(qǐng)求事件時(shí)會(huì)調(diào)用該處理函數(shù)。

HTTP請(qǐng)求解析 基礎(chǔ)結(jié)構(gòu)體

??結(jié)構(gòu)體ngx_connection_t存儲(chǔ)socket連接相關(guān)信息;nginx預(yù)先創(chuàng)建若干個(gè)ngx_connection_t對(duì)象,存儲(chǔ)在全局變量ngx_cycle->free_connections,稱之為連接池;當(dāng)新生成socket時(shí),會(huì)嘗試從連接池中獲取空閑connection連接,如果獲取失敗,則會(huì)直接關(guān)閉此socket。

??指令worker_connections用于配置連接池最大連接數(shù)目,配置在events指令塊中,由ngx_event_core_module解析。

events {
   use epoll;
   worker_connections  60000;
}

??當(dāng)nginx作為HTTP服務(wù)器時(shí)(從用戶的角度,http 1.1協(xié)議下,瀏覽器默認(rèn)使用兩個(gè)并發(fā)連接),最大客戶端數(shù)目maxClient=worker_processes X worker_connections/2;當(dāng)nginx作為反向代理服務(wù)器時(shí),最大客戶端數(shù)目maxClient=worker_processes X worker_connections/4。其worker_processes為用戶配置的worker進(jìn)程數(shù)目。

??結(jié)構(gòu)體ngx_connection_t定義如下:

struct ngx_connection_s {
    //空閑連接池中,data指向下一個(gè)連接,形成鏈表;取出來使用時(shí),data指向請(qǐng)求結(jié)構(gòu)體ngx_http_request_s
    void               *data;
    //讀寫事件結(jié)構(gòu)體
    ngx_event_t        *read;
    ngx_event_t        *write;
 
    ngx_socket_t        fd;   //socket fd
 
    ngx_recv_pt         recv; //socket接收數(shù)據(jù)函數(shù)指針
    ngx_send_pt         send; //socket發(fā)送數(shù)據(jù)函數(shù)指針
 
    ngx_buf_t          *buffer; //輸入緩沖區(qū)
 
    struct sockaddr    *sockaddr; //客戶端地址
    socklen_t           socklen;
 
    ngx_listening_t    *listening; //監(jiān)聽的ngx_listening_t對(duì)象
 
    struct sockaddr    *local_sockaddr; //本地地址
    socklen_t           local_socklen;
 
    …………
}

??這里需要重點(diǎn)關(guān)注幾個(gè)字段:

data:void類型為指針;當(dāng)該鏈接空閑時(shí),data指向下一個(gè)空閑鏈接,以此形成鏈表;當(dāng)該鏈接被分配之后,data指向?qū)?yīng)的請(qǐng)求結(jié)構(gòu)體ngx_http_request_s;

read和write:讀寫事件結(jié)構(gòu)體,類型為ngx_event_t;事件結(jié)構(gòu)體中需要重點(diǎn)關(guān)注handler字段,標(biāo)識(shí)為事件處理函數(shù);

recv和send指向socket接收/發(fā)送數(shù)據(jù)函數(shù);

??結(jié)構(gòu)體ngx_http_request_t存儲(chǔ)整個(gè)HTTP請(qǐng)求處理流程所需的所有信息,字段非常多,這里只進(jìn)行簡要說明:

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;
 
    //請(qǐng)求頭緩沖區(qū)
    ngx_buf_t                        *header_in;
 
    //解析后的請(qǐng)求頭
    ngx_http_headers_in_t             headers_in;
     
    //請(qǐng)求體結(jié)構(gòu)體
    ngx_http_request_body_t          *request_body;
 
    //請(qǐng)求行
    ngx_str_t                         request_line;
    //解析后的若干請(qǐng)求行
    ngx_uint_t                        method;
    ngx_uint_t                        http_version;
    ngx_str_t                         uri;
    ngx_str_t                         args;
 
    …………
}

connection指向底層對(duì)應(yīng)的ngx_connection_t鏈接對(duì)象;

read_event_handler和write_event_handler指向HTTP請(qǐng)求讀寫事件處理函數(shù);

headers_in存儲(chǔ)解析后的請(qǐng)求頭;

request_body請(qǐng)求體結(jié)構(gòu)體;

request_line接受到的請(qǐng)求行;

method和http_version等為解析后的如干請(qǐng)求行;

??請(qǐng)求行與請(qǐng)求體解析相對(duì)比較簡單,這里重點(diǎn)講述請(qǐng)求頭的解析,解析后的請(qǐng)求頭信息都存儲(chǔ)在ngx_http_headers_in_t結(jié)構(gòu)體中。

??ngx_http_request.c文件中定義了所有的HTTP頭部,存儲(chǔ)在ngx_http_headers_in數(shù)組,數(shù)組的每個(gè)元素是一個(gè)ngx_http_header_t結(jié)構(gòu)體,主要包含三個(gè)字段,頭部名稱、頭部解析后字段存儲(chǔ)在ngx_http_headers_in_t的偏移量,解析頭部的處理函數(shù)。

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;

??解析請(qǐng)求頭時(shí),只需從ngx_http_headers_in數(shù)組中查找請(qǐng)求頭ngx_http_header_t對(duì)象,調(diào)用處理函數(shù)handler,存儲(chǔ)到r->headers_in對(duì)應(yīng)字段即可。以解析Connection頭部為例,ngx_http_process_connection實(shí)現(xiàn)如下:

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;
}

??輸入?yún)?shù)offset在此處并沒有什么作用。注意到第二個(gè)輸入?yún)?shù)類型為ngx_table_elt_t,存儲(chǔ)了當(dāng)前請(qǐng)求頭的鍵值對(duì)信息:

typedef struct {
    ngx_uint_t        hash;  //請(qǐng)求頭key的hash值
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;  //請(qǐng)求頭key轉(zhuǎn)為小寫字符串(可以看到HTTP請(qǐng)求頭解析時(shí)key不區(qū)分大小寫)
} ngx_table_elt_t;

??再思考一個(gè)問題,從ngx_http_headers_in數(shù)組中查找請(qǐng)求頭對(duì)應(yīng)ngx_http_header_t對(duì)象時(shí),需要遍歷,每個(gè)元素都需要進(jìn)行字符串比較,效率低下。因此nginx將ngx_http_headers_in數(shù)組轉(zhuǎn)換為哈希表,哈希表的鍵即為請(qǐng)求頭的key,方法ngx_http_init_headers_in_hash實(shí)現(xiàn)了數(shù)組到哈希表的轉(zhuǎn)換,轉(zhuǎn)換后的哈希表存儲(chǔ)在cmcf->headers_in_hash字段。

??基礎(chǔ)結(jié)構(gòu)體關(guān)系示意圖如下所示:

解析HTTP請(qǐng)求

??"初始化服務(wù)器"小節(jié)提到,在創(chuàng)建socket啟動(dòng)監(jiān)聽時(shí),會(huì)添加可讀事件到epoll,事件處理函數(shù)為ngx_event_accept,用于接收socket連接,分配connection連接,并調(diào)用ngx_listening_t對(duì)象的處理函數(shù)(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連接成功時(shí),都需要分配connection連接,如果分配失敗則會(huì)直接關(guān)閉此socket;

而每個(gè)worker進(jìn)程連接池的最大連接數(shù)目是固定的,當(dāng)不存在空閑連接時(shí),此worker進(jìn)程accept的所有socket都會(huì)被拒絕;

多個(gè)worker進(jìn)程通過搶鎖競(jìng)爭(zhēng)是否注冊(cè)監(jiān)聽端口的事件;而當(dāng)ngx_accept_disabled大于0時(shí),會(huì)直接放棄此次競(jìng)爭(zhēng),同時(shí)ngx_accept_disabled減1。

通過ngx_accept_disabled計(jì)算方式可以看到,當(dāng)worker進(jìn)程的空閑連接過少時(shí),可以減少其搶鎖成功的次數(shù);

??socket連接成功后,nginx會(huì)等待客戶端發(fā)送HTTP請(qǐng)求,默認(rèn)會(huì)有60秒的超時(shí)時(shí)間,即60秒內(nèi)沒有接收到客戶端請(qǐng)求時(shí),斷開此連接,打印錯(cuò)誤日志。函數(shù)ngx_http_init_connection用于設(shè)置讀事件處理函數(shù),以及超時(shí)定時(shí)器。

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配置塊中設(shè)置)。

??函數(shù)ngx_http_wait_request_handler為解析HTTP請(qǐng)求的入口函數(shù),實(shí)現(xiàn)如下:

static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
    //讀事件已經(jīng)超時(shí)
    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);
 
    //創(chuàng)建請(qǐng)求對(duì)象ngx_http_request_t,HTTP請(qǐng)求整個(gè)處理過程都有用;
    c->data = ngx_http_create_request(c);
 
    //設(shè)置讀事件處理函數(shù)(此次請(qǐng)求行可能沒有讀取完)
    rev->handler = ngx_http_process_request_line; 
    
    ngx_http_process_request_line(rev);
}

??注意到當(dāng)讀事件超時(shí)時(shí),nginx會(huì)直接關(guān)閉該鏈接;函數(shù)ngx_http_create_request創(chuàng)建并初始化ngx_http_request_t對(duì)象;解析請(qǐng)求行處理函數(shù)為ngx_http_process_request_line。

??解析請(qǐng)求行與請(qǐng)求頭的代碼較為繁瑣,重點(diǎn)在于讀取socket數(shù)據(jù),解析字符串,這里不做詳述。HTTP請(qǐng)求解析過程主要函數(shù)調(diào)用如下圖所示:

??注意,解析完成請(qǐng)求行與請(qǐng)求頭,nginx就開始處理HTTP請(qǐng)求,并沒有等到解析完請(qǐng)求體再處理。處理請(qǐng)求入口為ngx_http_process_request。

HTTP請(qǐng)求處理階段 HTTP請(qǐng)求處理的11個(gè)階段

??nginx將HTTP請(qǐng)求處理流程分為11個(gè)階段,絕大多數(shù)HTTP模塊都會(huì)將自己的handler添加到某個(gè)階段(將handler添加到全局唯一的數(shù)組phases中),nginx處理HTTP請(qǐng)求時(shí)會(huì)挨個(gè)調(diào)用每個(gè)階段的handler。需要注意的是其中有4個(gè)階段不能添加自定義handler。11個(gè)階段定義如下:

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,  //檢查是否發(fā)生了url重寫,如果有,重新回到FIND_CONFIG階段;不能自定義handler;
  
    NGX_HTTP_PREACCESS_PHASE,     //訪問控制,比如限流模塊會(huì)注冊(cè)handler到此階段
  
    NGX_HTTP_ACCESS_PHASE,        //訪問權(quán)限控制,比如基于ip黑白名單的權(quán)限控制,基于用戶名密碼的權(quán)限控制等
    NGX_HTTP_POST_ACCESS_PHASE,   //根據(jù)訪問權(quán)限控制階段做相應(yīng)處理;不能自定義handler;
  
    NGX_HTTP_TRY_FILES_PHASE,     //只有配置了try_files指令,才會(huì)有此階段;不能自定義handler;
    NGX_HTTP_CONTENT_PHASE,       //內(nèi)容產(chǎn)生階段,返回響應(yīng)給客戶端
  
    NGX_HTTP_LOG_PHASE            //日志記錄
} ngx_http_phases;

NGX_HTTP_POST_READ_PHASE:第一個(gè)階段,ngx_http_realip_module模塊會(huì)注冊(cè)handler到該階段(nginx作為代理服務(wù)器時(shí)有用,后端以此獲取客戶端原始IP),而該模塊默認(rèn)不會(huì)開啟,需要通過--with-http_realip_module啟動(dòng);

NGX_HTTP_SERVER_REWRITE_PHASE:server塊中配置了rewrite指令時(shí),該階段會(huì)重寫url;

NGX_HTTP_FIND_CONFIG_PHASE:查找匹配的location配置;該階段不能自定義handler;

NGX_HTTP_REWRITE_PHASE:location塊中配置了rewrite指令時(shí),該階段會(huì)重寫url;

NGX_HTTP_POST_REWRITE_PHASE:該階段會(huì)檢查是否發(fā)生了url重寫,如果有,重新回到FIND_CONFIG階段,否則直接進(jìn)入下一個(gè)階段;該階段不能自定義handler;

NGX_HTTP_PREACCESS_PHASE:訪問控制,比如限流模塊ngx_http_limit_req_module會(huì)注冊(cè)handler到該階段;

NGX_HTTP_ACCESS_PHASE:訪問權(quán)限控制,比如基于ip黑白名單的權(quán)限控制,基于用戶名密碼的權(quán)限控制等;

NGX_HTTP_POST_ACCESS_PHASE:該階段會(huì)根據(jù)訪問權(quán)限控制階段做相應(yīng)處理,不能自定義handler;

NGX_HTTP_TRY_FILES_PHASE:只有配置了try_files指令,才會(huì)有此階段,不能自定義handler;

NGX_HTTP_CONTENT_PHASE:內(nèi)容產(chǎn)生階段,返回響應(yīng)給客戶端;ngx_http_fastcgi_module模塊就處于該階段;

NGX_HTTP_LOG_PHASE:該階段會(huì)記錄日志;

??nginx使用結(jié)構(gòu)體ngx_module_s表示一個(gè)模塊,其中字段ctx,是一個(gè)指向模塊上下文結(jié)構(gòu)體的指針(上下文結(jié)構(gòu)體的字段都是一些函數(shù)指針);nginx的HTTP模塊上下文結(jié)構(gòu)體大多都有字段postconfiguration,負(fù)責(zé)注冊(cè)本模塊的handler到某個(gè)處理階段。11個(gè)階段在解析完成http配置塊指令后初始化。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //解析http配置塊
 
    //初始化11個(gè)階段的phases數(shù)組,注意多個(gè)模塊可能注冊(cè)到同一個(gè)階段,因此phases是一個(gè)二維數(shù)組
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    //遍歷所有HTTP模塊,注冊(cè)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;
            }
        }
    }
 
    //將二維數(shù)組轉(zhuǎn)換為一維數(shù)組,從而遍歷執(zhí)行數(shù)組所有handler
    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}

多個(gè)模塊可能注冊(cè)handler到同一個(gè)階段,因此phases是一個(gè)二維數(shù)組;

for循環(huán)遍歷所有HTTP類型的模塊,調(diào)用其postconfiguration函數(shù),注冊(cè)handler到相應(yīng)階段;

ngx_http_init_phase_handlers函數(shù)會(huì)將二維數(shù)組phase轉(zhuǎn)換為一維數(shù)組,后續(xù)遍歷執(zhí)行該數(shù)組所有handler;

以限流模塊ngx_http_limit_req_module模塊為例,postconfiguration方法簡單實(shí)現(xiàn)如下:

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請(qǐng)求時(shí),都會(huì)調(diào)用此方法判斷應(yīng)該繼續(xù)執(zhí)行還是拒絕請(qǐng)求
  
    return NGX_OK;
}

??GDB調(diào)試,斷點(diǎn)到ngx_http_block方法執(zhí)行所有HTTP模塊注冊(cè)handler之后,打印phases數(shù)組

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

11個(gè)階段注冊(cè)的handler如下圖所示:

11個(gè)階段初始化

??上面提到HTTP的11個(gè)處理階段handler存儲(chǔ)在phases數(shù)組,但由于多個(gè)模塊可能注冊(cè)handler到同一個(gè)階段,使得phases是一個(gè)二維數(shù)組,因此需要轉(zhuǎn)換為一維數(shù)組,轉(zhuǎn)換后存儲(chǔ)在cmcf->phase_engine字段,phase_engine的類型為ngx_http_phase_engine_t,定義如下:

typedef struct {
    ngx_http_phase_handler_t  *handlers;   //一維數(shù)組,存儲(chǔ)所有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;  //執(zhí)行handler之前的校驗(yàn)函數(shù)
    ngx_http_handler_pt        handler;
    ngx_uint_t                 next;   //下一個(gè)待執(zhí)行handler的索引(通過next實(shí)現(xiàn)handler跳轉(zhuǎn)執(zhí)行)
};
 
//cheker函數(shù)指針類型定義
typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r, ngx_http_phase_handler_t *ph);
//handler函數(shù)指針類型定義
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

handlers字段為存儲(chǔ)所有handler的一維數(shù)組;

server_rewrite_index字段記錄NGX_HTTP_SERVER_REWRITE_PHASE階段handler的索引值;

location_rewrite_index字段記錄NGX_HTTP_REWRITE_PHASE階段handler的索引值;

ngx_http_phase_handler_t結(jié)構(gòu)體中的checker字段為執(zhí)行handler之前的校驗(yàn)函數(shù);next字段為下一個(gè)待執(zhí)行handler的索引(通過next實(shí)現(xiàn)handler跳轉(zhuǎn)執(zhí)行);

數(shù)組轉(zhuǎn)換功能由函數(shù)ngx_http_init_phase_handlers實(shí)現(xiàn),代碼邏輯比較長但是相對(duì)簡單,這里不做過多詳述;

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

處理HTTP請(qǐng)求

??上面提到HTTP請(qǐng)求的處理入口函數(shù)是ngx_http_process_request,其主要調(diào)用ngx_http_core_run_phases實(shí)現(xiàn)11個(gè)階段的執(zhí)行流程;

??ngx_http_core_run_phases遍歷預(yù)先設(shè)置好的cmcf->phase_engine.handlers數(shù)組,調(diào)用其checker函數(shù),邏輯如下:

void ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ph = cmcf->phase_engine.handlers;
 
    //phase_handler初始為0,表示待處理handler的索引;cheker內(nèi)部會(huì)根據(jù)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內(nèi)部就是調(diào)用handler,并設(shè)置下一步要執(zhí)行handler的索引;比如說ngx_http_core_generic_phase實(shí)現(xiàn)如下:

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;
    }
}
內(nèi)容產(chǎn)生階段

??內(nèi)容產(chǎn)生階段NGX_HTTP_CONTENT_PHASE是HTTP請(qǐng)求處理的第10個(gè)階段,一般情況有3個(gè)模塊注冊(cè)handler到此階段:ngx_http_static_module、ngx_http_autoindex_module和ngx_http_index_module。

??但是當(dāng)我們配置了proxy_pass和fastcgi_pass時(shí),情況會(huì)有所不同。

??使用proxy_pass配置上游時(shí),ngx_http_proxy_module模塊會(huì)設(shè)置其處理函數(shù)到配置類conf;使用fastcgi_pass配置時(shí),ngx_http_fastcgi_module會(huì)設(shè)置其處理函數(shù)到配置類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對(duì)象,將其handler賦值給ngx_http_request_t對(duì)象的content_handler字段(內(nèi)容產(chǎn)生階段處理函數(shù))。

??而在執(zhí)行內(nèi)容產(chǎn)生階段的checker函數(shù)時(shí),會(huì)檢測(cè)執(zhí)行content_handler指向的函數(shù);查看ngx_http_core_content_phase函數(shù)實(shí)現(xiàn)(內(nèi)容產(chǎn)生階段的checker函數(shù)):

ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    if (r->content_handler) {  //如果請(qǐng)求對(duì)象的content_handler字段不為空,則調(diào)用
        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);  //否則執(zhí)行內(nèi)容產(chǎn)生階段handler
}
總結(jié)

??nginx處理HTTP請(qǐng)求的流程較為復(fù)雜,因此本文只是簡單提供了一條線索:分析了nginx服務(wù)器啟動(dòng)監(jiān)聽的過程,HTTP請(qǐng)求的解析過程,11個(gè)階段的初始化與調(diào)用過程。至于HTTP解析處理的詳細(xì)流程,還需要讀者去探索。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/40437.html

相關(guān)文章

  • nginx學(xué)習(xí)一】基本原理初探

    摘要:關(guān)于過程中如何細(xì)節(jié)控制一致性,穩(wěn)定性,信號(hào)控制,控制等等,敬請(qǐng)期待小拽的進(jìn)一步探索處理流程和模塊啟動(dòng)進(jìn)程后,請(qǐng)求在內(nèi)部是如何流轉(zhuǎn)的,內(nèi)部包括哪些模塊處理過程請(qǐng)求到達(dá)后首先讀取,中初始時(shí)間便從此開始。 由于性能問題,需要將 apache + php5.2 升級(jí)到 nginx + php7,對(duì)于nginx的性能和熱加載早有耳聞,why nginx so diao。小拽進(jìn)行了初探,有任何疑問...

    Simon 評(píng)論0 收藏0
  • nginx、swoole高并發(fā)原理初探

    摘要:一閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。等奶茶做好了,店員喊一聲小明,奶茶好了,然后小明去取奶茶。將響應(yīng)結(jié)果發(fā)給相應(yīng)的連接請(qǐng)求處理完成因?yàn)榛?,所以每個(gè)可以處理無數(shù)個(gè)連接請(qǐng)求。如此,就輕松的處理了高并發(fā)。 一、閱前熱身 為了更加形象的說明同步異步、阻塞非阻塞,我們以小明去買奶茶為例。 1、同步與異步 ①同步與異步的理解 同步與異步的重點(diǎn)在消息通知的方式上...

    denson 評(píng)論0 收藏0
  • nginx、swoole高并發(fā)原理初探

    摘要:一閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。等奶茶做好了,店員喊一聲小明,奶茶好了,然后小明去取奶茶。將響應(yīng)結(jié)果發(fā)給相應(yīng)的連接請(qǐng)求處理完成因?yàn)榛?,所以每個(gè)可以處理無數(shù)個(gè)連接請(qǐng)求。如此,就輕松的處理了高并發(fā)。 一、閱前熱身 為了更加形象的說明同步異步、阻塞非阻塞,我們以小明去買奶茶為例。 1、同步與異步 ①同步與異步的理解 同步與異步的重點(diǎn)在消息通知的方式上...

    617035918 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<