Nginx 源码阅读笔记9 http 模块初始化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/scnu20142005027/article/details/71244240

http 模块的初始化个人认为十分复杂,主要数据之间的关系太乱了,那么先从 http 部分的配置解析开始,首先是 http 块的解析,也就是ngx_http_block函数,由于这些函数都特别特别地长,所以就挑重点看吧
首先保证只有一个 http 块,然后创建一个存放 http 块下所有模块配置信息的ngx_http_conf_ctx_t结构体

if (*(ngx_http_conf_ctx_t **) conf) {
    return "is duplicate";
}

ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
    return NGX_CONF_ERROR;
}

*(ngx_http_conf_ctx_t **) conf = ctx;

确定 http 模块的数量,创建并初始化相应数量的各模块各级的配置结构体,注意这些都是只用于这个 http 块的,如果下面有 serv 块需要重新创建,也就是分块保存配置,以此来实现不同层级的配置合并

ngx_http_max_module = ngx_count_modules(cf->cycle, NGX_HTTP_MODULE);

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

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

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;
    mi = cf->cycle->modules[m]->ctx_index;

    if (module->create_main_conf) {
        ctx->main_conf[mi] = module->create_main_conf(cf);
        if (ctx->main_conf[mi] == NULL) {
            return NGX_CONF_ERROR;
        }
    }

    if (module->create_srv_conf) {
        ctx->srv_conf[mi] = module->create_srv_conf(cf);
        if (ctx->srv_conf[mi] == NULL) {
            return NGX_CONF_ERROR;
        }
    }

    if (module->create_loc_conf) {
        ctx->loc_conf[mi] = module->create_loc_conf(cf);
        if (ctx->loc_conf[mi] == NULL) {
            return NGX_CONF_ERROR;
        }
    }
}

然后调用各 http 模块的preconfiguration,接着开始解析 http 块下的 main 级配置,这里还是老规矩,先要保存之前 cf 用于解析完后恢复

pcf = *cf;
cf->ctx = ctx;

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->preconfiguration) {
        if (module->preconfiguration(cf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }
}

cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);

那么先存个档,准备跳转了,后续部分等到最后再看,这里假设遇到了一个 server 块,跳到ngx_http_core_server函数
函数开头和解析 http 块时差不多

ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
    return NGX_CONF_ERROR;
}

http_ctx = cf->ctx;
ctx->main_conf = http_ctx->main_conf;

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

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

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

    module = cf->cycle->modules[i]->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[i]->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[i]->ctx_index] = mconf;
    }
}

这里没有重复性判断,因为可以有很多个 server 块,然后就像刚刚说的,创建了自己的ngx_http_conf_ctx_t结构体,需要注意的是这里的main_conf指向了 http 块的main_conf,server 块里没有 main 级配置
接着又是熟悉的节奏,解析 server 块下的配置

cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
cscf->ctx = ctx;

cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

cscfp = ngx_array_push(&cmcf->servers);
if (cscfp == NULL) {
    return NGX_CONF_ERROR;
}

*cscfp = cscf;

pcf = *cf;
cf->ctx = ctx;
cf->cmd_type = NGX_HTTP_SRV_CONF;

rv = ngx_conf_parse(cf, NULL);

相比之前,这里又多了一些操作,首先明确下ngx_http_core_module模块的 main 和 srv 级配置结构体

typedef struct {
    ngx_array_t                servers;         /* ngx_http_core_srv_conf_t */
    ...
} ngx_http_core_main_conf_t;

typedef struct {
    ...
    ngx_http_conf_ctx_t        *ctx;
    ...
} ngx_http_core_srv_conf_t;

首先是设置 srv 级配置结构体中的ctx,指向当前 server 块的ngx_http_conf_ctx_t结构体,简单地理解的话就是这个结构体代表了当前 server 块,内含当前 server 块的所有配置信息,然后往ngx_http_core_module 的 main 级结构体的servers数组中加入当前 server 块的ngx_http_conf_ctx_t结构体,servers数组用于保存配置文件中的所有 server 块,用一张图来简单表达下就是
srv_conf
那么接下来又要跳转了,在解析 location 块前,先假设遇到一个listen 0.0.0.0:80 reuseport,这时候会调用ngx_http_core_listen函数

