nginx http块配置解析

本秂侑毒 提交于 2020-02-25 18:59:25

        在上一篇文章中,我们讲解了nginx http模块的存储结构,这个存储结构是我们理解http模块工作原理的基石。本文则主要讲解nginx是如何通过解析nginx.conf中的http配置块来一步一步构建http模块的这种存储结构的。

1. http配置块的解析

        http配置块的解析方法定义在http配置块对应的ngx_command_t结构体中,如下是该结构体的定义:

static ngx_command_t ngx_http_commands[] = {

    {ngx_string("http"),
     NGX_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS,
     ngx_http_block,
     0,
     0,
     NULL},

    ngx_null_command
};

        通过该定义可以看出,http配置块的解析工作主要是交由ngx_http_block()方法进行。如下是该方法的源码:

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
  char *rv;
  ngx_uint_t mi, m, s;
  ngx_conf_t pcf;
  ngx_http_module_t *module;
  ngx_http_conf_ctx_t *ctx;
  ngx_http_core_loc_conf_t *clcf;
  ngx_http_core_srv_conf_t **cscfp;
  ngx_http_core_main_conf_t *cmcf;

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

  // 创建用于存储http模块配置数据的结构体数组
  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;

  // 计算NGX_HTTP_MODULE类型的模块的个数,并且为每个模块的ctx_index赋值
  ngx_http_max_module = ngx_count_modules(cf->cycle, NGX_HTTP_MODULE);

  // 申请存储配置main conf对象的内存空间
  ctx->main_conf = ngx_pcalloc(cf->pool,sizeof(void *) * ngx_http_max_module);
  if (ctx->main_conf == NULL) {
    return NGX_CONF_ERROR;
  }

  // 申请存储srv conf对象的内存空间
  ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
  if (ctx->srv_conf == NULL) {
    return NGX_CONF_ERROR;
  }

  // 申请存储loc conf对象的内存空间
  ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
  if (ctx->loc_conf == NULL) {
    return NGX_CONF_ERROR;
  }

  // 这里主要是分别调用各个http模块的create_main_conf()、create_srv_conf()和create_loc_conf()
  // 方法,以生成对应模块的配置对象,然后将生成的对象放到ctx->main_conf、ctx->srv_conf和ctx->loc_conf
  // 数组中。需要注意的是,在将生成的对象放到对应的数组中的时候,所存放的相对位置是通过各个模块的ctx->index
  // 属性指定的,也就是其在当前类型的模块中的相对位置
  for (m = 0; cf->cycle->modules[m]; m++) {
    if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
      continue;
    }

    // 对于http模块而言,这里的ctx字段指向的是一个ngx_http_module_t结构体对象
    module = cf->cycle->modules[m]->ctx;
    mi = cf->cycle->modules[m]->ctx_index;

    if (module->create_main_conf) {
      // 这里的main_conf对应的是解析http块中的NGX_HTTP_MAIN_CONF_OFFSET类型的指令
      ctx->main_conf[mi] = module->create_main_conf(cf);
      if (ctx->main_conf[mi] == NULL) {
        return NGX_CONF_ERROR;
      }
    }

    if (module->create_srv_conf) {
      // 这里的srv_conf对应的是解析http块中的NGX_HTTP_SRV_CONF_OFFSET类型的指令
      ctx->srv_conf[mi] = module->create_srv_conf(cf);
      if (ctx->srv_conf[mi] == NULL) {
        return NGX_CONF_ERROR;
      }
    }

    if (module->create_loc_conf) {
      // 这里的loc_conf对应的是解析http块中的NGX_HTTP_SRV_CONF_OFFSET类型的指令
      ctx->loc_conf[mi] = module->create_loc_conf(cf);
      if (ctx->loc_conf[mi] == NULL) {
        return NGX_CONF_ERROR;
      }
    }
  }

  pcf = *cf;
  cf->ctx = ctx;  // 这里的ctx是ngx_http_conf_ctx_t类型的

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

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

    // 这里主要是调用各个模块的preconfiguration()方法进行前置配置,
    // 可以看到,这里的preconfiguration()方法主要是在解析http模块之前进行调用的
    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;
  // 由于这里是对http块进行解析,因而filename不需要传值,只需要根据当前位置开始进行解析即可。
  // 需要注意的是,调用完此方法之后,各个配置项的初始化方法也就解析完成了
  // 另外需要说明的是,当前解析的是http模块,调用到这里之后,就会对http模块的子模块进行解析,也就是说,
  // 当前方法调用完成之后,server、location等模块都已经解析完成
  rv = ngx_conf_parse(cf, NULL);

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

    // ngx_http_module_t
    // mi记录了当前遍历的模块的配置的ctx_index,也即module参数创建的配置对应的位置
    // 注意这里的module对象只是定义了当前模块进行相应的流程处理所需要执行的方法,其内部没有存储任何的与具体模块相关的数据对象
    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;
      }
    }

    // 这里主要是对各个模块的配置对象进行合并,因为有的配置项是既可以配置在http块,也可以配置在server块,
    // 还可以配置在location块中的,这里会根据当前模块的要求,对相应的配置块进行合并
    rv = ngx_http_merge_servers(cf, cmcf, module, mi);
    if (rv != NGX_CONF_OK) {
      goto failed;
    }
  }
  
  // 省略部分代码...
  
  // 调用各个模块的postconfiguration()方法进行配置解析完成之后的处理
  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;
      }
    }
  }
  
  // 省略部分代码
  
  return NGX_OK;
}

        这里展示的ngx_http_block()方法的源码我们只展示了与http模块结构体的初始化相关的代码,省略的代码我们将在后面的文章中进行讲解。ngx_http_block()方法可以说是解析http配置块的入口方法,该方法主要完成了如下:

  • 创建用于存储整个http模块配置数据的结构体ngx_http_conf_ctx_t
  • 计算当前所拥有的http模块的个数,并且会初始化各个模块中的ctx_index属性的值,将其标记为该模块在所有http模块中的相对位置,这个位置也就对应了当前模块在后面要讲解的main_confsrv_confloc_conf数组中的位置;
  • 分别为创建的ngx_http_conf_ctx_t结构体的main_confsrv_confloc_conf数组申请内存空间,而这些数组的长度就是前面计算得到的所有http模块的个数;
  • 遍历所有的模块,只要其为http模块,则依次调用其create_main_conf()create_srv_conf()create_loc_conf()方法,创建各个模块用于存储http配置块中的配置项结构体,需要注意的是,这些结构体的属性在此时还只是默认数据;
  • 遍历所有的http模块,调用其preconfiguration()方法进行配置文件解析之前的前置处理工作;
  • 调用ngx_conf_parse()方法,继续对http配置块进行解析,这里的解析工作其实就是对http配置块中的子配置项进行解析,不仅包括基础的配置项,也还包括server和location等配置块,在当前方法调用完成之后,整个http配置块都已经解析完成;
  • 遍历所有的http模块,调用其init_main_conf()方法,对各个模块的配置结构体属性设置默认值,由于上一步中已经解析完成了http配置块,因而配置文件中配置的属性是已经有值了的,这里的init_main_conf()方法主要是对那些没有配置的属性设置默认值。这里在初始化了各个模块的配置项时,还会调用ngx_http_merge_servers()进行配置项的合并,其主要作用就是在某个配置项在http块、server块或者location块中的两个及两个以上的配置块中都进行了配置时,将会选用哪一个值作为有效值的工作。这里的合并原理我们将在后面的文章进行讲解;
  • 遍历各个http模块,调用其postconfiguration()方法,进行配置解析完成之后的处理。

        上面的方法,可以说对解析http配置块的整套流程都进行了处理,这里的入口主要是进行了一些数据结构的初始化,而最终解析各个配置项的工作则主要交由ngx_conf_parse()方法进行。其中就包括server块和location块的解析。

