• 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的文章,可通过页面右上方扫码订阅最新更新。

    « | »

    9 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. 依葫芦画瓢 能勉强写些模块出来

    Leave a Comment