zoukankan      html  css  js  c++  java
  • Nginx:解析HTTP配置的流程

    参考资料:深入理解Nginx(陶辉)

    书中有详细的讲解,这里只用本人的理解梳理一下该流程。

    一点提议:对于像我这样的新手,面对暂时看不懂章节,建议先往下看一下(可能就会有新的理解或灵感),而不要死磕在某一章节。

    几个重要的数据结构

    定义一个用于测试的结构体

    我们的测试模块将使用该结构体来存放配置信息,该结构只存放一个ngx_str_t。

    typedef struct {
      ngx_str_t          my_str;
    } ngx_http_mytest_conf_t;

    先看看ngx_http_module_t的定义

    typedef struct {
      ngx_int_t (*preconfiguration)(ngx_conf_t *cf);                   //解析配置文件前调用
      ngx_int_t (*postconfiguration)(ngx_conf_t *cf);                  //完成配置文件解析后调用
      void *(*create_main_conf)(ngx_conf_t *cf);                       //当需要创建数据结构用户存储main级别的全局配置项时候调用
      char *(*init_main_conf)(ngx_conf_t *cf, void *conf);             //初始化main级别配置项
      void *(*create_srv_conf)(ngx_conf_t *cf);                        //当需要创建数据结构用户存储srv级别的全局配置项时候调用
      char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); //合并server级别的配置项
      void *(*create_loc_conf)(ngx_conf_t *cf);                        //当需要创建数据结构用户存储loc级别的全局配置项时候调用
      char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); //合并location级别的配置项
    } ngx_http_module_t;

    HTTP框架定义了3个级别的配置main、srv、loc,分别表示直接出现在http{}、server{}、location{}块内的配置项。
    例如:当解析遇到http{}配置块时,会调用create_main_conf回调函数来创建并返回每个HTTP模块对应的结构体(对于我们的测试模块是ngx_http_mytest_conf_t)。

    我们的mytest模块实现create_loc_conf的是ngx_http_mytest_create_loc_conf方法

    static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf)
    {
      //创建mytest模块对应的结构体ngx_http_mytest_conf;
      ngx_http_mytest_conf_t *mycf;
      mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t));
      if(mycf==NULL){
        return NULL;
      }
      return mycf;
    }

    那么Nginx是怎么保存这些配置信息的呢?

    再看看ngx_http_conf_ctx_t结构

    typedef struct {
      /* 指针数组,数组中的每个元素指向所有HTTP模块create_main_conf方法产生的结构体*/
      void **main_conf;
      /* 指针数组,数组中的每个元素指向所有HTTP模块create_srv_conf方法产生的结构体*/
      oid **srv_conf;
      /* 指针数组,数组中的每个元素指向所有HTTP模块create_loc_conf方法产生的结构体*/
      void **loc_conf;
    } ngx_http_conf_ctx_t;
    

    用于定义模块的配置参数的结构体ngx_command_t

    typedef struct ngx_command_s 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);
      ngx_uint_t conf;
      ngx_uint_t offset;
      void *post;
    }

    其中各个成员代表的意义如下:
    name:配置项的名称
    type:配置项的参数类型、参数个数以及配置项可以在哪些位置出现(http、server、location等)
    set:处理配置项的回调方法。Nginx提供了14个预设的解析配置项的方法。
    conf:对于HTTP模块,conf是必须设置的,它的取值范围见下表

     

    offset:当前配置项在整个存储配置项的结构体中的偏移位置。对于使用Nginx预设的解析方法:
             Nginx首先通过conf成员找到应该用哪个结构体来存放,然后通过offset成员找到这个结构体中的相应成员,以便存放该配置。

    在mytest模块中,我们将这样定义一个ngx_command_t数组

    static ngx_command_t ngx_http_mytest_commands[] = {
      {
        ngx_string("test_str"),   //配置项名称
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,    //配置项只能出现在location块中并且配置参数数量为1个
        ngx_conf_set_str_slot,  //使用预设的解析方法解析配置参数,表示我们希望用ngx_str_t类型的变量来保存这个配置项的参数
        NGX_HTTP_LOC_CONF_OFFSET,  //使用create_loc_conf方法产生的结构体来存储解析出的配置项参数
        offsetof(ngx_http_mytest_conf_t,my_str); //与上面两个成员一起使用,通过这3个成员来找到该配置参数存放的位置(ngx_http_mytest_conf_t.my_str)。
        NULL
      },
      ngx_null_command
    };
    

    HTTP配置的流程

    1.主循环调用配置文件解析器解析nginx.conf文件。

    2.当发现配置文件中含有http{}关键字时,HTTP框架开始启动。

    3.初始化所有HTTP模块的序列号,并创建ngx_http_conf_ctx_t结构。

    4.调用每个HTTP模块的create_main_conf、create_srv_conf、create_loc_conf方法。

    5.把各HTTP模块上述3个方法返回的地址依次保存到ngx_http_conf_ctx_t结构体的3个数组中(如果某个模块没有定义相应的方法,则为NULL)。

    6.调用每个HTTP模块的preconfiguration方法。

    7.如果preconfiguration返回失败,那么Nginx进程将会停止。

    8.HTTP框架开始循环解析nginx.conf文件中http{...}里面的所有配置项

    9.配置文件解析器在检测到一个配置项后,会遍历所有HTTP模块,检查它们的ngx_command_t数组中的name项是否与配置项名相同。

    10.如果找到一个HTTP模块对这个配置项感兴趣,就调用ngx_command_t结构中的set方法来处理该配置项。

    11.如果set方法返回失败,那么Nginx进程会停止。

    12.配置文件解析器继续检查配置项。如果发现server{...}配置项,就会调用ngx_http_core_module模块来处理。

    13.ngx_http_core_module模块在解析server{...}之前,也会如第三步一样建立ngx_http_conf_ctx_t结构,并调用每个HTTP模块的create_srv_conf、create_loc_conf回调方法。

    14.将上一步各HTTP模块返回的指针地址保存到ngx_http_conf_ctx_t对应的数组中。

    15.开始调用配置文件解析器来处理server{...}里面的配置项。

    16.继续重复第9步的过程,遍历nginx.conf中当前server{...}内的所有配置项。

    17.配置文件解析器继续解析配置项,如果发现当前server块已经遍历到尾部,则返回ngx_http_core_module模块。

    18.返回配置文件解析器继续解析后面的配置项。

    19.配置文件解析器继续解析配置项,如果发现处理到了http{...}的尾部,返回个HTTP框架继续处理。

    20.调用merge_srv_conf、merge_loc_conf等方法合并这些不同块中每个HTTP模块分配的数据结构。

    21.HTTP框架处理完毕http配置项,返回给配置文件解析器继续处理其他http{...}外的配置项。

    22.配置文件解析器处理完所有配置项后告诉Nginx主循环配置项解析完毕,这是Nginx才会启动Web服务器。

    注意:上面还有一些我们没有列出来的步骤(如发现其他server块或者location块)。它们都会创建ngx_http_conf_ctx_t结构。

    HTTP配置模型的内存布局

    mytest模块的完整代码

      1 #include <ngx_config.h>
      2 #include <ngx_core.h>
      3 #include <ngx_http.h>
      4 
      5 typedef struct{
      6     ngx_str_t my_str;
      7 }ngx_http_mytest_conf_t;
      8 
      9 static ngx_int_t 
     10 ngx_http_mytest_handler(ngx_http_request_t *r);
     11 
     12 //创建mytest模块对应的结构体ngx_http_mytest_conf;
     13 static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf)
     14 {
     15     ngx_http_mytest_conf_t *mycf;
     16     mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t));
     17     if(mycf==NULL){
     18         return NULL;
     19     }
     20     return mycf;
     21 }
     22 
     23 static ngx_command_t ngx_http_mytest_commands[] = {
     24     {
     25         ngx_string("response_line"),   //配置项名称
     26         NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,    //配置项只能出现在location块中并且配置参数数量为1个
     27         ngx_conf_set_str_slot,  //使用预设的解析方法解析配置参数,表示我们希望用ngx_str_t类型的变量来保存这个配置项的参数
     28         NGX_HTTP_LOC_CONF_OFFSET,  //使用create_loc_conf方法产生的结构体来存储解析出的配置项参数
     29         offsetof(ngx_http_mytest_conf_t,my_str), //与上面两个成员一起使用,通过这3个成员来找到该配置参数存放的位置(ngx_http_mytest_conf_t.my_str)。
     30         NULL
     31     },
     32     ngx_null_command
     33 };
     34 
     35 //挂载handler
     36 static ngx_int_t
     37 ngx_http_mytest_init(ngx_conf_t *cf)
     38 {
     39         ngx_http_handler_pt        *h;
     40         ngx_http_core_main_conf_t  *cmcf;
     41 
     42         cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
     43 
     44         h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
     45         if (h == NULL) {
     46                 return NGX_ERROR;
     47         }
     48 
     49         *h = ngx_http_mytest_handler;
     50 
     51         return NGX_OK;
     52 }
     53 
     54 static ngx_http_module_t ngx_http_mytest_module_ctx={
     55     NULL,
     56     ngx_http_mytest_init,
     57     NULL,
     58     NULL,
     59     NULL,
     60     NULL,
     61     ngx_http_mytest_create_loc_conf,
     62     NULL
     63 };
     64 
     65 ngx_module_t ngx_http_mytest_module={
     66     NGX_MODULE_V1,
     67     //指向ngx_http_module_t结构体
     68     &ngx_http_mytest_module_ctx,
     69     //用来处理nginx.conf中的配置项
     70     ngx_http_mytest_commands,
     71     //表示该模块的类型
     72     NGX_HTTP_MODULE,
     73     NULL,
     74     NULL,
     75     NULL,
     76     NULL,
     77     NULL,
     78     NULL,
     79     NULL,
     80     NGX_MODULE_V1_PADDING
     81 };
     82 
     83 static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
     84 {
     85     //判断请求方法是否为GET或者HEAD,否则返回405 Not Allowed
     86     if(!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))){
     87         return NGX_HTTP_NOT_ALLOWED;
     88     }
     89     
     90     //丢弃请求中的包体
     91     ngx_int_t rc=ngx_http_discard_request_body(r);
     92     if(rc!=NGX_OK){
     93         return rc;
     94     }
     95     
     96     //设置相应头
     97     ngx_str_t type=ngx_string("text/plain");
     98     //获取配置参数
     99     ngx_http_mytest_conf_t* my_conf;
    100     my_conf = ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);
    101     ngx_str_t response=my_conf->my_str;
    102     r->headers_out.status=NGX_HTTP_OK;
    103     r->headers_out.content_length_n=response.len;
    104     r->headers_out.content_type=type;
    105     
    106     //发送HTTP头部
    107     rc=ngx_http_send_header(r);
    108     if(rc==NGX_ERROR||rc>NGX_OK||r->header_only){
    109         return rc;
    110     }
    111     
    112     //构造ngx_buf_t结构体准备发送包体
    113     ngx_buf_t *b;
    114     b=ngx_create_temp_buf(r->pool,response.len);
    115     if(b==NULL){
    116         return NGX_HTTP_INTERNAL_SERVER_ERROR;
    117     }
    118     ngx_memcpy(b->pos,response.data,response.len);
    119     b->last=b->pos+response.len;
    120     b->last_buf=1;
    121     
    122     //构造发送时的ngx_chain_t结构体
    123     ngx_chain_t out;
    124     out.buf=b;
    125     out.next=NULL;
    126     return ngx_http_output_filter(r,&out);
    127 }
    View Code

    按照http://www.cnblogs.com/runnyu/p/4871866.html编译安装该模块

    在nginx.conf文件中默认server块下配置如下信息

    location /response {
        response_line HelloWorld
    ;  
    }

    使用telnet查看结果

  • 相关阅读:
    匿名对象、栈空间和堆空间,String的两种实例化方式的比较,"=="和"equals()"的区别
    间接调用父类私有成员方法、通过抽象类可以达到不需要实现接口所有方法的目的
    this关键字、构造快和静态块
    java可变参数的支持和foreach输出
    java-循环结构体
    switch和if else的区别和应用
    作用域对象
    JavaWeb1
    JavaScript 表单编程
    好久不见
  • 原文地址:https://www.cnblogs.com/runnyu/p/4887256.html
Copyright © 2011-2022 走看看