Nginx 模块-细节详探

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:

    本文主要基于

    http://www.codinglabs.org/html/intro-of-nginx-module-development.html

    http://www.evanmiller.org/nginx-modules-guide.html#compiling

    的学习些的

     

    nginx模块要负责三种角色

    handler:接收请求+产生Output

    filters:处理hander产生的output

    load-balancer:负载均衡,选择一个后端server发送请求(如果把nginx当做负载均衡服务器的话,这个角色必须实现)

     


    nginx内部流程(非常重要)

    图片讲解:

    clip_image001

    英文讲解

    Client sends HTTP request → Nginx chooses the appropriate handler based on the location config → (if applicable) load-balancer picks a backend server → Handler does its thing and passes each output buffer to the first filter → First filter passes the output to the second filter → second to third → third to fourth → etc. → Final response sent to client

    中文讲解:

    客户端发送http请求 -- nginx根据配置文件conf中的location来确定由哪个handler处理-- handler执行完request返回output给filter--第一个filter处理output -- 第二个filter处理output--- … -- 生成Response

     


    Nginx模块的几个数据结构

    1 Module Configuration Struct(s) 模块配置结构

    2 Module Directives 模块命令结构

    3 The Module Context模块内容

    3.1 create_loc_conf

    3.2 merge_loc_conf

    4 The Module Definition模块整合

    5 Module Installation模块安装

     

     

    1 模块配置结构:

    这个结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。

    main,srv,loc表示这个模块的作用范围是配置文件中的main/server/location三种范围(这个需要记住,后面会经常用到)

    例子:

    1
    2
    3
    4
    5
    typedef struct  {
     
    ngx_str_t ed; //echo模块只有一个参数 比如 echo "hello"
     
    } ngx_http_echo_loc_conf_t; //echo 模块

    2 模块命令结构: 
    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    static  ngx_command_t ngx_http_echo_commands[] = {
     
    { ngx_string( "echo" ), //命令名字
     
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //代表是local配置,带一个参数
     
    ngx_http_echo, //组装模块配置结构
     
    NGX_HTTP_LOC_CONF_OFFSET, //上面的组装模块配置获取完参数后存放到哪里?使用这个和下面的offset参数来进行定位
     
    offsetof(ngx_http_echo_loc_conf_t, ed), //同上
     
    NULL // Finally, post is just a pointer to other crap the module might need while it's reading the configuration. It's often NULL.我也没理解是什么意思。通常情况下设置为NULL
     
    },
     
    ngx_null_command //必须使用ngx_null_command作为commands的结束标记
     
    };

     

    注1:

    ngx_http_echo 是组装模块配置结构的函数指针,有三个参数:

    ngx_conf_t *cf 包含这个命令的所有参数

    ngx_command_t *cmd 执行这个command命令结构的指针

    void *conf 模块订制的配置结构

    这个函数比较不好理解,其功能是把参数传到命令结构体中,并且把合适的值放入到模块配置结构中。我们称之为"setup function"。它会在命令运行的时候被调用。

    nginx已经提供了几个现成的方法了:

    ngx_conf_set_flag_slot

    ngx_conf_set_str_slot

    ngx_conf_set_num_slot

    ngx_conf_set_size_slot

    所以你可以这样定义cmd:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    { ngx_string( "add_after_body" ),
     
    NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
     
    ngx_conf_set_str_slot,
     
    NGX_HTTP_LOC_CONF_OFFSET,
     
    offsetof(ngx_http_addition_conf_t, after_body),
     
    NULL },
    也可以这样:

    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
    static  ngx_command_t ngx_http_echo_commands[] = {
     
    { ngx_string( "echo" ),
     
    NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
     
    ngx_http_echo,
     
    NGX_HTTP_LOC_CONF_OFFSET,
     
    offsetof(ngx_http_echo_loc_conf_t, ed),
     
    NULL },
     
    ngx_null_command
     
    };
     
    static  char  *
     
    ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void  *conf)
     
    {
     
    ngx_http_core_loc_conf_t *clcf;
     
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
     
    clcf->handler = ngx_http_echo_handler; //这里指定了handler,那么就会使用新的handler进行处理
     
    ngx_conf_set_str_slot(cf,cmd,conf); //这里还是使用系统的函数
     
    return  NGX_CONF_OK;
     
    }

    3 模块内容

    static ngx_http_module_t ngx_http_circle_gif_module_ctx = {

    NULL, /* preconfiguration */

    NULL, /* postconfiguration 这里是放置filter的地方,在filter章节会说*/

    NULL, /* create main configuration */

    NULL, /* init main configuration */

    NULL, /* create server configuration */

    NULL, /* merge server configuration */

    ngx_http_circle_gif_create_loc_conf, /* create location configuration */

    ngx_http_circle_gif_merge_loc_conf /* merge location configuration */

    };

    模块的内容ngx_http_<module name>_module_ctx是为了定义各种钩子函数,就是nginx在各个不同的时期将会运行的函数。

    一般的location只需要配置create location configuration(在创建location配置的时候运行)和merge location configuration(和server config如何合并,一般包含如果配置有错误的话应该抛出异常)

    这两个函数的例子:(来自https://github.com/evanmiller/nginx_circle_gif/

    static void *

    ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)

    {

    ngx_http_circle_gif_loc_conf_t *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));

    if (conf == NULL) {

    return NGX_CONF_ERROR;

    }

    conf->min_radius = NGX_CONF_UNSET_UINT; //对conf中的每个参数进行配置,min_redius和max_redius是nginx_circle_gif模块的配置结构的字段

    conf->max_radius = NGX_CONF_UNSET_UINT;

    return conf;

    }

    static char *

    ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)

    {

    ngx_http_circle_gif_loc_conf_t *prev = parent; //server的loc配置

    ngx_http_circle_gif_loc_conf_t *conf = child; // 自己的loca配置

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);

    ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);

    if (conf->min_radius < 1) {

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

    "min_radius must be equal or more than 1");

    return NGX_CONF_ERROR; //这里负责抛出错误

    }

    if (conf->max_radius < conf->min_radius) {

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

    "max_radius must be equal or more than min_radius");

    return NGX_CONF_ERROR; //这里负责抛出错误

    }

    return NGX_CONF_OK;

    }

    注1 : ngx_conf_merge_uint_value是nginx core中自带的函数

    ngx_conf_merge_<data type>_value

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);的意思是:

    如果设置了conf->min_redius的话使用conf->min_redius

    如果没有设置conf->min_redius的话使用 prev->min_radius

    如果两个都没有设置的话使用10

    更多函数请看 core/ngx_conf_file.h

    4 模块整合

    ngx_module_t ngx_http_<module name>_module = {

    NGX_MODULE_V1,

    &ngx_http_<module name>_module_ctx, /* module context 模块内容 */

    ngx_http_<module name>_commands, /* module directives 模块命令*/

    NGX_HTTP_MODULE, /* module type 模块类型,HTTP模块,或者HTTPS*/

    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

    };

    5 模块安装

    模块安装文件的编写依赖于这个模块是handler,filter还是load-balancer的工作角色

    下面开始是Handler,filter,load-balancer的编写和安装

    1 Handler安装

    还记得在模块命令的时候有设置handler的语句吗?

    clcf->handler = ngx_http_echo_handler;

    这个语句就是handler的安装

    2 Handler编写

    Handler的执行有四部:

    读入模块配置

    处理功能业务

    产生HTTP header

    产生HTTP body

    读入模块配置

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static  ngx_int_t
     
    ngx_http_circle_gif_handler(ngx_http_request_t *r)
     
    {
     
    ngx_http_circle_gif_loc_conf_t *circle_gif_config;
     
    circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
     
    ...

    就是使用nginx已经有的函数ngx_http_get_module_loc_conf,第一个参数是当前请求,第二个参数是前面写好的模块

    处理功能业务

    这个部分是我们要模块处理的实际部分

    用需求举例:

    这个模块有个命令是 getRedisInfo 192.168.0.1 //获取redis的信息

    那么这个功能业务就是(伪代码):

    case cmd->opcode

    {

    "getRedisInfo" :

    获取redis 的信息

    }

    这里应该把所有这个模块设置的命令的业务逻辑都写好

    产生HTTP Header

    例子:

    r->headers_out.status = NGX_HTTP_OK;

    r->headers_out.content_length_n = 100;

    r->headers_out.content_type.len = sizeof("image/gif") - 1;

    r->headers_out.content_type.data = (u_char *) "image/gif";

    ngx_http_send_header(r);

    产生HTTP Body

    这个部分是最重要的一步

    借用codingLabs的图讲解一下nginx的IO

    clip_image002

    handler是可以一次产生出一个输出,也可以产生出多个输出使用ngx_chain_t的链表来进行连接

    struct ngx_chain_s {

    ngx_buf_t *buf;

    ngx_chain_t *next;

    };

    buf中有pos和last来代表out数据在内存中的位置,next是代表下一个ngx_chain_t

    下面来说一下只有一个ngx_chain_t的设置

    1 申明

    ngx_buf_t *b;

    ngx_chain_t out

    2 设置buffer

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

    if (b == NULL) {

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,

    "Failed to allocate response buffer.");

    return NGX_HTTP_INTERNAL_SERVER_ERROR;

    }

    b->pos = some_bytes; /* first position in memory of the data */

    b->last = some_bytes + some_bytes_length; /* last position */

    b->memory = 1; /* content is in read-only memory */

    /* (i.e., filters should copy it rather than rewrite in place) */

    b->last_buf = 1; /* there will be no more buffers in the request */

    3 模块加入链表

    out.buf = b;

    out.next = NULL; //如果有下一个链表可以放到这里

    4 返回

    return ngx_http_output_filter(r, &out);

     

     

    2 Filter的编写和安装

    Filter作为过滤器又可以细分为两个过滤器: Header filters 和 body filters

     

    Filter的安装

    filter是在模块内容设置的时候加上的

    例子:

    static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {

    NULL, /* preconfiguration */

    ngx_http_chunked_filter_init, /* postconfiguration */

    ...

    };

    static ngx_int_t

    ngx_http_chunked_filter_init(ngx_conf_t *cf)

    {

    ngx_http_next_header_filter = ngx_http_top_header_filter;

    ngx_http_top_header_filter = ngx_http_chunked_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;

    ngx_http_top_body_filter = ngx_http_chunked_body_filter;

    return NGX_OK;

    }

    注1: ngx_http_top_hreader_filter是什么意思呢?

    当handler生成了response的时候,它调用了两个方法:ngx_http_output_filter和ngx_http_send_header

    ngx_http_output_filter会调用ngx_http_top_body_filter

    ngx_http_send_header会调用ngx_top_header_filter

    Filter的编写

    Header filters

    分为三个部分:

    是否操作这个handler的response

    操作response

    调用下一个filter

    例子

    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
    static
     
    ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
     
    {
     
    time_t if_modified_since;
     
    if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
     
    r->headers_in.if_modified_since->value.len);
     
    /* step 1: decide whether to operate */
     
    if  (if_modified_since != NGX_ERROR &&
     
    if_modified_since == r->headers_out.last_modified_time) {
     
    /* step 2: operate on the header */
     
    r->headers_out.status = NGX_HTTP_NOT_MODIFIED; //返回304
     
    r->headers_out.content_type.len = 0; //长度设置为0
     
    ngx_http_clear_content_length(r); //清空
     
    ngx_http_clear_accept_ranges(r); //清空
     
    }
     
    /* step 3: call the next filter */
     
    return  ngx_http_next_header_filter(r);
     
    }

    Body filters

    假设有个需求:在每个request后面插入"<l!-- Served by Nginx -->"

    1 要找出最后chain的最后一个buf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ngx_chain_t *chain_link;
     
    int  chain_contains_last_buffer = 0;
     
    for  ( chain_link = in ; chain_link != NULL; chain_link = chain_link->next ) {
     
    if  (chain_link->buf->last_buf)
     
    chain_contains_last_buffer = 1;
     
    }

    2 创建一个新的buf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ngx_buf_t *b;
     
    b = ngx_calloc_buf(r->pool);
     
    if  (b == NULL) {
     
    return  NGX_ERROR;
     
    }
    3 放数据在新buf上

    1
    2
    3
    b->pos = (u_char *) "<!-- Served by Nginx -->" ;
     
    b->last = b->pos + sizeof ( "<!-- Served by Nginx -->" ) - 1;
    4 把新buf放入一个新chain_t

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ngx_chain_t *added_link;
     
    added_link = ngx_alloc_chain_link(r->pool);
     
    if  (added_link == NULL)
     
    return  NGX_ERROR;
     
    added_link->buf = b;
     
    added_link->next = NULL;
    5 把新的chain链接到原来的chain_link中

    chain_link->next = added_link;

    6 重新设置last_buf

    chain_link->buf->last_buf = 0;

    added_link->buf->last_buf = 1;

    7 传给下一个filter

    return ngx_http_next_body_filter(r, in);

     

    最后一点是如何写和编译nginx模块

    必须写两个文件configngx_http_<your module>_module.c

    其中config会被./configure包含

    ngx_addon_name=ngx_http_<your module>_module

    HTTP_MODULES="$HTTP_MODULES ngx_http_<your module>_module"

    NGX_ADDON_SRCS=" N G X A D D O N S R C S ngx_addon_dir/ngx_http_<your module>_module.c"

    几乎都是填空

    ngx_http_<your module>_module.c文件就是你的所有模块代码

     

    编译nginx:

    ./configure --add-module=path/to/your/new/module/directory #这里是config放置的地方

    本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/archive/2012/04/01/2428385.html,如需转载请自行联系原作者

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
负载均衡 应用服务中间件 API
Nginx:location配置模块的用法(一)
Nginx:location配置模块的用法(一)
555 2
|
2月前
|
应用服务中间件 nginx C++
nginx的cgi模块
nginx的cgi模块
43 0
|
4月前
|
缓存 应用服务中间件 nginx
安装nginx-http-flv-module模块
本文介绍如何为Nginx安装`nginx-http-flv-module`模块。此模块基于`nginx-rtmp-module`二次开发,不仅具备原模块的所有功能,还支持HTTP-FLV播放、GOP缓存、虚拟主机等功能。安装步骤包括:确认Nginx版本、下载相应版本的Nginx与模块源码、重新编译Nginx并加入新模块、验证模块安装成功。特别注意,此模块已包含`nginx-rtmp-module`功能,无需重复编译安装。
221 1
|
4月前
|
负载均衡 应用服务中间件 Linux
在Linux中,常用的 Nginx 模块有哪些,常来做什么?
在Linux中,常用的 Nginx 模块有哪些,常来做什么?
|
4月前
|
缓存 前端开发 应用服务中间件
Nginx:location配置模块的用法(二)
Nginx:location配置模块的用法(二)
243 2
|
5月前
|
应用服务中间件 Linux nginx
FFmpeg开发笔记(四十)Nginx集成rtmp模块实现RTMP推拉流
《FFmpeg开发实战》书中介绍了如何使用FFmpeg向网络推流,简单流媒体服务器MediaMTX不适用于复杂业务。nginx-rtmp是Nginx的RTMP模块,提供基本流媒体服务。要在Linux上集成rtmp,需从官方下载nginx和nginx-rtmp-module源码,解压后在nginx目录配置并添加rtmp模块,编译安装。配置nginx.conf启用RTMP服务,监听1935端口。使用ffmpeg推流测试,如能通过VLC播放,表明nginx-rtmp运行正常。更多详情见书本。
141 0
FFmpeg开发笔记(四十)Nginx集成rtmp模块实现RTMP推拉流
|
4月前
|
Ubuntu 前端开发 JavaScript
如何在 Ubuntu 14.04 上为 Nginx 添加 gzip 模块
如何在 Ubuntu 14.04 上为 Nginx 添加 gzip 模块
36 0
|
7月前
|
Ubuntu 应用服务中间件 nginx
ubuntu编译安装nginx及安装nginx_upstream_check_module模块
以上是编译安装Nginx和安装 `nginx_upstream_check_module`模块的基本步骤。根据你的需求和环境,你可能需要进一步配置Nginx以满足特定的要求。
313 3
|
7月前
|
应用服务中间件 数据库 nginx
nginx 第三方模块 与变量
nginx 第三方模块 与变量
|
7月前
|
应用服务中间件 nginx Python
nginx-upload-module模块实现文件断点续传_nginx upload module 断点续传 进度(1)
nginx-upload-module模块实现文件断点续传_nginx upload module 断点续传 进度(1)