开发Nginx Stream模块(echo-module)

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 许可协议。转载请注明出处!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容