初探nginx-HTTP处理流程

15次阅读

共计 13889 个字符,预计需要花费 35 分钟才能阅读完成。

作者:景罗

基本概念:

  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 字段。

  基础结构体关系示意图如下所示:

解析 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 请求解析过程主要函数调用如下图所示:

  注意,解析完成请求行与请求头,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 如下图所示:

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。

处理 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 解析处理的详细流程,还需要读者去探索。

正文完
 0