• Nginx模块开发之http过滤器filter


    什么是过滤模块

    在这里插入图片描述
    Nignx是一个代理服务器, 他前端被客户端请求,后端连接服务器。这里涉及的数据处理大概有

    1. 客户端请求数据,Nginx直接返回(handler 模块)
    2. 客户端请求数据,Nginx转发给服务器(upstream 模块)
    3. 服务器返回数据,Nginx转发给客户端(filter 模块)

    Nginx相关数据结构介绍

    ngx_module_t的数据结构

    struct ngx_module_s {
        ngx_uint_t            ctx_index; //是哪个进程
        ngx_uint_t            index;	//进程id
    
        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;// nginx模块类型
    
        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;
    };
    typedef struct ngx_module_s ngx_module_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    ngx_http_module_t数据结构

    typedef struct {
        void        **main_conf;
        void        **srv_conf;
        void        **loc_conf;
    } ngx_http_conf_ctx_t;
    
    
    typedef struct {
        ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);	// 解析配置文件之前
        ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);	// 解析配置文件完成之后
    // **_main_ **解析配置文件中http关键字的内部
        void       *(*create_main_conf)(ngx_conf_t *cf);	
        char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
    // **_srv_ **解析配置文件中server关键字的内部
        void       *(*create_srv_conf)(ngx_conf_t *cf);	
        char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
    // **_loc_ **解析配置文件中location关键字的内部
        void       *(*create_loc_conf)(ngx_conf_t *cf);
        char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
    } ngx_http_module_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ngx_command_s数据结构

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    相关宏定义

    #define NGX_MODULE_V1                                                         \
        NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,                           \
        NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE
    
    #define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0
    
    
    #define NGX_HTTP_MODULE           0x50545448   /* "HTTP" 模块*/
    
    /* 以下宏定义为了去确定该项配置属于哪个类目下 
    比如service 
    比如location
    */
    #define NGX_HTTP_MAIN_CONF        0x02000000
    #define NGX_HTTP_SRV_CONF         0x04000000
    #define NGX_HTTP_LOC_CONF         0x08000000
    #define NGX_HTTP_UPS_CONF         0x10000000
    #define NGX_HTTP_SIF_CONF         0x20000000
    #define NGX_HTTP_LIF_CONF         0x40000000
    #define NGX_HTTP_LMT_CONF         0x80000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    filter(过滤器)实现

    Nginx模块开发流程

    (1)定义一个模块名,ngx_module_t,选择好http模块NGX_HTTP_MODULE。
    (2)定义cmd命令,有多少条cmd写多少条cmd,ngx_command_t。
    (3)定义用来解析http block,ngx_http_module_t。
    (4)执行过程实现添加模块。

    Nginx 模块执行

    (1)初始化。当进程启动的时候进行的模块初始化。
    (2)解析conf文件。解析conf文件中模块的相关命令和设置。
    (3)Nginx启动之后,有命令或请求到来时,处理请求的流程。

    开发模块时,需要实现的主要是这三个流程的功能。

    具体实现流程

    create_loc_conf

    内存池中分配一片kong空间,用以存储配置文件中指令对应的值

    // void       *(*create_loc_conf)(ngx_conf_t *cf);
    // 解析conf文件location关键字之前的动作
    void  *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf)
    {
    	ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t));
    	if (conf == NULL)
    		return NULL;
    
    	conf->enable = NGX_CONF_UNSET;
    	
    	return conf;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    merge_loc_conf

    char  *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
    {
    
    	ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent;
    	//如果prefix中是on,那么next->enable的值就为1,这个是在ngx_conf_set_flag_slot
    	//函数中设置的,即可以理解为将配置
    	//文件中的on或者off转换为nginx内存中的1或者0
    	ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child;
    
    	ngx_conf_merge_value(next->enable, prev->enable, 0);
    
    	return NGX_CONF_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其中 ngx_conf_merge_value

    #define ngx_conf_merge_value(conf, prev, default)                            \
        if (conf == NGX_CONF_UNSET) {                                            \
            conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
    
    • 1
    • 2
    • 3

    postconfiguration

    1. 解析完毕conf文件后执行该指令,设置运行时候的回调函数
    2. 使用头插法,将header_filter 与 body_filter插入filter队列的头部
    // ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
    // 解析完配置文件之后的动作,也就是解析完http关键字模块之后
    ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf)
    {
    	// 模块的初始化
    	// http {  }
    
    	// O->O->O->O
    	// 多个模块的头插法,取出最前面的模块
    	//top指向第一个,next指向第二个
    	ngx_http_next_header_filter = ngx_http_top_header_filter;
    	ngx_http_top_header_filter = ngx_http_fly_header_filter;
    
    	ngx_http_next_body_filter = ngx_http_top_body_filter;
    	ngx_http_top_body_filter = ngx_http_fly_body_filter;
    
    	return NGX_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    修改header信息

    这里仅仅修改要回发的内容长度,由于修改了body内容,那么header中的length字段自然要做出相应的修改

    static ngx_str_t prefix = ngx_string("

    FLY.

    "
    ); ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) { if (r->headers_out.status != NGX_HTTP_OK) { // 不正常返回,则进行next return ngx_http_next_header_filter(r); } //r->headers_out.content_type.len == sizeof("text/html") r->headers_out.content_length_n += prefix.len; return ngx_http_next_header_filter(r); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    修改body信息

    ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {
    
    	/*
    	* 关于ngx_chain_t:
    	* 在nginx中,有一个数据链,存放要发送的数据。
    	* O->O->O->O
    	* 每次send的是ngx_chain_t中的一个ngx_buf_t
    	*/
    
    	// 添加一个chain buffer
    	ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);
    	b->start = b->pos = prefix.data;
    	b->last = b->pos + prefix.len;
    
    	ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);
    	c1->buf = b;
    	c1->next = chain;
    
    	return ngx_http_next_body_filter(r, c1);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    示例代码

    这里主要实现了在返回的网页中添加一个内容。里面在重点地方附上了详细注释。
    ngx_http_filter_module.c

    
    #include 
    #include 
    #include 
    
    typedef struct {
    	ngx_flag_t enable;
    }ngx_http_filter_conf_t;
    
    static ngx_str_t prefix = ngx_string("

    FLY.

    "
    ); static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; // ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r) // 添加头,header ngx_int_t ngx_http_fly_header_filter(ngx_http_request_t *r) { if (r->headers_out.status != NGX_HTTP_OK) { // 不正常返回,则进行next return ngx_http_next_header_filter(r); } //r->headers_out.content_type.len == sizeof("text/html") r->headers_out.content_length_n += prefix.len; return ngx_http_next_header_filter(r); } // ngx_int_t (*ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *chain) // 添加内容,body ngx_int_t ngx_http_fly_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) { /* * 关于ngx_chain_t: * 在nginx中,有一个数据链,存放要发送的数据。 * O->O->O->O * 每次send的是ngx_chain_t中的一个ngx_buf_t */ // 添加一个chain buffer ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len); b->start = b->pos = prefix.data; b->last = b->pos + prefix.len; ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool); c1->buf = b; c1->next = chain; return ngx_http_next_body_filter(r, c1); } // ngx_int_t (*postconfiguration)(ngx_conf_t *cf); // 解析完配置文件之后的动作,也就是解析完http关键字模块之后 ngx_int_t ngx_http_fly_filter_init(ngx_conf_t *cf) { // 模块的初始化 // http { } // O->O->O->O // 多个模块的头插法,取出最前面的模块 ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_fly_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_fly_body_filter; return NGX_OK; } // void *(*create_loc_conf)(ngx_conf_t *cf); // 解析conf文件location关键字之前的动作 void *ngx_http_fly_filter_create_loc_conf(ngx_conf_t *cf) { ngx_http_filter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_filter_conf_t)); if (conf == NULL) return NULL; conf->enable = NGX_CONF_UNSET; return conf; } // char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); // 解析完配置文件location关键字之后的动作 // 模块可能在多个地方定义,这个函数合并所有的值一起使用 char *ngx_http_fly_filter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_filter_conf_t *prev = (ngx_http_filter_conf_t*)parent; ngx_http_filter_conf_t *next = (ngx_http_filter_conf_t*)child; // 合并enable的值 ngx_conf_merge_value(next->enable, prev->enable, 0); return NGX_CONF_OK; } /* 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; }; */ /* // conf文件命令解析 char *ngx_http_fly_filter_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; // 对应 ngx_http_fly_filter_create_loc_conf函数的conf->enable = NGX_CONF_UNSET; ngx_flag_t *flag = (p + cmd->offset); return NGX_CONF_OK; } */ // conf文件中的每一行都是一个指令指令 ngx_command_t ngx_http_fly_filter_module_cmd[] = { { //命令名称,比如listen,定义了就可以在conf文件中使用,注意不能和其他的起冲突 ngx_string("predix"), // 指示name命令放的位置在哪里以及可以带多少个参数,NGX_CONF_FLAGE表示开关标志 // predix on/off NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, // 命令解析,可以使用nginx内部的也可以自己实现 ngx_conf_set_flag_slot,//ngx_http_fly_filter_set_slot, NGX_HTTP_LOC_CONF_OFFSET, // offsetof获取enable在结构体中的偏移位置 offsetof(ngx_http_filter_conf_t,enable), NULL, }, ngx_null_command }; // 用来解析对应的conf文件,其实表示的就是模块定义中的上下文 static ngx_http_module_t ngx_http_fly_filter_module_ctx = { NULL, ngx_http_fly_filter_init, NULL, NULL, NULL, NULL, ngx_http_fly_filter_create_loc_conf, ngx_http_fly_filter_merge_loc_conf }; // 模块定义 ngx_module_t ngx_http_fly_filter_module = { NGX_MODULE_V1, &ngx_http_fly_filter_module_ctx, ngx_http_fly_filter_module_cmd, // http的ascii值,指示是什么模块 NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING // 填充 };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185

    编写config文件

    创建:

    touch config
    
    • 1

    内容:

    ngx_addon_name=ngx_http_fly_filter_module
    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_fly_filter_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_filter_module.c"
    
    • 1
    • 2
    • 3

    包含三部分信息,一个是模块的名称这里名称需要与代码中的定义的模块名称ngx_module_t一致;第二部分是指定模块的类型和名称,这里定义的是一个filter模块;最后是指定模块源文件路径。
    注意,config文件要和模块的代码在相同目录。

    编译模块到Nginx源码中

    (1)配置中添加模块:

    ./configure --prefix=/usr/local/nginx --with-http_realip_module 
    --with-http_addition_module --with-http_gzip_static_module 
    --with-http_secure_link_module --with-http_stub_status_module 
    --with-stream --with-pcre=/home/fly/workspace/pcre-8.41 
    --with-zlib=/home/fly/workspace/zlib-1.2.11 
    --with-openssl=/home/fly/workspace/openssl-1.1.0g 
    --add-module=/mnt/hgfs/sourcecode_learning/ngx_http_filter_module
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意模块路径要正确。出现如下表示成功:

    configuring additional modules
    adding module in /mnt/hgfs/sourcecode_learning/ngx_http_filter_module
     + ngx_http_fly_filter_module was configured
    creating objs/Makefile
    
    • 1
    • 2
    • 3
    • 4

    (2)查看是否添加模块到动态代码中:

    vim objs/ngx_modules.c
    
    • 1

    (3)编译:

    make
    sudo make install
    
    • 1
    • 2

    执行效果

    编译安装完成后,在conf文件中添加模块的开关(predix on):

    worker_processes 4;
    
    events {
    	worker_connections 1024;
    }
    
    http {
    
    	upstream backend {
    		server 192.168.7.146:8889;
    		server 192.168.7.146:8890;
    	}
    
    	server {
    		listen 8888;
    		location / {
    			proxy_pass http://backend;
    		}
    	}
    	server {
                    listen 8889;
            }
    	server {
                    listen 8890;
    		predix on;
            }
    	server {
                    listen 8891;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    执行Nginx:

    sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/fly.conf 
    
    • 1

    在网页输入IP和端口,执行效果如下:
    在这里插入图片描述
    可以看到,返回的网页中多出来添加的内容(FLY.)。

    总结

    1. Nginx中http模块非常多,每个模块都会有ngx_http_module_t,为了防止解析过程出现冲突,Nginx编译的时候会把所有的模块都集中起来,组织到/obj/ngx_module.c(以数组的方式)。
    2. 在编译模块时,需要编写config文件,这个文件最好不要使用笔记本编辑(notepad),容易造成编码方式的错误。
    3. 网页中常见的广告(其实里面存储了图片、链接、名称信息)等等其实就是通过nginx过滤器模块去实现的。
  • 相关阅读:
    Java“牵手”淘宝商品详情数据,淘宝商品详情接口,淘宝API接口申请指南介绍
    新版pycharm(2023.2.2)修改字体大小
    不要再稀里糊涂的使用反射了,四万字带你搞定Java反射(JDK8)
    【uni-app系列】uni-ui扩展组件和uViewUI的安装使用
    【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷E
    软件要想做的好,测试必定少不了
    JS 如何实现继承
    SpringBoot源码解读与原理分析(三十二)SpringBoot整合JDBC(一)JDBC组件的自动装配
    嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第六天-ARM Linux编程之高级驱动基础 (物联技术666)
    S7-1200PLC与昆仑通态触摸屏通讯
  • 原文地址:https://blog.csdn.net/qq_29750559/article/details/134553271