美文网首页理解Nginx源码
开发Nginx Stream模块(echo-module)

开发Nginx Stream模块(echo-module)

作者: seawish | 来源:发表于2018-12-12 21:49 被阅读52次

    ngx_stream_echo_module

    使用echo指令输出字符串。
    repo地址:https://github.com/seawish/stream-echo-nginx-module
    该repo是从openresty处fork下来的。

    Nginx版本

    • nginx-1.10.3

    源码方式安装nginx

    参照博文Nginx源码编译安装教程

    定义模块Context

    1. 定义ngx_http_module_t类型的结构体变量

    /**
     * 定义ngx_stream_module_t类型的结构体变量   命名规则为ngx_http_[module-name]_module_ctx,这个结构主要用于定义各个Hook函数
     *
     * nginx1.11.02版本之前可以看到一共有6个Hook注入点,分别会在不同时刻被Nginx调用,由于我们的模块仅仅用于server域,这里将不需要的注入点设为NULL即可。nginx1.11.02+的版本增加了preconfiguration注入点。
     *
     * ngx_stream_echo_create_srv_conf  ngx_stream_echo_merge_srv_conf 这两个函数会被Nginx自动调用。注意这里的命名规则:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf。
     */
    static ngx_stream_module_t  ngx_stream_echo_module_ctx = {
    #if (nginx_version >= 1011002)
        NULL,                                  /* preconfiguration */
    #endif
        NULL,                                  /* postconfiguration */
    
        NULL,                                  /* create main configuration */
        NULL,                                  /* init main configuration */
    
        ngx_stream_echo_create_srv_conf,       /* create server configuration */
        ngx_stream_echo_merge_srv_conf         /* merge server configuration */
    };
    
    

    2. 初始化一个配置结构体

    typedef struct {
        ngx_array_t      cmds;  /* of elements of type ngx_stream_echo_cmd_t */
    
        ngx_msec_t       send_timeout;
        ngx_msec_t       read_timeout;
    
        ngx_uint_t       log_level;
    
        ngx_uint_t       lingering_close;
        ngx_msec_t       lingering_time;
        ngx_msec_t       lingering_timeout;
    
        size_t           read_buffer_size;
        unsigned         needs_buffer_in;   /* :1 */
    } ngx_stream_echo_srv_conf_t;
    

    3. 实现了配置的继承

    将其parent block的配置信息合并到此结构体。 Nginx 为不同的数据类型提供了merge 函数,可查阅 core/ngx_conf_file.h;merge_loc_conf 函数定义如下:

    static char *
    ngx_stream_echo_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
    {
        ngx_stream_echo_srv_conf_t *prev = parent;
        ngx_stream_echo_srv_conf_t *conf = child;
    
    #if 0
        if (conf->cmds.nelts == 0 && prev->cmds.nelts > 0) {
            /* assuming that these arrays are read-only afterwards */
            ngx_memcpy(&conf->cmds, &prev->cmds, sizeof(ngx_array_t));
        }
    #endif
    
        ngx_conf_merge_str_value(conf->ed, prev->ed, '"');
    
        return NGX_CONF_OK;
    }
    

    定义echo模块的指令和参数转化函数

    定义echo模块的指令

    /**
     * 定义echo模块的指令。
     * ngx_command_t在https://github.com/nginx/nginx/blob/master/src/core/ngx_conf_file.h定义。
     * 
     */
    static ngx_command_t  ngx_stream_echo_commands[] = {
    
        { ngx_string("echo"),     /* echo命令 */
          NGX_STREAM_SRV_CONF|NGX_CONF_ANY,
          ngx_stream_echo_echo,
          NGX_STREAM_SRV_CONF_OFFSET,
          0,
          NULL },
    
          ngx_null_command /* 空命令 */
    };
    

    参数转化函数

    ngx_stream_echo_helper

    static ngx_stream_echo_cmd_t *
    ngx_stream_echo_helper(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
        ngx_stream_echo_opcode_t opcode, ngx_array_t *args, ngx_array_t *opts)
    {
        ngx_stream_echo_srv_conf_t  *escf = conf;
    
        ngx_stream_echo_cmd_t       *echo_cmd;
        ngx_stream_core_srv_conf_t  *cscf;
    
        if (escf->cmds.nelts == 0) {
            cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);
            cscf->handler = ngx_stream_echo_handler;
        }
    
        echo_cmd = ngx_array_push(&escf->cmds);
        if (echo_cmd == NULL) {
            return NULL;
        }
    
        echo_cmd->opcode = opcode;
    
        if (args != NULL && opts != NULL) {
    
            if (ngx_array_init(args, cf->temp_pool, cf->args->nelts - 1,
                               sizeof(ngx_str_t))
                == NGX_ERROR)
            {
                return NULL;
            }
    
            if (ngx_array_init(opts, cf->temp_pool, 1, sizeof(ngx_str_t))
                == NGX_ERROR)
            {
                return NULL;
            }
    
            if (ngx_stream_echo_eval_args(cf->args, 1, args, opts) != NGX_OK) {
                return NULL;
            }
        }
    
        return echo_cmd;
    }
    
    

    ngx_stream_echo_echo

    static char *
    ngx_stream_echo_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        u_char          *p;
        size_t           size;
        unsigned         nl;  /* controls whether to append a newline char */
        ngx_str_t       *opt, *arg;
        ngx_uint_t       i;
        ngx_array_t      opts, args;
    
        ngx_stream_echo_cmd_t     *echo_cmd;
    
        echo_cmd = ngx_stream_echo_helper(cf, cmd, conf,
                                          NGX_STREAM_ECHO_OPCODE_ECHO,
                                          &args, &opts);
        if (echo_cmd == NULL) {
            return NGX_CONF_ERROR;
        }
    
        /* handle options */
    
        nl = 1;
        opt = opts.elts;
    
        for (i = 0; i < opts.nelts; i++) {
    
            if (opt[i].len == 1 && opt[i].data[0] == 'n') {
                nl = 0;
                continue;
            }
    
            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                          "stream echo sees unknown option \"-%*s\" "
                          "in \"echo\"", opt[i].len, opt[i].data);
    
            return NGX_CONF_ERROR;
        }
    
        /* prepare the data buffer to be sent.
         * TODO we could merge the data buffers of adjacent "echo" commands
         * further though it might not worth the trouble. oh well.
         */
    
        /* step 1: pre-calculate the total size of the data buffer actually
         * needed and allocate the buffer. */
    
        arg = args.elts;
    
        for (size = 0, i = 0; i < args.nelts; i++) {
    
            if (i > 0) {
                /* preserve a byte for prepending a space char */
                size++;
            }
    
            size += arg[i].len;
        }
    
        if (nl) {
            /* preserve a byte for the trailing newline char */
            size++;
        }
    
        if (size == 0) {
    
            echo_cmd->data.buffer.data = NULL;
            echo_cmd->data.buffer.len = 0;
    
            return NGX_CONF_OK;
        }
    
        p = ngx_palloc(cf->pool, size);
        if (p == NULL) {
            return NGX_CONF_ERROR;
        }
    
        echo_cmd->data.buffer.data = p;
        echo_cmd->data.buffer.len = size;
    
        /* step 2: fill in the buffer with actual data */
    
        for (i = 0; i < args.nelts; i++) {
    
            if (i > 0) {
                /* prepending a space char */
                *p++ = (u_char) ' ';
            }
    
            p = ngx_copy(p, arg[i].data, arg[i].len);
        }
    
        if (nl) {
            /* preserve a byte for the trailing newline char */
            *p++ = LF;
        }
    
        if (p - echo_cmd->data.buffer.data != (off_t) size) {
            /* just as an insurance */
    
            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                          "stream echo internal buffer error: %O != %uz",
                          p - echo_cmd->data.buffer.data, size);
    
            return NGX_CONF_ERROR;
        }
    
        return NGX_CONF_OK;
    }
    

    编写Handler 模块

    在ngx_stream_echo_helper方法中修改核心模块配置(也就是当前service), 将其handler替换为我们自己定义的ngx_stream_echo_handler。

    static void
    ngx_stream_echo_handler(ngx_stream_session_t *s)
    {
        ngx_connection_t            *c;
        ngx_stream_echo_ctx_t       *ctx;
        ngx_stream_echo_srv_conf_t  *escf;
    
        escf = ngx_stream_get_module_srv_conf(s, ngx_stream_echo_module);
        if (escf->cmds.nelts == 0) {
            /* cannot really happen */
            ngx_stream_echo_finalize(s, NGX_DECLINED);
            return;
        }
    
        c = s->connection;
    
        c->write->handler = ngx_stream_echo_writer;
        c->read->handler = ngx_stream_echo_block_reading;
    
        ctx = ngx_stream_echo_create_ctx(s);
        if (ctx == NULL) {
            ngx_stream_echo_finalize(s, NGX_ERROR);
            return;
        }
    
        ngx_stream_set_ctx(s, ctx, ngx_stream_echo_module);
    
        ngx_stream_echo_resume_execution(s);
    }
    
    

    组合Nginx Module

    /**
     * 组合Nginx Module
     *
     * 上面完成了Nginx模块各种组件的开发,下面就是将这些组合起来了。一个Nginx模块被定义为一个ngx_module_t结构体https://github.com/nginx/nginx/blob/master/src/core/ngx_module.h,这个结构体的字段很多,不过开头和结尾若干字段一般可以通过Nginx内置的宏去填充
     *
     * 开头和结尾分别用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若干字段,就不去深究了。
     * 这里主要需要填入的信息从上到下以依次为context、指令数组、模块类型以及若干特定事件的回调处理函数(不需要可以置为NULL),
     * 其中内容还是比较好理解的,注意我们的echo是一个STREAM模块,所以这里类型是NGX_STREAM_MODULE,其它可用类型还有NGX_EVENT_MODULE(事件处理模块)和NGX_MAIL_MODULE(邮件模块),NGX_STREAM_MODULE等。
     *
     */
    ngx_module_t  ngx_stream_echo_module = {
        NGX_MODULE_V1,
        &ngx_stream_echo_module_ctx,           /* module context */
        ngx_stream_echo_commands,              /* module directives */
        NGX_STREAM_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
    };
    
    
    

    编写config文件

    ngx_addon_name=ngx_stream_echo_module
    STREAM_MODULES="$STREAM_MODULES ngx_stream_echo_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_stream_echo_module.c"
    NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h"
    

    编译安装echo模块

    ./configure --prefix=/opt/nginx \
        --with-stream --with-debug \
        --add-module=/path/to/stream-echo-nginx-module
    

    例如:

    ./configure --prefix=/opt/nginx \
        --with-stream --with-debug \
        --add-module=/Users/zsb/Documents/Workspaces/nginx-dev/nginx-moudle/stream-echo-nginx-module
    

    指令

    echo

    syntax: echo [options] <string>...

    default: no

    context: server

    phase: content

    Sends string arguments joined by spaces, along with a trailing newline, out to the client.

    For example,

    stream {
        server {
            listen 1234;
    
            echo "Hello, world!";
            echo foo bar baz;
        }
    }
    

    Then connecting to the server port 1234 will immediately receive the response data

    Hello, world!
    foo bar baz
    

    参考文献


    本文作者: seawish
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

    相关文章

      网友评论

        本文标题:开发Nginx Stream模块(echo-module)

        本文链接:https://www.haomeiwen.com/subject/gzhmhqtx.html