如何写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: https://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

Python thread socket server

从网上参考了一些代码,实现了一个Python实现的基于线程的socket server, 用来实现各种服务系统的原型。放在这里供以后参考。

#!/usr/bin/env python
import threading
import SocketServer

users = []

class MyTCPHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        username = None
        while True:
            self.data = self.rfile.readline().strip()
            cur_thread = threading.currentThread()
            print "RECV from ", self.client_address[0]
            cmd = self.data
            if cmd == None or len(cmd) == 0:
                break;
            print cmd
            # business logic here
            try:
                if cmd.startswith('echo'):
                    result = cmd[5:]
                elif cmd.startswith('login'):
                    username = cmd[6:]
                    users.append({username:self.wfile})
                    result = username + ' logined.'
                elif cmd == 'quit':
                    break
                else:
                    result = 'error cmd'
                self.wfile.write(result)
                self.wfile.write('\n')
            except:
                print 'error'
                break
        try:
            if username != None:
                users.remove(username)
        except:
            pass
        print username, ' closed.'

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    server = ThreadedTCPServer((HOST, PORT), MyTCPHandler)
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.setDaemon(True)
    server_thread.start()
    server.serve_forever()

python web.py使用flup lighttpd优化过程

前文用Python实现CRUD功能REST服务中发现,一个普通的web.py页面每秒只能执行数十次requests,经网友Arbow提醒, web.py默认是单线程方式,所以性能提升困难,并推荐了一些高性能的web framework。同时也看到Python资深网友ZoomQuiet的总结 Pythonic Web 应用平台对比,因此觉得有必要换一种更强的web framework。同时也研究了国内著名的豆瓣所采用的Quixote框架。但由于牵涉到更换之后web.py中的REST接口代码实现要调整,所以就暂时搁置了。

后来看到搜狐qiuyingbo在lighttpd 2.0一文中提到sohu mail也是用web.py, 在向qiuyingbo请教之后,了解到web.py通过fastcgi多进程方式也可以实现高性能的访问,决定不再换框架了。

qiuyingbo推荐使用nginx+flup+webpy, 但是最近nginx的mod_wsgi页面中的 http://wiki.codemongers.com/NginxNgxWSGIModule 下载链接始终不能访问,所以就转向 lighttpd/fastcgi 方式,国外著名的reddit也是采用此架构,性能上应该不会有很大的差异。

在安装了lighttpd和配置之后,目前调用一个helloworld.py在本地一普通服务器上可以每秒达到1000次左右,在一个更专业的4核服务器上,执行速度更可4,000次。基本上可以满足运营的要求。

另外赖勇浩在blog我常用的几个第三方 Python 库中提到,使用psyco可以提升Python 40%或更高的性能。在32bit Linux下,测试上面的场景可提高约10%的性能。但由于Psyco不支持64bit架构,所以正式的生产环境就没有安装这个加速功能。

具体配置过程如下,假定lighttpd安装在/data0/lighttpd下:

  • Install Lighttpd, Download lighttpd http://www.lighttpd.net/download/lighttpd-1.4.21.tar.gz

./configure –prefix=/data0/lighttpd –with-openssl; make; make install
cp docs/lighttpd.conf /data0/lighttpd/sbin
openssl req -new -x509 -keyout lighttpd.pem -out lighttpd.pem -days 365 -nodes

  • Install Python 2.6, 具有内置Json支持 http://www.python.org/ftp/python/2.6.1/Python-2.6.1.tgz

./configure; make; make install

  • Install web.py http://webpy.org/static/web.py-0.31.tar.gz

python setup.py install

  • Install flup, http://www.saddi.com/software/flup/dist/flup-1.0.1.tar.gz
  • Install lighttpd + fastcgi with web.py

fastcgi.server = ( “/main.py” =>
(
( “socket” => “/tmp/fastcgi.socket”,
“bin-path” => “/data0/lighttpd/www/python/main.py”,
“max-procs” => 50,
“bin-environment” => (
“REAL_SCRIPT_NAME” => “”
), “check-local” => “disable”
)

)
)

url.rewrite-once = (
“^/favicon.ico$” => “/static/favicon.ico”,
“^/static/(.*)$” => “/static/$1”,
“^/(.*)$” => “/main.py/$1”,
)

也可参看webpy官方的lighttpd fastcgi说明:http://webpy.org/cookbook/fastcgi-lighttpd

  • 启动Lighttpd

cd /data0/lighttpd/sbin; ./lighttpd -f lighttpd.conf