Nginx学习(6)—— handler模块(自定义handler配置模块的编码编译)

2023-05-16

文章目录

  • Nginx请求的处理
  • handler简介
  • handler模块的基本结构
    • 模块配置结构
    • 模块配置指令
      • 先看一下 ngx_command_t 结构。
      • 一个模块配置的demo
      • 简单模块配置的案例演示
  • 模块上下文结构
  • 模块的定义
  • handler模块的基本结构
    • handler模块的挂载
  • handler模块的编码步骤
    • 实现一个自定义的handler模块
      • 需求
      • 实现步骤
        • 编码
        • 编译
  • 小结

Nginx请求的处理

nginx使用一个多进程模型来对外提供服务,其中一个master进程,多个worker进程。master进程负责管理nginx本身和其他worker进程。
所有实际上的业务处理逻辑都在worker进程的ngx_worker_process_cycle()函数中处理。
这个函数在src/os/unix/目录下的ngx_process_cycle.c文件中定义。

在ngx_worker_process_cycle()函数中对一次请求的处理流程如下:

  1. 操作系统提供的机制(例如epoll, kqueue等)产生相关的事件。
  2. 接收和处理这些事件,如是接受到数据,则产生更高层的request对象。
  3. 处理request的header和body。
  4. 产生响应,并发送回客户端。
  5. 完成request的处理。
  6. 重新初始化定时器及其他事件。

以http的一次request为例,一个HTTP Request的处理过程涉及到以下几个阶段:

  1. 初始化HTTP Request(读取来自客户端的数据,生成HTTP Request对象,该对象含有该请求所有的信息)
  2. 处理请求头。
  3. 处理请求体。
  4. 如果有的话,调用与此请求(URL或者Location)关联的handler。
  5. 依次调用各phase handler进行处理。(phase handler是每个阶段都有一些handler,在这一阶段处理问题的时候会调用该阶段的handler,通常phase handler是与配置文件中的某个location相关联的)

一个phase handler通常执行的任务如下:

  1. 获取location配置。
  2. 产生适当的响应。
  3. 发送response handler。
  4. 发送response body。

当nginx读取到一个HTTP Request的header的时候,nginx首先查找与这个请求关联的虚拟主机的配置。如果找到了这个虚拟主机的配置,那么通常情况下,这个HTTP Request将会经过以下几个阶段的处理:

  1. NGX_HTTP_POST_READ_PHASE:读取请求内容阶段。
  2. NGX_HTTP_SERVER_REWRITE_PHASE:server请求地址重写阶段。
  3. NGX_HTTP_FIND_CONFIG_PHASE:配置查找阶段。
  4. NGX_HTTP_REWRITE_PHASE:location请求地址重写阶段。
  5. NGX_HTTP_POST_REWRITE_PHASE:请求地址重写提交阶段。
  6. NGX_HTTP_PREACCESS_PHASE:访问权限检查准备阶段。
  7. NGX_HTTP_ACCESS_PHASE:访问权限检查阶段。
  8. NGX_HTTP_POST_ACCESS_PHASE:访问权限检查提交阶段。
  9. NGX_HTTP_TRY_FILES_PHASE:配置项try_files处理阶段。
  10. NGX_HTTP_CONTENT_PHASE:内容产生阶段。
  11. NGX_HTTP_LOG_PHASE:日志模块处理阶段。

在内容产生阶段,为了给一个request产生正确的响应,nginx必须把这个request交给一个合适的content handler去处理。如果这个request对应的location在配置文件中被明确指定了一个content handler,那么nginx就可以通过对location的匹配,直接找到这个对应的handler,并把这个request交给这个content handler去处理。这样的配置指令包括像,perl,flv,proxy_pass,mp4等。

如果一个request对应的location并没有直接有配置的content handler,那么nginx会依次尝试如下步骤:

  1. 如果一个location里面有配置 random_index on,那么随机选择一个文件,发送给客户端。
  2. 如果一个location里面有配置 index指令,那么发送index指令指明的文件,给客户端。
  3. 如果一个location里面有配置 autoindex on,那么就发送请求地址对应的服务端路径下的文件列表给客户端。
  4. 如果这个request对应的location上有设置gzip_static on,那么就查找是否有对应的.gz文件存在,有的话,就发送这个给客户端(客户端支持gzip的情况下)
  5. 请求的URI如果对应一个静态文件,static module就发送静态文件的内容到客户端。

内容产生阶段完成以后,生成的输出会被传递到filter模块去进行处理。filter模块也是与location相关的。所有的fiter模块都被组织成一条链。输出会依次穿越所有的filter,直到有一个filter模块的返回值表明已经处理完成。

常见的filter模块有:

  1. server-side includes。
  2. XSLT filtering。
  3. 图像缩放。
  4. gzip压缩。
  5. write:写输出到客户端,实际上是写到连接对应的socket上。
  6. postpone:这个filter是负责subrequest的,也就是子请求的。
  7. copy:将一些需要复制的buf(文件或者内存)重新复制一份然后交给剩余的body filter处理。

handler简介