2. server配置块的解析

        对于server块的解析,其对应的ngx_command_t结构体的配置如下:

{
  ngx_string("server"),
	NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS,
 	ngx_http_core_server,
 	0,
 	0,
 	NULL
}

        可以看到,对于server块的解析工作,主要是交由ngx_http_core_server()方法进行的。如下是该方法的源码:

static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) {
  char *rv;
  void *mconf;
  ngx_uint_t i;
  ngx_conf_t pcf;
  ngx_http_module_t *module;
  struct sockaddr_in *sin;
  ngx_http_conf_ctx_t *ctx, *http_ctx;
  ngx_http_listen_opt_t lsopt;
  ngx_http_core_srv_conf_t *cscf, **cscfp;
  ngx_http_core_main_conf_t *cmcf;

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

  // 这里的http_ctx的类型为ngx_http_conf_ctx_t结构体,其存储了main_conf、srv_conf和loc_conf三个
  // 属性的数组,这里需要特别注意,在ct->ctx也是一个ngx_http_conf_ctx_t类型的结构体,该属性在解析http
  // 模块的时候已经进行了初始化,也就是说,这里的ctx->main_conf = http_ctx->main_conf;是将当前新创建的
  // ctx的main_conf指向了其所在的http模块的main_conf,这样是可以达到一种复用的。
  // 从这里可以看出,nginx在解析每个server块的时候,会每一个server块都新创建一个ngx_http_conf_t结构体,
  // 用于存储当前正要解析的server块,而该结构体的main_conf会指向当前server块所在的http块的main_conf,
  // 这样可以节省内存,而且这样做的另一个好处在于,后续进行server和http配置块的配置合并的时候,
  // 只需要在每个server块对应的结构体中调用相应的合并方法即可
  http_ctx = cf->ctx; // 存储http块的ngx_http_conf_t结构体
  ctx->main_conf = http_ctx->main_conf;

  // 为当前要解析的server块申请内存
  ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
  if (ctx->srv_conf == NULL) {
    return NGX_CONF_ERROR;
  }

  // 为location申请内存
  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;

    // 调用各个模块的create_srv_conf()方法,以为每个模块创建其存储数据所需要的结构体对象
    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;
    }

    // 调用各个模块的create_loc_conf()方法,以为每个模块创建其存储数据所需要的结构体对象
    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;
    }
  }

  // 这里的ngx_http_core_module就是server配置项所在的模块定义,这里就是获取该模块的一个配置对象,
  // 也即ngx_http_core_srv_conf_t类型对象。
  // 这里的ngx_http_core_module.ctx_index的值为0
  cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
  cscf->ctx = ctx;

  // 获取核心模块的配置对象,这里的main_conf上面只是将其指向了http块创建的main_conf数组,因而这里
  // 获取到的也还是http模块中使用的同一个核心模块配置对象
  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;

  // 这里开始解析其余的配置项,比如location配置块
  rv = ngx_conf_parse(cf, NULL);

  *cf = pcf;

  if (rv == NGX_CONF_OK && !cscf->listen) {
    // 这里主要是创建一个ngx_http_listen_opt_t结构体,并且将各个属性进行初始化
    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
    // 如果当前用户对80端口没有权限,则使用8000端口
    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;

    // 这里主要是将当前的地址和端口的类型将其组织到lsopt.sockaddr.sockaddr中
    (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;
    }
  }

  return rv;
}

        对于server配置块的解析,其主要分为如下几个步骤:

  • 创建用于存储SRV类型和LOC类型的模块的配置项的结构体数组,需要注意的是,这里并没有创建用于存储MAIN类型的模块的结构体数组,因为既然配置项是配置在server块中的,说明其一定不是MAIN类型的,因而这里只是将main_conf指向了http块的main_conf数组;
  • 依次调用各个http模块的create_srv_conf()方法和create_loc_conf()方法,创建这些模块用于存储SRV类型和LOC类型的配置结构体;
  • 找到解析http模块得到的配置数组中核心模块的配置结构体,将当前server块对应的ngx_http_core_srv_conf_t结构体添加到该核心模块的配置结构体的servers数组中,通过这种方式将http配置块解析得到的结构体与各个server块解析得到的结构体进行关联;
  • 调用ngx_conf_parse()方法继续解析当前server块下的子配置项,包括各个子location,当前方法调用完成后,表示当前server块的解析已经完成;
  • 将当前server块配置的需要监听的端口添加到已有的端口列表中,以便于后续进行监听;

