nginx模块开发入门(六)-3.1 Anatomy of a Handler (Non-proxying)

ccschan 2013-09-05

3.Handlers

接下来我们把模块的细节放到显微镜下面来看,它们到底怎么运行的。

3.1.剖析Handler(非代理)

AnatomyofaHandler(Non-proxying)

Handler一般做4件事:获取location配置;生成合适的响应;发送响应头;发送响应体。Handler有一个参数,即请求结构体。请求结构体包含很多关于客户请求的有用信息,比如说请求方法,URI,请求头等等。我们一个个地来看。

3.1.1.获取location配置

这部分很简单。只需要调用ngx_http_get_module_loc_conf,传入当前请求的结构体和模块定义即可。下面是我的circlegifhandler的相关部分:

static ngx_int_t
ngx_http_circle_gif_handler(ngx_http_request_t *r)
{
    ngx_http_circle_gif_loc_conf_t  *cglcf;
    cglcf = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
    ...

现在我们就可以访问之前在合并函数中设置的所有变量了。

3.1.2.生成响应

   
ngx_http_circle_gif_handler(ngx_http_request_t *r)

先来探讨一下参数ngx_http_request_t

这才是模块真正干活的地方,很有趣哦。

这里要用到请求结构体,主要是这些结构体成员:

typedef struct {
...
/* the memory pool, used in the ngx_palloc functions */
    ngx_pool_t                       *pool; 
    ngx_str_t                         uri;
    ngx_str_t                         args;
    ngx_http_headers_in_t             headers_in;
    ngx_http_headers_out_t            headers_out;

...
} ngx_http_request_t;

uri是请求的路径,e.g."/query.cgi".

args请求串参数中问号后面的参数(e.g."name=john").

headers_in包含有很多有用的东西,比如说cookie啊,浏览器信息啊什么的,但是许多模块可能用不到这些东东。如果你感兴趣的话,可以参看http/ngx_http_request.h。

对于生成输出,这些信息应该是够了。完整的ngx_http_request_t结构体定义在http/ngx_http_request.h。

3.1.3.发送响应头

响应头存放在结构体headers_out中,它的引用存放在请求结构体中。Handler设置相应的响应头的值,然后调用ngx_http_send_header(r)。headers_out中比较有用的是:

typedef stuct {
...
    ngx_uint_t                        status;
    size_t                            content_type_len;
    ngx_str_t                         content_type;
    ngx_table_elt_t                  *content_encoding;
    off_t                             content_length_n;
    time_t                            date_time;
    time_t                            last_modified_time;
..
} ngx_http_headers_out_t;

(剩下的可以在http/ngx_http_request.h找到。)

举例来说,如果一个模块要设置Content-Type为"image/gif",Content-Length为100,并返回HTTP200OK的响应,代码应当是这样的:

r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 100;
    r->headers_out.content_type.len = sizeof("image/gif") - 1;
    r->headers_out.content_type.data = (u_char *) "image/gif";
    ngx_http_send_header(r);

上面的HTTPheaders设定方式针对大多数参数都是有效的。但一些头部(headers)的变量设定要比上面的例子要麻烦;比如,content_encoding它还含有类型(ngx_table_elt_t*),所以必须先为此分配空间。可以用一个叫做ngx_list_push的函数来做,它传入一个ngx_list_t(与数组类似),返回一个list中的新成员(类型是ngx_table_elt_t)。下面的代码设置了Content-Encoding为"deflate"并发送了响应头:

r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
    if (r->headers_out.content_encoding == NULL) {
        return NGX_ERROR;
    }
    r->headers_out.content_encoding->hash = 1;
    r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
    r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
    r->headers_out.content_encoding->value.len = sizeof("deflate") - 1;
    r->headers_out.content_encoding->value.data = (u_char *) "deflate";
    ngx_http_send_header(r);

当头部有多个值时,这个机制常常被用到。它(理论上讲)使得过滤模块添加、删除某个值而保留其他值的时候更加容易,在操纵字符串的时候,不需要把字符串重新排序。

3.1.4.发送响应体(Sendingthebody)

现在模块已经生成了一个响应,并存放在了内存中。接下来它需要将这个响应分配给一个特定的缓冲区,然后把这个缓冲区加入到链表,然后调用链表中“发送响应体(sendbody)”的函数。

链表在这里起什么作用呢?Nginx中,handler模块(其实filter模块也是)生成响应到buffer中是同时完成的;链表中的每个元素都有指向下一个元素的指针,如果是NULL则说明链表到头了。简单起见,我们假设只有一个buffer。

首先,模块需要先声明buffer和链表:

ngx_buf_t    *b;
    ngx_chain_t   out;

接着,需要给buffer分配空间,并将我们的响应数据指向它:

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 
            "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->pos = some_bytes; /* first position in memory of the data */
    b->last = some_bytes + some_bytes_length; /* last position */

    b->memory = 1; /* content is in read-only memory */
    /* (i.e., filters should copy it rather than rewrite in place) */

    b->last_buf = 1; /* there will be no more buffers in the request */

现在就可以把数据挂在链表上了:

out.buf = b;
    out.next = NULL;

最后,我们发送这个响应体,返回值是链表在一次调用后的状态:

return ngx_http_output_filter(r, &out);

Buffer链是NginxIO模型中的关键部分,你得比较熟悉它的工作方式。

引用

问:为什么buffer还需要有个`last_buf`变量啊,我们不是可以通过判断next是否是NULL来知道哪个是链表的最末端了吗?

答:链表可能是不完整的,比如说,当有多个buffer的时候,并不是所有的buffer都属于当前的请求和响应。所以有些buffer可能是buffer链表的表尾,但是不是请求的结束。这给我们引入了接下来的内容……

相关推荐