Handler模块就是接受来自客户端的请求并产生输出的模块。
配置文件中使用location指令可以配置content handler模块,当Nginx系统启动的时候,每个handler模块都有一次机会把自己关联到对应的location上。
如果有多个handler模块都关联了同一个location,那么实际上只有一个handler模块真正会起作用。
所以在开发阶段应该避免多个handler模块关联同一个location的情况发生。

handler模块处理的结果通常有三种情况

  1. 处理成功。
  2. 处理失败:处理的时候发生了错误。
  3. 拒绝处理:这个location的处理就会由默认的handler模块来进行处理。
    • 例如,当请求一个静态文件的时候,如果关联到这个location上的一个handler模块拒绝处理,就会由默认的ngx_http_static_module模块进行处理,该模块是一个典型的handler模块。

handler模块的基本结构

模块配置结构

Nginx中每个模块都会提供一些指令,以便于用户通过配置去控制该模块的行为。
Nginx的配置信息分成了几个作用域(scope,有时也称作上下文)。作用域有main, server, 以及location。

模块配置指令

先看一下 ngx_command_t 结构。

  • 位置: src/core 目录下 ngx_conf_file.h

  • 原型:

    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_HTTP_MODULE类型模块所用 
    								    (我们编写的基本上都是NGX_HTTP_MOUDLE,只有一些nginx核心模块是非NGX_HTTP_MODULE),
    								    该字段指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。
    								    因为http模块对所要保存的配置信息,划分了main, server和location三个地方进行存储,
    								    每个地方都有一个内存池用来分配存储这些信息的内存。
    								    这里可能的值为 NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET。
    								    当然也可以直接置为0,就是NGX_HTTP_MAIN_CONF_OFFSET。*/
        ngx_uint_t            offset;	/*指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。
    								    因为对于配置信息的存储,一般我们都是定义个结构体来存储的。
    								    那么比如我们定义了一个结构体A,该项配置的值需要存储到该结构体的b字段。
    								    那么在这里就可以填写为offsetof(A, b)。
    								    对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。*/
        void                 *post;		/* 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,
        								   以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为0即可。*/
    };
    
  • 对type字段的补充说明
    nginx提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配置指令的详细的说明。

    • 属性值可以有:

      1. NGX_CONF_NOARGS:配置指令不接受任何参数。
      2. NGX_CONF_TAKE1:配置指令接受1个参数。
      3. NGX_CONF_TAKE2:配置指令接受2个参数。
      4. NGX_CONF_TAKE3:配置指令接受3个参数。
      5. NGX_CONF_TAKE4:配置指令接受4个参数。
      6. NGX_CONF_TAKE5:配置指令接受5个参数。
      7. NGX_CONF_TAKE6:配置指令接受6个参数。
      8. NGX_CONF_TAKE7:配置指令接受7个参数。
        可以组合多个属性,比如一个指令即可以不填参数,也可以接受1个或者2个参数。那么就是
        NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2。
    • nginx还提供了一些定义,使用起来更简洁:

      1. NGX_CONF_TAKE12:配置指令接受1个或者2个参数。
      2. NGX_CONF_TAKE13:配置指令接受1个或者3个参数。
      3. NGX_CONF_TAKE23:配置指令接受2个或者3个参数。
      4. NGX_CONF_TAKE123:配置指令接受1个或者2个或者3参数。
      5. NGX_CONF_TAKE1234:配置指令接受1个或者2个或者3个或者4个参数。
      6. NGX_CONF_1MORE:配置指令接受至少一个参数。
      7. NGX_CONF_2MORE:配置指令接受至少两个参数。
      8. NGX_CONF_MULTI:配置指令可以接受多个参数,即个数不定。
      9. NGX_CONF_BLOCK:配置指令可以接受的值是一个配置信息块。也就是一对大括号括起来的内容。里面可以再包括很多的配置指令。比如常见的server指令就是这个属性的。
      10. NGX_CONF_FLAG:配置指令可以接受的值是”on”或者”off”,最终会被转成bool值。
      11. NGX_CONF_ANY:配置指令可以接受的任意的参数值。一个或者多个,或者”on”或者”off”,或者是配置块。
        需要说明的是,无论如何,nginx的配置指令的参数个数不可以超过NGX_CONF_MAX_ARGS个。目前这个值被定义为8,也就是不能超过8个参数值。
    • 下面介绍一组说明配置指令作用域的相关属性:

      1. NGX_DIRECT_CONF:可以出现在配置文件中最外层。例如已经提供的配置指令daemon,master_process等。
      2. NGX_MAIN_CONF:http、mail、events、error_log等。
      3. NGX_ANY_CONF:该配置指令可以出现在任意配置级别上。
      • 对于我们编写的大多数模块而言,都是在处理http相关的事情,也就是所谓的都是NGX_HTTP_MODULE,对于这样类型的模块,其配置可能出现的位置也是分为直接出现在http里面,以及其他位置:
      1. NGX_HTTP_MAIN_CONF: 可以直接出现在http配置指令里。
      2. NGX_HTTP_SRV_CONF:可以出现在http里面的server配置指令里。
      3. NGX_HTTP_LOC_CONF:可以出现在http server块里面的location配置指令里。
      4. NGX_HTTP_UPS_CONF:可以出现在http里面的upstream配置指令里。
      5. NGX_HTTP_SIF_CONF:可以出现在http里面的server配置指令里的if语句所在的block中。
      6. NGX_HTTP_LMT_CONF:可以出现在http里面的limit_except指令的block中。
      7. NGX_HTTP_LIF_CONF:可以出现在http server块里面的location配置指令里的if语句所在的block中。
  • 对set字段的补充说明
    这是一个函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。

    • 函数原型
      // 处理成功时,返回NGX_OK,否则返回NGX_CONF_ERROR或者是一个自定义的错误信息的字符串。
      /* cf: 该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个ngx_str_t类型的数组,
      	   该数组的首个元素是这个配置指令本身,第二个元素是指令的第一个参数,第三个元素是第二个参数,依次类推。*/
      /* cmd:  这个配置指令对应的ngx_command_t结构。*/
      /* conf: 就是定义的存储这个配置值的结构体,用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。*/
      char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
      
  • 为了更加方便的实现对配置指令参数的读取,nginx已经默认提供了对一些标准类型的参数进行读取的函数,可以直接赋值给set字段使用。
    下面列出这些已经实现的set类型函数

    1. ngx_conf_set_flag_slot:读取NGX_CONF_FLAG类型的参数。
    2. ngx_conf_set_str_slot:读取字符串类型的参数。
    3. ngx_conf_set_str_array_slot:读取字符串数组类型的参数。
    4. ngx_conf_set_keyval_slot:读取键值对类型的参数。
    5. ngx_conf_set_num_slot:读取整数类型(有符号整数ngx_int_t)的参数。
    6. ngx_conf_set_size_slot:读取size_t类型的参数,也就是无符号数。
    7. ngx_conf_set_off_slot:读取off_t类型的参数。
    8. ngx_conf_set_msec_slot: 读取毫秒值类型的参数。
    9. ngx_conf_set_sec_slot:读取秒值类型的参数。
    10. ngx_conf_set_bufs_slot:读取的参数值是2个,一个是buf的个数,一个是buf的大小。例如: output_buffers 1 128k;
    11. ngx_conf_set_enum_slot:读取枚举类型的参数,将其转换成整数ngx_uint_t类型。
    12. ngx_conf_set_bitmask_slot:读取参数的值,并将这些参数的值以bit位的形式存储。例如:HttpDavModule模块的dav_methods指令。

