`
奔跑的羚羊
  • 浏览: 570021 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

 
阅读更多
3. Handlers

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

3.1. 剖析Handler(非代理)
Anatomy of a Handler (Non-proxying)


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

3.1.1. 获取location配置


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

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, 并返回 HTTP 200 OK 的响应, 代码应当是这样的:
    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);

   上面的HTTP headers设定方式针对大多数参数都是有效的。但一些头部(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. 发送响应体(Sending the body)

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

    链表在这里起什么作用呢?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链是Nginx IO模型中的关键部分,你得比较熟悉它的工作方式。
引用

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

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

相关推荐

Global site tag (gtag.js) - Google Analytics