ngx_http_core_srv_conf_t *cscf = conf;
ngx_url_t               u;
ngx_http_listen_opt_t   lsopt;

cscf->listen = 1;

value = cf->args->elts;

ngx_memzero(&u, sizeof(ngx_url_t));

u.url = value[1];
u.listen = 1;
u.default_port = 80;

if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
    if (u.err) {
        ... // ngx_conf_log_error
    }
    return NGX_CONF_ERROR;
}

首先设置了 server 块的listen域,表示需要监听,然后来了一个新的结构体ngx_url_t,里面存放了端口、协议簇、地址等信息,可以看到这里默认端口是 80,然后是ngx_parse_url函数,这个函数比较长但是逻辑比较简单,按上面那个例子来说,就是从127.0.0.1:80解析出地址和端口号,然后存放到ngx_url_t
接下来的一大串代码都是在设置lsopt变量,类型为ngx_http_listen_opt_t,简单来说这个结构体里存放了描述 socket 特性的东西,例如发送接收缓冲区大小、fastopen、reuseport 等,这些值是根据listen命令后面的可选参数来设定的,然后就来到最后的部分

if (ngx_http_add_listen(cf, cscf, &lsopt) == NGX_OK) {
    return NGX_CONF_OK;
}

ngx_http_add_listen函数里面跳来跳去的,里面涉及到了几个结构体

typedef struct {
    ...
    ngx_array_t               *ports;     /* array of ngx_http_conf_port_t */
    ...
} ngx_http_core_main_conf_t;

typedef struct {
    ngx_int_t                  family;
    in_port_t                  port;
    ngx_array_t                addrs;     /* array of ngx_http_conf_addr_t */
} ngx_http_conf_port_t;

typedef struct {
    ngx_http_listen_opt_t      opt;
    ...
    /* the default server configuration for this address:port */
    ngx_http_core_srv_conf_t  *default_server;
    ngx_array_t                servers;  /* array of ngx_http_core_srv_conf_t */
} ngx_http_conf_addr_t;

函数的目的就是设置 main 级配置的ports数组,这个数组根据端口关联各个虚拟主机,也就是 server 块,假设配置文件如下

http {
    server {
        listen 10.0.0.1:80 default_server;
        server_name www.server1.com www.server2.com;
    }
    server {
        listen 10.0.0.1:80;
        server_name www.server3.com;
    }
    server {
        listen 10.0.0.2:80;
    }
    server {
        listen 10.0.0.3:8000;
    }
}

那么结果大概如下图所示,其中default_server就是如果不能找到 http 请求中指定的 host 时就会使用该虚拟主机
ports
解析listen配置结束,假设接下来遇到 location 块,解析函数为ngx_http_core_location,开头还是一如既往,只是srv_conf指向上级 server 块的srv_conf

ngx_http_core_loc_conf_t  *clcf;

ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
    return NGX_CONF_ERROR;
}

pctx = cf->ctx;
ctx->main_conf = pctx->main_conf;
ctx->srv_conf = pctx->srv_conf;

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

...

clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
clcf->loc_conf = ctx->loc_conf;

这里还将ngx_http_core_loc_conf_tloc_conf域指向了各个模块的 loc 级配置数组
接下来的部分是在判断 location 指令后的参数,设置前缀匹配、精确匹配、正则匹配、命名路径等相关值,其中还处理了嵌套 location 块的情况
函数最后调用了ngx_http_add_location,首先需要明确一下,server 块下个各个 location 块是通过 server 块的ngx_http_core_loc_conf_tlocations队列联系起来的,队列内存放的元素类型为ngx_http_location_queue_t,定义如下

typedef struct {
    ngx_queue_t                      queue;
    ngx_http_core_loc_conf_t        *exact;          // 路径精确匹配 包括正则
    ngx_http_core_loc_conf_t        *inclusive;      // 路径非精确匹配 例如前缀匹配
    ngx_str_t                       *name;           // 路径名
    u_char                          *file_name;      // 配置文件名称
    ngx_uint_t                       line;           // 配置文件行数
    ngx_queue_t                      list;           // 含有相同前缀的节点
} ngx_http_location_queue_t;