一个模块配置的demo

static ngx_command_t ngx_http_hello_commands[] = {
	{
		ngx_string("hello_string"),
		NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
		ngx_http_hello_string,
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_hello_loc_conf_t, hello_string),
		NULL 
	},
	
	ngx_null_command	// 每个模块配置后必不可少的  
						// 它的原型是 #define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }
};

我们定义的配置指令,一个叫hello_string可以接收一个或零个参数,可以出现在http server块里面的location配置指令里。该配置的处理函数是ngx_http_hello_string。该配置作用域在NGX_HTTP_LOC_CONF_OFFSET(location中)。该配置信息保存在自定义结构体ngx_http_hello_loc_conf_t 的hello_string字段。

简单模块配置的案例演示

  • 1. 首先我们在src/core/nginx.c文件中找到ngx_core_commands数组。

  • 2. 在该数组中添加一条自定义配置。
    在这里插入图片描述

  • 3. 实现处理函数ngx_conf_my_handler(这个函数名是自己起的)。
    在这里插入图片描述

  • 4. 重新编译install:可参考 https://blog.csdn.net/Stars____/article/details/129137175?spm=1001.2014.3001.5501

  • 5. 在nginx.conf配置文件中加上自定义的配置。
    在这里插入图片描述

  • 6. 测试./nginx -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);	/* 调用该函数创建本模块位于http block的配置信息存储结构。
        													 该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。*/
        char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);	/* 调用该函数初始化本模块位于http block的配置信息存储结构。
        								该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
    
        void       *(*create_srv_conf)(ngx_conf_t *cf);		/* 调用该函数创建本模块位于http server block的配置信息存储结构,
        							每个server block会创建一个。该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。*/
        char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);	/* 因为有些配置指令既可以出现在http block,
    					    也可以出现在http server block中。
    					    那么遇到这种情况,每个server都会有自己存储结构来存储该server的配置,
    					    但是在这种情况下http block中的配置与server block中的配置信息发生冲突的时候,就需要调用此函数进行合并,
    					    该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。当然为了安全起见还是建议提供。
    					    该函数执行成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
    
        void       *(*create_loc_conf)(ngx_conf_t *cf);	/* 调用该函数创建本模块位于location block的配置信息存储结构。
        						每个在配置中指明的location创建一个。该函数执行成功,返回创建的配置对象。失败的话,返回NULL。*/
        char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);	/* 与merge_srv_conf类似,这个也是进行配置值合并的地方。
        						该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
    } ngx_http_module_t;
    
  • Nginx里面的配置信息都是上下一层层的嵌套的,对于具体某个location的话,对于同一个配置,如果当前层次没有定义,那
    么就使用上层的配置,否则使用当前层次的配置。

    这些配置信息一般默认都应该设为一个未初始化的值,针对这个需求,Nginx定义了一系列的宏定义来代表各种配置所对应
    数据类型的未初始化值,如下:
    #define NGX_CONF_UNSET       -1
    #define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
    #define NGX_CONF_UNSET_PTR   (void *) -1
    #define NGX_CONF_UNSET_SIZE  (size_t) -1
    #define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1
    

模块的定义

对于开发一个模块来说,我们都需要定义一个ngx_module_t类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。

ngx_module_t 原型

typedef struct ngx_module_s          ngx_module_t;
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;

    char                 *name;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version;
    const char           *signature;

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

模块可以提供一些回调函数给nginx,当nginx在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,所以都简单赋值为NULL。

handler模块的基本结构

除了上面模块的基本结构以外,handler模块必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理。
这个处理函数的原型如下

// r是http的请求,里面包含请求所有的信息
// 该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的handler进行处理)返回NGX_DECLINE。
// 返回NGX_OK也就代表给客户端的响应已经生成好了,否则返回NGX_ERROR就发生错误了。
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

handler模块的挂载

  • handler模块真正的处理函数通过两种方式挂载到处理过程中

    1. 按处理阶段挂载。
    2. 按需挂载。
  • 按处理阶段挂载:为了更精细地控制对于客户端请求的处理过程,nginx把这个处理过程划分成了11个阶段。

    1. NGX_HTTP_POST_READ_PHASE:读取请求内容阶段。
    2. NGX_HTTP_SERVER_REWRITE_PHASE:Server请求地址重写阶段。
    3. NGX_HTTP_FIND_CONFIG_PHASE:配置查找阶段。
    4. NGX_HTTP_REWRITE_PHASE:Location请求地址重写阶段。
    5. NGX_HTTP_POST_REWRITE_PHASE:请求地址重写提交阶段。
    6. NGX_HTTP_PREACCESS_PHASE:访问权限检查准备阶段。
    7. NGX_HTTP_ACCESS_PHASE:访问权限检查阶段。
    8. NGX_HTTP_POST_ACCESS_PHASE:访问权限检查提交阶段。
    9. NGX_HTTP_TRY_FILES_PHASE:配置项try_files处理阶段。
    10. NGX_HTTP_CONTENT_PHASE:内容产生阶段。
    11. NGX_HTTP_LOG_PHASE:日志模块处理阶段。

    一般情况下,我们自定义的模块,大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的。挂载的动作一般是在模块上下文调用的postconfiguration函数中。

    • 注意:有几个阶段是特例,它不调用挂载地任何的handler,也就是不用挂载到这几个阶段:
      1. NGX_HTTP_FIND_CONFIG_PHASE:配置查找阶段。
      2. NGX_HTTP_POST_ACCESS_PHASE:访问权限检查提交阶段。
      3. NGX_HTTP_POST_REWRITE_PHASE:请求地址重写提交阶段。
      4. NGX_HTTP_TRY_FILES_PHASE:配置项try_files处理阶段。
  • 按需挂载

    • 以这种方式挂载的handler也被称为 content handler
    • 当一个请求进来以后,nginx从NGX_HTTP_POST_READ_PHASE阶段开始依次执行每个阶段中所有handler。执行到NGX_HTTP_CONTENT_PHASE阶段的时候,如果这个location有一个对应的content handler模块,那么就去执行这个content handler模块真正的处理函数。否则继续依次执行NGX_HTTP_CONTENT_PHASE阶段中所有content phase handlers,直到某个函数处理返回NGX_OK或者NGX_ERROR。
      也就是说当某个location处理到NGX_HTTP_CONTENT_PHASE阶段时,如果有content handler模块,那么NGX_HTTP_CONTENT_PHASE挂载的所有content phase handlers都不会被执行了。
      但是使用这个方法挂载上去的handler有一个特点是必须在NGX_HTTP_CONTENT_PHASE阶段才能执行到。如果你想自己的handler在更早的阶段执行,那就不要使用这种挂载方式。
    • 那么在什么情况会使用这种方式来挂载呢?
      一般情况下,某个模块对某个location进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用NGX_HTTP_CONTENT_PHASE阶段的其它handler进行处理的时候,就动态挂载上这个handler。

handler模块的编码步骤

  • 总结一下,实现一个handler的步骤:
    1. 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
    2. 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
    3. 编写handler处理函数。模块的功能主要通过这个函数来完成。

实现一个自定义的handler模块

需求

  • 我们将自定义一个模块,这个模块提供将2个配置指令,仅可以出现在location指令的作用域中(可以参考上文中的NGX_HTTP_LOC_CONF)。这两个指令是hello_string和hello_counter。
  • hello_string指令接受一个参数来设置显示的字符串。如果没有跟参数,那么就使用默认的字符串作为响应字符串。(可以参考上文中的NGX_CONF_TAKE1 | NGX_CONF_NOARGS)
  • hello_counter指令接收一个bool值(可以参考上文中的NGX_CONF_FLAG),如果设置为on,则会在响应的字符串后面追加Visited Times:的字样,以统计请求的次数。

实现步骤

编码

  1. 先在nginx源码目录下自己创建一个目录来保存自定义模块的代码和config文件。(名称无所谓)
    在这里插入图片描述
  2. ngx_http_hello_handler.c编码如下:
    #include <ngx_config.h>
    #include <ngx_core.h>
    #include <ngx_http.h>
    
    // 该结构体存放两条模块配置指令
    typedef struct
    {
        ngx_str_t hello_string;
        ngx_int_t hello_counter;
    }ngx_http_hello_loc_conf_t;
    
    // 一些自定义函数的声明
    static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
    static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
    static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    
    // 定义一个ngx_command_t数组来存放配置指令
    static ngx_command_t ngx_http_hello_commands[] = {
        {
            ngx_string("hello_string"),
            NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
            ngx_http_hello_string,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_hello_loc_conf_t, hello_string),
            NULL 
        },
        {
            ngx_string("hello_counter"),
            NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
            ngx_http_hello_counter,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_hello_loc_conf_t, hello_counter),
            NULL 
        },
        
        ngx_null_command    // 记得一定要有的结尾元素
    };
    
    
    static u_char ngx_hello_default_string[] = "Hello, world! (Default String)";
    static int ngx_hello_visited_times = 0;
    
    // 定义模块上下文结构
    static ngx_http_module_t ngx_http_hello_module_ctx = {
        NULL,                               /* preconfiguration */
        ngx_http_hello_init,                /* postconfiguration */
        NULL,                               /* create main configuration */
        NULL,                               /* init main configuration */
        NULL,                               /* create server configuration */
        NULL,                               /* merge server configuration */
        ngx_http_hello_create_loc_conf,     /* create location configuration */
        NULL                                /* merge location configuration */
    };
    
    // 定义模块,在该模块中设置自定义的模块上下文和模块指令
    ngx_module_t ngx_http_hello_module = {
        NGX_MODULE_V1,
        &ngx_http_hello_module_ctx,         /* module context */
        ngx_http_hello_commands,            /* module directives */
        NGX_HTTP_MODULE,                    /* module type */
        NULL,                               /* init master */
        NULL,                               /* init module */
        NULL,                               /* init process */
        NULL,                               /* init thread */
        NULL,                               /* exit thread */
        NULL,                               /* exit process */
        NULL,                               /* exit master */
        NGX_MODULE_V1_PADDING
    };
    
    // 定义自定义模块的真实处理函数
    static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
    {
        ngx_int_t rc;
        ngx_buf_t *b;
        ngx_chain_t out;
        ngx_http_hello_loc_conf_t* my_conf;     // 自定义的结构体,用来接收自定义配置信息
        u_char ngx_hello_string[1024] = {0};
        ngx_uint_t content_length = 0;
        ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
    
        // 在请求报文中凭借自定义模块ngx_http_hello_module去解析配置信息
        my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
        if (my_conf->hello_string.len == 0 )
        {
            // 若没有输入字符串使用默认字符信息
            ngx_str_set(&(my_conf->hello_string), ngx_hello_default_string);
        }
    
        if (my_conf->hello_counter == NGX_CONF_UNSET || my_conf->hello_counter == 0)
        {
            ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);
        }
        else
        {
            ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data,
            ++ngx_hello_visited_times);
        }
    
        ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
        content_length = ngx_strlen(ngx_hello_string);
    
        // 我们只响应“GET”和“HEAD”请求
        if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) 
        {
            return NGX_HTTP_NOT_ALLOWED;
        }
    
        // 丢弃请求正文,因为这里不需要它
        rc = ngx_http_discard_request_body(r);
        if (rc != NGX_OK) {
            return rc;
        }
    
        // 设置 “内容类型” 标头
        ngx_str_set(&r->headers_out.content_type, "text/html");
        // 如果请求类型为http “HEAD”,则仅发送标头
        if (r->method == NGX_HTTP_HEAD) {
            r->headers_out.status = NGX_HTTP_OK;
            r->headers_out.content_length_n = content_length;
            return ngx_http_send_header(r);
        }
    
        // 为响应主体分配缓冲区
        b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
        if (b == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    
        // 将此缓冲区连接到缓冲链
        out.buf = b;
        out.next = NULL;
    
        // 调整缓冲区的指针
        b->pos = ngx_hello_string;
        b->last = ngx_hello_string + content_length;
        b->memory = 1;          // 这个缓冲区在内存中
        b->last_buf = 1;        // 这是缓冲链中的最后一个缓冲区
        
        // 设置状态行
        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = content_length;
        
        // 发送响应的标题
        rc = ngx_http_send_header(r);
        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
        }
    
        // 发送响应的缓冲链
        return ngx_http_output_filter(r, &out);
    }
    
    // 创建本模块位于location block的配置信息存储结构
    static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
    {
        ngx_http_hello_loc_conf_t* local_conf = NULL;
    
        local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
        if (local_conf == NULL)
        {
            return NULL;
        }
    
        ngx_str_null(&local_conf->hello_string);
        local_conf->hello_counter = NGX_CONF_UNSET; // 默认未初始化的值
    
        return local_conf;
    }
    
    
    // hello_string指令回调函数
    static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_http_hello_loc_conf_t* local_conf;
        ngx_str_t* value;
    
        local_conf = conf;
        value = cf->args->elts;
    
        ngx_str_set(&(local_conf->hello_string), value[1].data);
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);
        
        return NGX_CONF_OK;
    }
    
    // hello_counter指令回调函数
    static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_http_hello_loc_conf_t* local_conf;
        ngx_str_t* value;
        unsigned char* val = NULL;
    
        local_conf = conf;
        value = cf->args->elts;
    
        val = value[1].data;
    
        local_conf->hello_counter = strncmp((const char*)val, "on", sizeof("on")) == 0 ? 1 : 0;
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_counter:%d", local_conf->hello_counter);
        
        return NGX_CONF_OK;
    }
    
    // 在创建和读取该模块的配置信息之后被调用
    static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf)
    {
        ngx_http_handler_pt *h;
        ngx_http_core_main_conf_t *cmcf;
    
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    
        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
        if (h == NULL) {
            return NGX_ERROR;
        }
    
        *h = ngx_http_hello_handler;
    
        return NGX_OK;
    }
    

