nginx http模块开发(6) - http框架简介

2018-07-21 16:59:43

nginx 的 http 框架是由核心模块 ngx_http_module 和 http 模块 ngx_http_core_module 共同定义的。

ngx_http_module 定义了指令 http,保存和管理各个层次里所有 http 模块的配置数据;而 nginx_http_core_module 模块则是 http 模块里面最"核心"的模块,它定义了 listen、server、location等http核心指令。

nginx的处理流程

1.监听端口,设置有新连接到来时的回调函数 ngx_http_init_connection()。

2.接受客户端连接,当有数据可读时,调用 ngx_http_wait_request_handler() 来准备读取请求行和请求头。

3.调用 ngx_http_create_request() 创建 ngx_http_request_t 对象,准备开始真正的处理请求。

4.调用 ngx_http_process_request_line() 来读取解析请求行。

5.调用 ngx_http_process_request_headers() 来读取解析请求头。

6.此时已经读取了完整的 http 请求头,调用 ngx_http_process_request() 开始处理请求。

7.调用 ngx_http_request_handler() 来真正处理请求。 

8.调用 ngx_http_core_run_phases() 按阶段处理请求。这是 http 框架核心的部分,大部分 http 模块都在这里运行,最终产生响应数据。

9.调用 ngx_http_send_header() 发送响应头,调用 ngx_http_top_header_filter() 过滤响应头,最终发送经过处理的响应头。

10.调用 ngx_http_output_filter() 发送响应体,调用 ngx_http_top_body_filter() 过滤响应体,最终发送经过处理的响应体。

11.调用 ngx_http_finalize_request() 准备结束请求,调用 ngx_http_log_request() 记录访问日志。

请求流程大致如下图:

请求结构体

结构体 ngx_http_request_t 表示一个 http 请求,是 nginx 处理请求的核心数据结构,包含处理过程中需要的各种数据和信息,下面是部分成员。

typedef struct ngx_http_request_s     ngx_http_request_t;

struct ngx_http_request_s {
    // 结构体的“签名”,C程序里的常用手段,用特殊字符来标记结构体
    uint32_t                          signature;         /* "HTTP" */

    // 请求对应的连接对象,里面有log用于记录日志
    // 里面还有读写事件read/write
    // 使用它来与客户端通信收发数据
    ngx_connection_t                 *connection;

    // 保存有所有http模块的配置、ctx数据
    // 使用ngx_http_get_module_ctx获取ctx
    // 是一个数组,里面存储的是void*
    void                            **ctx;

    // 使用ngx_http_get_module_main_conf访问
    // 都是一维数组,里面存储的是void*
    void                            **main_conf;
    void                            **srv_conf;
    void                            **loc_conf;

    // 读事件的处理函数
    // 随着处理的阶段不同会变化
    // ngx_http_discarded_request_body_handler:丢弃请求体
    // ngx_http_block_reading:忽略读事件,即不读取数据
    ngx_http_event_handler_pt         read_event_handler;

    // 写事件的处理函数
    // 写最开始是ngx_http_empty_handler
    // 然后是ngx_http_core_run_phases
    // 当进入content阶段调用location handler后变成ngx_http_request_empty_handler
    // 最后是ngx_http_set_write_handler
    ngx_http_event_handler_pt         write_event_handler;


    // 请求的内存池,请求结束时会回收
    ngx_pool_t                       *pool;

    // 缓冲区,用于读取请求头
    // 如果有请求体数据,也会都读到这里
    ngx_buf_t                        *header_in;

    // 请求头结构体
    // 里面用链表存储了所有的头,也可以用指针快速访问常用头
    ngx_http_headers_in_t             headers_in;

    // 响应头结构体
    // 里面有状态码/状态行和响应头链表
    ngx_http_headers_out_t            headers_out;

    // 读取并存储请求体
    // 指针的形式只有在需要的时候才分配内存
    // 相关函数ngx_http_discard_request_body/ngx_http_read_client_request_body
    ngx_http_request_body_t          *request_body;

    // 请求开始的时间,可用于限速
    time_t                            start_sec;
    ngx_msec_t                        start_msec;

    // 从请求头解析出来的方法
    // r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)
    ngx_uint_t                        method;

    // http协议版本号,通常不需要关心
    ngx_uint_t                        http_version;

    // 请求行字符串
    ngx_str_t                         request_line;

    // uri地址,不含参数,即$uri
    ngx_str_t                         uri;

    // uri后的参数,不含问号,即$args
    ngx_str_t                         args;

    // uri里文件的扩展名
    ngx_str_t                         exten;

    // 原始请求uri,未解码,即$request_uri
    ngx_str_t                         unparsed_uri;

    // 请求的方法名字符串,例如GET/POST/DELETE
    // 因为字符串比较慢,所以应该尽量用method来判断方法
    ngx_str_t                         method_name;