假设配置文件为

server {
    location = /test1 {
    }
    location ^~ /test2 {
    }
}

则执行完`ngx_http_add_location后效果如下图所示
loc_conf
接下来便是解析 location 下的配置项,这里就不分析下去了,现在假设解析完了 server 块里的配置项,回到ngx_http_core_server函数

if (rv == NGX_CONF_OK && !cscf->listen) {
    ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t));

    sin = &lsopt.sockaddr.sockaddr_in;

    sin->sin_family = AF_INET;
#if (NGX_WIN32)
    sin->sin_port = htons(80);
#else
    sin->sin_port = htons((getuid() == 0) ? 80 : 8000);
#endif
    sin->sin_addr.s_addr = INADDR_ANY;

    lsopt.socklen = sizeof(struct sockaddr_in);

    lsopt.backlog = NGX_LISTEN_BACKLOG;
    lsopt.rcvbuf = -1;
    lsopt.sndbuf = -1;
#if (NGX_HAVE_SETFIB)
    lsopt.setfib = -1;
#endif
#if (NGX_HAVE_TCP_FASTOPEN)
    lsopt.fastopen = -1;
#endif
    lsopt.wildcard = 1;

    (void) ngx_sock_ntop(&lsopt.sockaddr.sockaddr, lsopt.socklen,
                         lsopt.addr, NGX_SOCKADDR_STRLEN, 1);

    if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}

解析完 server 块后,如果没有指定listen,则监听默认地址,这里的函数之前都提到过,和解析listen配置项时类似
好的,然后假设解析完 http 块下的配置项,回到最开始的ngx_http_block函数

cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
cscfp = cmcf->servers.elts;

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

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

    /* init http{} main_conf's */

    if (module->init_main_conf) {
        rv = module->init_main_conf(cf, ctx->main_conf[mi]);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }

    rv = ngx_http_merge_servers(cf, cmcf, module, mi);
    if (rv != NGX_CONF_OK) {
        goto failed;
    }
}

首先遍历servers数组,调用init_main_conf函数,然后逐级向下合并配置项,合并完后开始构造 location 搜索树

for (s = 0; s < cmcf->servers.nelts; s++) {
    clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];

    if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}

location 搜索树用于快速查找 http 请求对应的 location,首先遍历servers数组,调用ngx_http_init_locations函数,这个函数将之前提到的locations队列按类型和特定规则排序,处理然后切分出命名路径存放到 srv 级配置的named_locations中,切分出正则路径放到 loc 级配置的regex_locations中,其余路径留在 loc 级配置的locations
然后调用ngx_http_init_static_location_trees函数构造静态树,这个函数首先合并了路径名称相同的精确匹配路径和前缀匹配路径,例如location = /testlocation /test,合并后将前缀匹配的inclusive存放到精确匹配的inclusive域中,然后从队列中删除前缀匹配的项

lq = (ngx_http_location_queue_t *) q;
lx = (ngx_http_location_queue_t *) x;

if (lq->name->len == lx->name->len
    && ngx_filename_cmp(lq->name->data, lx->name->data, lx->name->len) == 0) {
    ...

    lq->inclusive = lx->inclusive;
    ngx_queue_remove(x);
}

ngx_http_init_static_location_trees函数接着合并具有相同前缀的节点合并到同一个节点的list队列中,这部分我没仔细看,感觉上最后应该是构造出一个三叉树,左子树是字典序小的节点,右子树是字典序大的节点,中间子树是具有相同前缀的节点,某些部分有点像字典树
然后调用ngx_http_init_phases为各个 phase 的 handler 数组分配空间,代码比较简单,接下来把ngx_http_headers_in中定义的 http 头存入headers_in_hash哈希表,里面指定了遇到这些 http 头是需要执行的处理函数

if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
    return NGX_CONF_ERROR;
}

if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
    return NGX_CONF_ERROR;
}

接着调用各个 http 模块的postconfiguration函数,完后调用ngx_http_variables_init_vars函数,但是与变量有关的部分我都没有看

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->postconfiguration) {
        if (module->postconfiguration(cf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }
}

if (ngx_http_variables_init_vars(cf) != NGX_OK) {
    return NGX_CONF_ERROR;
}

由于各个模块一般在postconfiguration函数中加入自己希望介入的 phase 的处理函数,所以接下来就可以调用ngx_http_init_phase_handlers来初始化各个 handler 数组,这个函数将各个 handler 数组的元素按顺序加入到phases数组中,此外还指定了nextchecker,开头首先定义了变量n来存放 handler 总数

use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;

n = 1                  /* find config phase */
    + use_rewrite      /* post rewrite phase */
    + use_access       /* post access phase */
    + cmcf->try_files;

for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
    n += cmcf->phases[i].handlers.nelts;
}

由于以下四个阶段是用户不可以介入的,所以这里需要算上这些阶段可能存在的 handler

NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_TRY_FILES_PHASE
NGX_HTTP_FIND_CONFIG_PHASE

接着对应各阶段设置不同的nextchecker,需要注意next一般指的下一个阶段的首个处理函数(可能跳过了本阶段的处理函数),但是也有特殊情况,比如NGX_HTTP_ACCESS_PHASE,默认的next不是指向NGX_HTTP_POST_ACCESS_PHASE,还有就是这里的 handler 是反向添加的

n += cmcf->phases[i].handlers.nelts;

for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
    ph->checker = checker;
    ph->handler = h[j];
    ph->next = n;
    ph++;
}

初始化完 phase 处理函数后就来到最后的部分了!也就是ngx_http_optimize_servers函数,首先遍历ports数组,并将其中存放的地址排序,然后将地址中存放的所有servers的以名字为 key 加入 hash 表,这里还分了前置通配符和后置通配符的情况

for (p = 0; p < ports->nelts; p++) {
    ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
             sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);

    addr = port[p].addrs.elts;
    for (a = 0; a < port[p].addrs.nelts; a++) {

        if (addr[a].servers.nelts > 1
#if (NGX_PCRE)
            || addr[a].default_server->captures
#endif
           ) {
            if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
                return NGX_ERROR;
            }
        }
    }

    if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
        return NGX_ERROR;
    }
}

循环的最后,调用了ngx_http_init_listening,设置了监听对象,函数开头判断了是否含有通配地址

addr = port->addrs.elts;
last = port->addrs.nelts;

if (addr[last - 1].opt.wildcard) {
    addr[last - 1].opt.bind = 1;
    bind_wildcard = 1;

} else {
    bind_wildcard = 0;
}

若端口有含有通配地址,也就是*:port形式的,则那些没有设置 socket 属性的地址,统一bind通配地址即可,由于addr是排过序的,所以直接根据最后一个来元素判断,函数接着循环遍历addr数组,为每个需要绑定的地址添加监听对象

if (bind_wildcard && !addr[i].opt.bind) {
    i++;
    continue;
}

ls = ngx_http_add_listening(cf, &addr[i]);
if (ls == NULL) {
    return NGX_ERROR;
}

ngx_http_add_listening函数根据地址的opt设置ngx_listening_t的各项属性,需要注意的是,其中还为其指定了handler,也就是在处理 accept 事件是回调用的函数,具体以后有机会再说

ngx_listening_t           *ls;
...
ls = ngx_create_listening(cf, &addr->opt.sockaddr.sockaddr, addr->opt.socklen);
...
ls->handler = ngx_http_init_connection;

接着ngx_http_init_listening的循环中,还为刚刚创建的ngx_listening_t设置了servers,这是一个ngx_http_port_t,里面存放了该端口的其他地址,在ngx_http_init_connection中会使用到,例如使用通配地址的情况,可能需要与前面的请求地址比较
循环最后调用了ngx_clone_listening函数,该函数只有在对地址开启了reuseport时才有作用,也就是对同一ngx_listening_t复制与进程数量等量的份,它们绑定相同的地址,然后每个进程使用一个以此来解决惊群问题

if (ngx_clone_listening(cf, ls) != NGX_OK) {
    return NGX_ERROR;
}

最后,看完这些东西我已经晕了

猜你喜欢

转载自blog.csdn.net/scnu20142005027/article/details/71244240