3. location配置块的解析

        对于location配置块的解析,其处理过程与server块本质上是类似的,不过location配置块解析的过程中会处理location配置项后面所指定的url匹配规则,并且会判断当前的匹配规则是否使用了正则表达式。另外,location配置块解析的时候需要注意,location配置块内部是可以有多个子location配置块的,这些子location配置块相当于是对父location配置块配置的一个补充,至于子location配置块的层级有多少个,nginx并没有做出限制。如下是对location配置块进行定义的ngx_command_t结构体:

{
  ngx_string("location"),
 	NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE12,
 	ngx_http_core_location,
 	NGX_HTTP_SRV_CONF_OFFSET,
 	0,
 	NULL
}

        可以看到,这里对location配置块的解析工作主要交由ngx_http_core_location()方法进行,如下是该方法的源码:

static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) {
  char *rv;
  u_char *mod;
  size_t len;
  ngx_str_t *value, *name;
  ngx_uint_t i;
  ngx_conf_t save;
  ngx_http_module_t *module;
  ngx_http_conf_ctx_t *ctx, *pctx;
  ngx_http_core_loc_conf_t *clcf, *pclcf;

  // 这里创建一个ctx对象,其主要是存储解析当前location配置块中的各个属性值
  ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
  if (ctx == NULL) {
    return NGX_CONF_ERROR;
  }

  pctx = cf->ctx;
  // 这里将当前ctx的main_conf和srv_conf分别指向当前location块所在的server和http块对应的结构体对象
  ctx->main_conf = pctx->main_conf;
  ctx->srv_conf = pctx->srv_conf;

  // 为loc_conf申请空间,以存储解析当前location块得到的各个模块属性值
  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;
    // 如果当前模块的create_loc_conf()方法不为空,则调用该方法生成对应的配置
    if (module->create_loc_conf) {
      ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = module->create_loc_conf(cf);
      if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
        return NGX_CONF_ERROR;
      }
    }
  }

  // 获取解析得到的核心模块的loc_conf值
  clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
  // 这里其实就是将核心模块的loc_conf指向当前location块对应的loc_conf值
  clcf->loc_conf = ctx->loc_conf;

  // 这里的elts是location配置块的参数个数,比如
  // location / {
  //            root   html;
  //        }
  // 那么这里的参数个数就是2,分别为location和/
  // 这里我们首先需要说明一下location的语法:
  // - 语法:
  //  location [=|~|~*|^~] uri {...}
  //  location @name {...}
  //- 使用规则:
  //  - 前缀字符串匹配:
  //    - 常规的字符串,比如`/html`,那么就匹配前缀为`/html`的URI;
  //    - `=`:精确匹配,那么URI就必须等于后面的字符串;
  //    - `^~`:如果匹配上了,就不再进行正则表达式匹配;
  //  - 正则表达式匹配:
  //    - `~`:大小写敏感的正则匹配;
  //    - `~*`:忽略大小写的正则匹配;
  //  - 用于内部跳转的命名location:
  //    - `@`
  //  - 合并连续的`/`符号:
  //    - `merge_slashes on;`
  value = cf->args->elts;

  // 判断当前的参数个数是否为3
  if (cf->args->nelts == 3) {

    // 这里下标为1的参数即为第二个参数
    len = value[1].len;
    mod = value[1].data;
    name = &value[2];

    if (len == 1 && mod[0] == '=') {

      // 这里的exact_match字段标志了当前匹配方式是否为完全匹配,
      // 由于当前是=操作,因而将其标志为1
      clcf->name = *name;
      clcf->exact_match = 1;

      // 如果当前是^~,则不进行正则匹配
    } else if (len == 2 && mod[0] == '^' && mod[1] == '~') {

      clcf->name = *name;
      clcf->noregex = 1;

      // 如果当前是~,则表示当前是大小写敏感的正则匹配
    } else if (len == 1 && mod[0] == '~') {

      if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
        return NGX_CONF_ERROR;
      }

      // 如果当前是~*,则表示其为大小写不敏感的正则匹配
    } else if (len == 2 && mod[0] == '~' && mod[1] == '*') {

      if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
        return NGX_CONF_ERROR;
      }

    } else {
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "invalid location modifier \"%V\"", &value[1]);
      return NGX_CONF_ERROR;
    }

  } else {

    // 这里else分支的功能与上面if分支的功能基本上是一致的,主要区别在于下面允许第二个和第三个元素之间是
    // 没有空格的,除此之外,下面的else分支还处理了一种情况就是location后的参数是以@符号开头的,
    // 这种情况是会转发当前的请求到其他的location中的
    name = &value[1];

    if (name->data[0] == '=') {

      clcf->name.len = name->len - 1;
      clcf->name.data = name->data + 1;
      clcf->exact_match = 1;

    } else if (name->data[0] == '^' && name->data[1] == '~') {

      clcf->name.len = name->len - 2;
      clcf->name.data = name->data + 2;
      clcf->noregex = 1;

    } else if (name->data[0] == '~') {

      name->len--;
      name->data++;

      if (name->data[0] == '*') {

        name->len--;
        name->data++;

        if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) {
          return NGX_CONF_ERROR;
        }

      } else {
        if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) {
          return NGX_CONF_ERROR;
        }
      }

    } else {

      clcf->name = *name;

      // 处理以@符号开头的情况
      if (name->data[0] == '@') {
        clcf->named = 1;
      }
    }
  }

  // 如果当前location是嵌入某个location中的,那么pclcf指向的就是父location的解析值,
  // 如果没有父location,那么这里的cf->cmd_type就不等于NGX_HTTP_LOC_CONF
  pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];

  if (cf->cmd_type == NGX_HTTP_LOC_CONF) {

    /* nested location */

#if 0
    clcf->prev_location = pclcf;
#endif

    // 如果父location是精确匹配,那么其是不能有子location的
    if (pclcf->exact_match) {
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "location \"%V\" cannot be inside "
                         "the exact location \"%V\"",
                         &clcf->name, &pclcf->name);
      return NGX_CONF_ERROR;
    }

    // 如果父location使用了@进行重定向,那么其是不能有子location的
    if (pclcf->named) {
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "location \"%V\" cannot be inside "
                         "the named location \"%V\"",
                         &clcf->name, &pclcf->name);
      return NGX_CONF_ERROR;
    }

    // 使用@进行重定向的location只允许出现在server块下,不能出现在嵌套的子location中
    if (clcf->named) {
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "named location \"%V\" can be "
                         "on the server level only",
                         &clcf->name);
      return NGX_CONF_ERROR;
    }

    len = pclcf->name.len;