编译

  1. config编码如下:这里的ngx_http_hello_module要和上面代码中的模块名称对应上,ngx_http_hello_handler要和文件名对应上。
    ngx_addon_name=ngx_http_hello_module
    HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_handler.c"
    
  2. 编译:进入nginx源码目录下,这里–add-module后面的路径是你自定义创建模块的绝对路径。
    ./configure --prefix=/usr/local/nginx --add-module=/home/vv/nginx-1.22.1/src/http/handlerTest
    make
    make install
    
  3. 修改配置文件:
    在这里插入图片描述
  4. 启动nginx:在nginx安装目录的sbin目录下,我的是/usr/local/nginx/sbin。执行 ./nginx
  5. 测试:这里的端口号和"test"都要和配置文件中的对应上
    在这里插入图片描述

小结

  • 1. 自定义模块的编码步骤

    1. 首先自定义结构体,用来存放自定义配置指令的集合(结构体名称自定义)。
    2. 定义一个ngx_command_t数组,用来存放每条指令的具体信息。
    3. 定义模块上下文结构(ngx_http_module_t)。(在这个结构中通过回调函数指明加载配置信息前后需要执行的操作,通常这里面需要指定真实的处理函数)
    4. 定义模块(ngx_module_t),并在其中指明第二和第三步的上下文和配置指令数组以及其他需要的信息。
    5. 实现真实的处理函数。
    6. 实现各种用到的回调函数。
    7. 编写config文件告诉nginx系统该模块的位置和名称信息。
    8. 编译。
      ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module
      make
      make install
      
    9. 测试。
  • 2. 模块中涉及到的回调函数的调用顺序:以上述案例为例

    • 首先调用了 ngx_http_hello_create_loc_conf 来初始化location的存储空间,并将这篇内存的首地址链到ngx_conf_t->pool上。
    • 其次读到配置指令调用 ngx_http_hello_string 和 ngx_http_hello_counter (也就是ngx_command_t->set函数)对指令进行解析。
    • 再调用模块上下文中指定的 ngx_http_hello_init 初始化函数,这里面实现了处理函数的挂载。
    • 当有请求过来时,调用处理函数 ngx_http_hello_handler

    ngx_http_module_t 结构中回调的调用顺序主要参考各个回调函数的调用时机(上文中有列出)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nginx学习(6)—— handler模块(自定义handler配置模块的编码编译) 的相关文章

  • nginx + python + websocket

    我如何配置nginx 最新版本 他们说它支持websockets 来支持WebSockets 我如何使用 python 来运行 websockets 连接 这就是我想要的 客户端使用 JavaScript 创建 WebSocket webs
  • 缓存 auth_request 中的令牌

    我想缓存请求标头字段授权中的令牌 Authorization Bearer abcdefghijklmnopqrstuvwxyz 我的目标是 我不必验证验证服务器上的每个请求 如果授权令牌已缓存 且有效 则请求应调用 API 而无需验证 l
  • NGINX 添加两个变量/参数编号

    我正在尝试设置一个 Nginx 我想在其中将两个数字相加 server server name pr d review apps example com location set port 50000 1 proxy pass http l
  • Sqlalchemy 返回 SELECT 命令的不同结果(query.all)

    我有网络服务器 512 RAM FLASK SQLAlchemy SQLite gt uWSGI gt Nginx 问题 Sqlalchemy 返回 SELECT 命令 query all 的不同结果 Example 在数据库中添加了一些记
  • 无法将上游映射到 nginx 服务器中的文件夹

    我想将系统端口 82 映射到 127 0 0 1 8080 runningSite 但 nginx 配置出现异常 upstream dev server 127 0 0 1 8080 runningSite server rewrite l
  • Meteor - 自动发起客户端登录

    我有一个 Meteor 应用程序 我使用 nginx 和内部 SSO 服务进行身份验证 我能够成功地完成此操作 并在服务器 Meteor onConnection 方法上的 nginx 设置 http 标头中检索用户详细信息 此时 我不确定
  • 如何在 OpenShift 上安装 Nginx

    虽然我跟着https blog openshift com lightweight http serving using nginx on openshift https blog openshift com lightweight htt
  • 使用 nginx 代理时在 RStudio 中运行闪亮的应用程序

    我正在尝试使用通过 nginx 代理的 RStudio 服务器来开发一个闪亮的应用程序 当我使用 RStudio 而不是服务器 来运行闪亮的应用程序时 一切正常 但是 当我通过代理 RStudio 运行时 该应用程序出现在 查看器 窗格中
  • 如何将 CORS(跨源策略)添加到 NGINX 中的所有域?

    我创建了一个文件夹 用于提供静态文件 CSS 图像 字体和 JS 等 我最终会将文件夹 CNAME 到子域中 以便在 CDN 上使用 以便与我的 Magento 2 设置一起使用 我想允许所有域通过 CORS 跨源策略进行所有访问 并且我也
  • 如何设置 Nginx URI 以修复重定向到指定位置中的空 URI

    问题 当使用包含 符号的无效 URL 访问我们的网站时 Nginx 会抛出 400 Bad Request 错误 我们希望将请求重写为 WordPress 404 页面 而不是 Nginx 页面 我已经尝试过以下方法 location 40
  • nginx工作进程如何共享“监听套接字”

    This http aosabook org en nginx html http aosabook org en nginx html说 工作进程接受来自共享 监听 套接字的新请求 并在每个进程内执行高效的运行循环 我查看了代码 但不明白
  • Amazon ECS - 在 Docker 入口点上使用 IAM 角色时权限被拒绝

    我正在寻找一种将机密 证书注入 Amazon ECS 容器的方法 就我而言 它是一个简单的 nginx 容器 我一直在使用 AWS Parameter Store 关注这篇文章 https aws amazon com blogs comp
  • 在后台线程加载广告 (adMob)

    我想在后台线程上加载我的添加 因为它使得SlidingMenu打开和关闭时滞后 我应该使用Thread Handler Or AsyncTask String MY AD UNIT ID AdView adView new AdView g
  • ./manage.py 使用 https 运行服务器

    manage py 运行服务器 0 0 0 0 8000 我使用上面的行作为我从 github 借用的代码的一部分 https github com ribeiroit boh puppet https github com ribeiro
  • Kong - 验证上游 ssl(ssl_proxy 打开)

    我已经成功为 API 安装了 kong 网关 该 API 通过上游负载平衡到多个目标 应用程序服务器 现在 我有一个我的应用程序服务器的自签名证书 kong 和目标之间的 ssl 握手应该失败 我推断 kong 不验证上游证书 经过一些研究
  • 上传大文件(几 GB)时,nginx 返回内部服务器错误

    我在 nginx 后面有一个 Artifactory 上传大于 4 GB 的文件失败 我相当确定这是 nginx 的错误 因为如果文件从本地主机上传 上传到本地主机 则不会出现问题 nginx 设置为client max body size
  • NGinx 域名重定向

    假设我有一个名为 xyz co 的网站 我还有其他具有相同前缀的域名 例如 xyz com xyz it xyz co it 现在 nginx 与端口 80 的 nginx conf 中的 server name xyz co 配合得很好
  • Nginx反向代理返回404

    我的 Nginx 安装并运行 下面是配置 etc nginx nginx conf 我要全部转发 api 到我的 tomcat 服务器 该服务器在同一服务器上的端口 9100 上运行 类型http myhost 9100 api apps有
  • nginx 反向代理 websocket

    nginx 现在支持代理 websockets 但我无法找到任何有关如何在没有单独的情况下执行此操作的信息location应用于使用 websocket 的 URI 的块 我见过一些人推荐这种方法的一些变体 location proxy h
  • 如何在位置中使用 Nginx Regexp

    Web 项目将静态内容放入 some content img 文件夹中 url规则为 img some md5 但文件夹中的位置 content img 前两位数字 Example url example com img fe5afe048