    // http协议字符串,通常不需要关注
    ngx_str_t                         http_protocol;

    // 发送的数据链表
    // 所有的header、body数据都会存在这里
    ngx_chain_t                      *out;

    // 指向主请求,即由客户端发起的请求
    // 如果没有子请求,那么r == main
    ngx_http_request_t               *main;

    // 父请求,如果是子请求,那么指向产生它的父请求
    // 如果是主请求,指针是空
    ngx_http_request_t               *parent;

    // 执行ngx_http_core_run_phases时的重要参数,标记在引擎数组里的位置
    // 可以理解为一个执行的“游标”
    ngx_int_t                         phase_handler;

    // 重要!!
    // 本location专门的内容处理函数,产生响应内容
    // 在ngx_http_update_location_config里设置
    ngx_http_handler_pt               content_handler;
    
    ...
}

ngx_http_request_t 贯穿了 nginx 处理 http 请求的整个流程,可以说在 nginx 中处理 http 请求就是在操作 ngx_http_request_t 数据结构。

ngx_http_create_request() 创建了 ngx_http_request_t 对象

ngx_http_request_t *
ngx_http_create_request(ngx_connection_t *c)
{
    r = ngx_pcalloc(pool, sizeof(ngx_http_request_t));
    
    // 为所有http模块分配存储ctx数据的空间,即一个大数组
    r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);

    r->main_conf = hc->conf_ctx->main_conf;
    r->srv_conf = hc->conf_ctx->srv_conf;
    r->loc_conf = hc->conf_ctx->loc_conf;

    // 设置请求开始的时间,限速用
    r->start_sec = tp->sec;
    r->start_msec = tp->msec;

    // 还没有开始解析请求头,方法未知
    r->method = NGX_HTTP_UNKNOWN;

    // http协议版本号默认是1.0
    r->http_version = NGX_HTTP_VERSION_10;
    
    ...
}

各个请求阶段

在接收完请求头数据后,nginx 就会调用 ngx_http_core_run_phases() 驱动各个 http 模块处理请求。

nginx 划分了 11 个阶段,相互协作以流水线的方式处理请求,从而达到高度的灵活性,它里面可以存储数个回调函数,nginx 会逐个调用里面的函数完成请求的处理。

// 需要在配置解析后的postconfiguration里向cmcf->phases数组注册
// content阶段较特殊,在loc_conf里可以为每个location设置单独的handler
// 这样可以避免数组过大
typedef enum {
    // 读取并解析完http头后,即将开始读取body
    // 目前仅有一个模块ngx_http_realip_module
    NGX_HTTP_POST_READ_PHASE = 0,

    // 在server阶段重写url
    NGX_HTTP_SERVER_REWRITE_PHASE,

    // 查找匹配的location,此阶段用户不可介入
    NGX_HTTP_FIND_CONFIG_PHASE,

    // 在location阶段重写url,较常用
    NGX_HTTP_REWRITE_PHASE,

    // 重写后,此阶段用户不可介入
    NGX_HTTP_POST_REWRITE_PHASE,

    // 检查访问权限之前
    NGX_HTTP_PREACCESS_PHASE,

    // 检查访问权限,较常用
    NGX_HTTP_ACCESS_PHASE,

    // 检查访问权限后,此阶段用户不可介入
    NGX_HTTP_POST_ACCESS_PHASE,

    // 1.13.3之前此阶段用户不可介入
    //NGX_HTTP_TRY_FILES_PHASE,
    // 1.13.4后改为NGX_HTTP_PRECONTENT_PHASE
    // 用户可以在此阶段添加模块,在产生内容前做一些处理
    NGX_HTTP_PRECONTENT_PHASE,

    // 最常用的阶段,产生http内容,响应客户端请求
    // 在这里发出去的数据会由过滤链表处理最终发出
    // content阶段较特殊,在loc_conf里可以为每个location设置单独的handler
    NGX_HTTP_CONTENT_PHASE,

    // 记录访问日志,请求已经处理完毕
    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

请求的环境数据

nginx_http_request_t 里的 ctx 是 http 请求处理流程中非常重要的数据,它在 ngx_http_create_request() 里创建的,可以存储每个模块各自的数据,整个生命周期都可以访问。

r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);

因为 nginx 的异步和多阶段处理机制的原因,很多时候模块不可能在一次调用中完成请求的处理,ctx可以用来存储中间结果。

我们可以使用模块的 ctx_index 直接访问 ctx 数组,当然 nginx 已经为我们定义了宏命令

#define ngx_http_get_module_ctx(r, module)  (r)->ctx[module.ctx_index]

// 设置模块存储在ngx_http_request_t里的ctx数据
#define ngx_http_set_ctx(r, c, module)      r->ctx[module.ctx_index] = c;

每个模块各自创建的环境数据必须使用 ngx_http_request_t 的内存池成员,这样才能保证当请求结束时分配的内存才会被及时回收。


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/cf


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。