#if (NGX_PCRE)
    // 这里的len是父location的表达式的长度,因而这里就是比较子location是否是以父location为前缀开头的,
    // 如果不是,则返回异常
    if (clcf->regex == NULL && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
#else
      if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0)
#endif
    {
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                         "location \"%V\" is outside location \"%V\"",
                         &clcf->name, &pclcf->name);
      return NGX_CONF_ERROR;
    }
  }

  if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
    return NGX_CONF_ERROR;
  }

  save = *cf;
  cf->ctx = ctx;
  cf->cmd_type = NGX_HTTP_LOC_CONF;

  // 继续解析配置文件的子模块配置
  rv = ngx_conf_parse(cf, NULL);

  *cf = save;

  return rv;
}

        这里对location配置块的解析工作主要分为如下几个步骤进行:

  • 申请用于存储location配置块中各个模块的配置项的配置的结构体数组,并将其保存到loc_conf属性中,而其main_confsrv_conf则直接继承自当前location块所在的server块对应的结构体的main_confsrv_conf
  • 依次调用各个http模块的create_loc_conf()方法,创建用于存储各个模块的配置项的结构体;
  • 解析location配置项后面的用于匹配url的表达式;
  • 进行父子location的校验工作,比如,父location是精确匹配的,那么此时是不能有子location的。这项校验工作主要用于有嵌套location的情况;
  • 继续调用ngx_conf_parse()方法解析当前location配置块下的子配置项,也包括子location的解析。可以看出来,这里对子location的解析过程实际上就是一个递归解析的过程,而解析所使用的方法还是上面的ngx_http_core_location()方法。

4. 小结

        本文主要从源码的角度对nginx是如何解析http块、server块和location块的过程进行了详细讲解,着重说明了其是如何构建nginx http模块的配置结构体的。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!