随机推荐

  • webpack 混淆压缩 javascript 后端代码

    需求背景 JavaScript 是脚本语言 没有编译过程 直接以源码就可以运行 有的时候 出于安全或者其他的原因 我们不希望别人直接读到源码 或者很容易对源码做出修改使用 这个时候 就需要对源码进行混淆压缩处理 经过处理后的代码体积变小 不
  • VideoJS 网页直播实现默认静音

    在开发 LiveQing高性能流媒体服务器 网页直播多分屏的时候 产品提出议建说 能不能在多分屏的时候 默认静音状态 因为多分屏界面 如果声音打开 好多个直播画面同时发出声音太过嘈杂 体验不好 我表示赞同 心想 这个应该不难处理吧 结果踩到
  • VideoJS 网页直播实现双击全屏

    最近接到客户需求 要求我们的网页直播播放器更加符合广大人民群众的使用习惯 实现双击全屏的效果 目前网页直播播放器使用了开源的 VideoJS 它的默认效果是单击播放区域暂停 只能通过右下角的最大化按钮触发最大化 要实现双击全屏播放的效果 就
  • LivePlayer H5直播/点播播放器安装与使用

    LivePlayer H5播放器 简介 H5直播 点播播放器 xff0c 使用简单 xff0c 功能强大 xff0c 免费使用 支持m3u8播放 支持HTTP FLV播放 支持RTMP播放 支持直播和点播播放 支持播放器快照截图 支持点播多
  • Echarts 修改折线的颜色和折线的点的大小方法

    在做SPC分析的时候或者一些专业的分析的时候有的客户会要求 点的大小 样式等 具体的设置方法如下 series type 39 line 39 showSymbol true symbol 39 circle 39 设定为实心点 symbo
  • 抽象工厂模式

    抽象工厂模式针对的是对产品族 xff0c 而不是产品等级结构 产品族 xff1a 同一厂商生产的产品 产品等级 xff1a 同一产品 xff0c 不同厂商的产品 比如水果类里面由苹果和香蕉 xff0c 水果就是产品族 xff0c 苹果香蕉就
  • 面向对象单例模式

    单例模式 xff1a 一个类只能创建一个对象 span class token keyword class span span class token class name A span span class token punctuati
  • 设计模式之代理模式

    概念 xff1a 为其他对象提供一种代理 xff0c 用来控制对象的访问 在某些情况下 xff0c 一个对象不适合或不能直接引用另一个对象 xff0c 而代理对象可以在客户端和目标对象之间起到中介作用 span class token co
  • ARM接口技术基础

    ARM介绍 嵌入式系统 xff1a 嵌入式系统 61 嵌入式硬件 43 嵌入式软件 硬件是软件的载体 xff0c 软件是硬件的灵魂 嵌入式软件 xff1a 1 裸机 xff1a APP 2 系统 xff1a OS 43 APP 嵌入式硬件
  • ARM指令之MOV指令汇编与机器码的对应关系

    指令条件码 条件码就是一种简单的测试ALU状态标志位的方法 mov指令机器码 比如 xff1a mov r1 3 立即数 xff1a 3 第一个操作数 xff1a 寄存器1 S xff1a 0 xff08 注意 xff1a mov指令后面加
  • 设计模式之外观模式

    什么是外观模式 外观模式就是将一个复杂的子类系统抽象到同一个接口进行管理 外界只需要通过此接口与子类系统进行交互 xff0c 不需要直接与复杂的子类系统进行交互 外观模式属于结构型模式 外观模式案例 1 实现KTV模式 xff1a 打开电视
  • 设计模式之适配器模式

    概念 将一个类的接口转换成客户希望的另外一个接口 使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 用vector容器中的for each 举例子 span class token keyword class span span c
  • Qt基础学习笔记

    Qt中的常用快捷键 注释 xff1a ctrl 43 运行 xff1a ctrl 43 r 编译 xff1a ctrl 43 b 帮助文档 xff1a F1 自动对齐 xff1a ctrl 43 i 同名之间 h文件与 cpp文件的切换 x
  • Nginx学习 (1) —— 初识Nginx(编译安装、启动停止、简单配置)

    文章目录 Nginx的发行版本Nginx的编译安装 xff08 ubuntu18 04 xff09 Nginx相关操作Nginx启动停止命令安装Nginx为系统服务 Nginx的目录结构与基本原理目录结构 xff1a 基本运行原理 xff1
  • Nginx学习 (2) —— 虚拟主机配置

    文章目录 虚拟主机原理域名解析与泛域名解析 xff08 实践 xff09 配置文件中ServerName的匹配规则技术架构多用户二级域名短网址 虚拟主机原理 为什么需要虚拟主机 xff1a 当一台主机充当服务器给用户提供资源的时候 xff0
  • 如任让Echarts默认显示全部的数据(x轴(时间轴)默认显示查询到的所有数据)

    Echarts绘制折线图 有时候会用到时间轴 但是有时候时间轴默认显示的是部分数据 而不是全部的数据 当客户要求默认显示全部的数据的时候 就可以使用下面的方法 把dataZoom属性的startValue设置为0即可 dataZoom st
  • Nginx学习(3)—— 反向代理、负载均衡、动静分离、URLRewrite、防盗链

    文章目录 网关 代理与反向代理Nginx的反向代理配置基于反向代理的负载均衡的配置负载均衡策略负载均衡权重相关配置 xff08 weight xff09 动静分离Nginx配置动静分离 URL rewrite伪静态配置负载均衡 43 URL
  • Nginx学习(4)—— 高可用配置(keepalived实现Nginx的高可用)

    文章目录 高可用场景及解决方案keepalived的安装 高可用场景及解决方案 一般高可用的服务端会都有备用服务 xff0c 当某一个服务节点挂掉的时候就会有备用机顶上 这往往需要反向代理 Nginx 来将连接切换到可用的服务机器上 如下图
  • Nginx学习(5)—— 基本结构(源码)

    文章目录 Nginx源码学习基本数据结构1 字符串结构 xff1a ngx str t2 类似资源管理的结构 xff1a ngx pool t3 Nginx数组结构 xff1a ngx array t4 哈希表结构 xff1a 1 ngx
  • Nginx学习(6)—— handler模块(自定义handler配置模块的编码编译)

    文章目录 Nginx请求的处理handler简介handler模块的基本结构模块配置结构模块配置指令先看一下 ngx command t 结构 一个模块配置的demo简单模块配置的案例演示 模块上下文结构模块的定义handler模块的基本结