• Feeds

  • 如何写nginx module

    对于一些访问量特别大,业务逻辑也相对简单的Web调用来说,通过一个nginx module来实现是一种比较好的优化方法。实现一个nginx module实际上比较简单。

    1. nginx 配置添加

    ./configure --add-module=/path/to/module1/source

    2. 添加 /path/to/module1/source/config 文件,内容

    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_module.c"
    CORE_LIBS="$CORE_LIBS -lfoo"

    最后一行如果没有使用其他library, 可以去掉

    3. 源代码 /path/to/module1/source/ngx_http_hello_module.c, 主要的业务逻辑在make_http_get_body 中完善。典型的hello world源代码如下

    #include <ngx_config.h>
    #include <ngx_core.h>
    #include <ngx_http.h>
    
    #define OUT_BUFSIZE 256
    
    static char *ngx_http_hello_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    static char *ngx_http_foo_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    
    static ngx_int_t ngx_http_hello_process_init(ngx_cycle_t *cycle);
    static void ngx_http_hello_process_exit(ngx_cycle_t *cycle);
    
    static ngx_int_t make_http_header(ngx_http_request_t *r);
    static ngx_int_t make_http_get_body(ngx_http_request_t *r, char *out_buf);
    
    static char g_foo_settings[64] = {0};
    
    /* Commands */
    static ngx_command_t  ngx_http_hello_commands[] = {
        { ngx_string("ngx_hello_module"),
          NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
          ngx_http_hello_set,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL },
    
        { ngx_string("hello"),
          NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
          ngx_http_foo_set,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL },  
    
          ngx_null_command
    };
    
    static ngx_http_module_t  ngx_http_hello_module_ctx = {
        NULL,                                  /* preconfiguration */
        NULL,                                     /* postconfiguration */
    
        NULL,                                  /* create main configuration */
        NULL,                                  /* init main configuration */
    
        NULL,                                  /* create server configuration */
        NULL,                                  /* merge server configuration */
    
        NULL,                                  /* create location configuration */
        NULL                                   /* merge location configuration */
    };
    
    /* hook */
    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 */
        ngx_http_hello_process_init,             /* init process */
        NULL,                                  /* init thread */
        NULL,                                  /* exit thread */
        ngx_http_hello_process_exit,             /* exit process */
        NULL,                                  /* exit master */
        NGX_MODULE_V1_PADDING
    };
    
    /* setting header for no-cache */
    static ngx_int_t make_http_header(ngx_http_request_t *r){
        ngx_uint_t        i;
        ngx_table_elt_t  *cc, **ccp;
    
        r->headers_out.content_type.len = sizeof("text/html") - 1;
        r->headers_out.content_type.data = (u_char *) "text/html";
        ccp = r->headers_out.cache_control.elts;
        if (ccp == NULL) {
    
            if (ngx_array_init(&r->headers_out.cache_control, r->pool,
                               1, sizeof(ngx_table_elt_t *))
                != NGX_OK)
            {
                return NGX_ERROR;
            }
    
            ccp = ngx_array_push(&r->headers_out.cache_control);
            if (ccp == NULL) {
                return NGX_ERROR;
            }
    
            cc = ngx_list_push(&r->headers_out.headers);
            if (cc == NULL) {
                return NGX_ERROR;
            }
    
            cc->hash = 1;
            cc->key.len = sizeof("Cache-Control") - 1;
            cc->key.data = (u_char *) "Cache-Control";
    
            *ccp = cc;
    
        } else {
            for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
                ccp[i]->hash = 0;
            }
    
            cc = ccp[0];
        }
    
        cc->value.len = sizeof("no-cache") - 1;
        cc->value.data = (u_char *) "no-cache";
    
        return NGX_OK;
    }
    
    static ngx_int_t make_http_get_body(ngx_http_request_t *r, char *out_buf){
        char *qs_start = (char *)r->args_start;
        char *qs_end = (char *)r->uri_end;
        char uri[128] = {0};
        char *id;
    
        if (qs_start == NULL || qs_end == NULL){
            return NGX_HTTP_BAD_REQUEST;
        }
        if ((memcmp(qs_start, "id=", 3) == 0)){
            id = qs_start + 3;
            *qs_end = '\0';
        }else{
            return NGX_HTTP_BAD_REQUEST;
        }
        snprintf(uri, r->uri.len + 1, "%s", r->uri.data);
        sprintf(out_buf, "Author: http://timyang.net/\nconfig=%s\nid=%snuri=%s\nret=%lx\n", g_foo_settings, id, uri, ngx_random());
        return NGX_OK;
    }
    
    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;
    
        /* Http Output Buffer */
        char out_buf[OUT_BUFSIZE] = {0};
    
        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 && rc != NGX_AGAIN) {
            return rc;
        }
    
        /* make http header */
        rc = make_http_header(r);
        if (rc != NGX_OK) {
            return rc;
        }
    
        if (r->method == NGX_HTTP_HEAD) {
            r->headers_out.status = NGX_HTTP_OK;
            return ngx_http_send_header(r);
        } else if (r->method == NGX_HTTP_GET) {
            /* make http get body buffer */
            rc = make_http_get_body(r, out_buf);
            if (rc != NGX_OK) {
                return rc;
            }
        } else {
            return NGX_HTTP_NOT_ALLOWED;
        }
    
        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 = (u_char *)out_buf;
        b->last = (u_char *)out_buf + strlen(out_buf);
        b->memory = 1;
        b->last_buf = 1;
        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = strlen(out_buf);
    
        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);
    }
    
    static char *
    ngx_http_hello_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    
        /* register hanlder */
        clcf->handler = ngx_http_hello_handler;
    
        return NGX_CONF_OK;
    }
    
    static char *
    ngx_http_foo_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_str_t *value = cf->args->elts;
        memcpy(g_foo_settings, value[1].data, value[1].len);
        g_foo_settings[value[1].len] = '�';
    
        return NGX_CONF_OK;
    }
    
    static ngx_int_t
    ngx_http_hello_process_init(ngx_cycle_t *cycle)
    {
        // do some init here
        return NGX_OK;
    }
    
    static void
    ngx_http_hello_process_exit(ngx_cycle_t *cycle)
    {
        return;
    }

    4. 配置文件 nginx.conf

            location /hello {
                ngx_hello_module;
                hello 1234;
            }

    5. 访问 http://localhost/hello?id=1

    也可参考更详细的英文说明:
    Emiller’s Guide To Nginx Module Development

    如想及时阅读Tim Yang的文章,可通过页面右上方扫码订阅最新更新。

    « | »

    12 Comments  »

    1. 试验了你提供的nginx module的例子,编译通过了,但是最后访问http://localhost/hello,报400 Bad Request的错,请教一下什么原因?

    2. anders chen

      编译成功,访问报400 Bad Request

    3. 注意看了源代码,最终访问的地址是:
      http://localhost/hello?id=6

      参见源码123行

    4. guest

      请问213行引号里的字符是啥??

    5. 我们在编写了 15 个产品级的 nginx 开源模块之后,发出的感慨其实是“实现一个nginx module实际上很不简单”,呵呵。

      当然,如果需要编写的 nginx 模块只是进行纯粹的 CPU 计算,确实还是很简单的,但在真实世界中这样的模块并非总是那么有趣 :)

      在 nginx 模块开发中经常面临的挑战就是必须进行非阻塞的 socket 通信 :)

    6. 依葫芦画瓢 能勉强写些模块出来

    7. Amazing! Tһis blog looks exactly lіke mʏ old one!
      It’s on a entirely different subject Ƅut it һas pretty mսch the same pаge layout andd design.
      Superb choice οf colors!

    8. Does your website have a contact page? I’m having trouble locating it but, I’d like
      tto send you an email. I’ve got some ideas for your blog
      you might be interested in hearing. Either way, great blog and I look orward to seeing it grow over time.

    9. Hello Dear, are you in fact visiting this web page daily, if so afterward you
      will definitely get good experience.

    Leave a Comment