nginx模块开发入门(六)-3.1 Anatomy of a Handler (Non-proxying)
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链表的表尾,但是不是请求的结束。这给我们引入了接下来的内容……