Nginx http 一些小知识点


Nginx http 模块开发笔记

一、自定义http context

nginx http context即模块的自定义上下文环境,与loc_conf之类的conf结构的区别在于,conf结构的生存周期是整个nginx进程,初始化于启动阶段,其内存空间在整个进程中有效,而contex结构的生存周期则同是单个request,在模块自定义的request回调函数中初始化,在request处理结束后由nginx自动回收。

nginx 的http结构体命名不象loc_conf结构那样有严格的命名规则,可以自定义结构命名,惯例一般命名为ngx_http_module_ctx_t。

这里以ngx_http_proxy_module为例(src/http/modules/ngx_http_proxy_module.c)

[code language="c"]
typedef struct {
ngx_http_status_t status;
ngx_http_proxy_vars_t vars;
size_t internal_body_length;
} ngx_http_proxy_ctx_t;

[/code]

在ngx_http_proxy_module 的 content_handler回调函数 ngx_http_proxy_handler() 中为本次request初始化context结构,并将其绑定到本次request结构中去:

[code language="c"]
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_http_set_ctx(r, ctx, ngx_http_proxy_module);
[/code]

ngx_http_set_ctx 定义为一个macro,就是将结构体指针保存到ngx_http_request_t的ctx指针数组中,以module的ctx_index为索引,绑定之后再次使用就很简单了:

[code language="c"]
ngx_http_proxy_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
[/code]

二、自定义http 变量

依然以ngx_http_proxy_module为例(src/http/modules/ngx_http_proxy_module.c)

[code language="c"]
static ngx_http_module_t ngx_http_proxy_module_ctx = {
ngx_http_proxy_add_variables, /* preconfiguration */
NULL, /* postconfiguration */

NULL, /* create main configuration */
NULL, /* init main configuration */

NULL, /* create server configuration */
NULL, /* merge server configuration */

ngx_http_proxy_create_loc_conf, /* create location configration */
ngx_http_proxy_merge_loc_conf /* merge location configration */
};
[/code]

在 preconfiguration 调用 ngx_http_proxy_add_variables() 来注册自定义的变量,这个函数的作用就是对模块内的自定义变量结构数组成员调用 ngx_http_add_variable():

[code language="c"]
for (v = ngx_http_proxy_vars; v->name.len; v++) {
var = ngx_http_add_variable(cf, &v->name, v->flags);
if (var == NULL) {
return NGX_ERROR;
}

var->get_handler = v->get_handler;
var->data = v->data;
}
[/code]

ngx_http_proxy_vars数组声明如下:

[code language="c"]
static ngx_http_variable_t ngx_http_proxy_vars[] = {
{ ngx_string("proxy_host"), NULL, ngx_http_proxy_host_variable, 0,
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },

....

{ ngx_null_string, NULL, NULL, 0, 0, 0 }
};
[/code]

ngx_http_variable_t 结构如下:

[code language="c"]
struct ngx_http_variable_s {
ngx_str_t name; /* must be first to build the hash */
ngx_http_set_variable_pt set_handler;
ngx_http_get_variable_pt get_handler;
uintptr_t data;
ngx_uint_t flags;
ngx_uint_t index;
};
[/code]

这里的get_handler即ngx_http_proxy_host_variable,用来解析$proxy_host变量,所做的事情就是从ctx里面取得相应的内容,设置ngx_http_variable_value_t变量:

[code language="c"]
static ngx_int_t
ngx_http_proxy_host_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_proxy_ctx_t *ctx;

ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module);

if (ctx == NULL) {
v->not_found = 1;
return NGX_OK;
}

v->len = ctx->vars.host_header.len;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = ctx->vars.host_header.data;

return NGX_OK;
}
[/code]

三、在指令中设置变量

在指令中设置变量的一个例子就是 nginx headers_filter_module 模块中的 add_header 指令,例如:

[code language="bash"]
add_header Content-Disposition "attachment; filename=$filename";
[/code]

这条指令配置之后会在输出的头中添加 Content-Disposition Header,其值包含 $filename 变量内容。这种用法要求nginx 在解析指令的值的时候要保存变量的完整信息,实现的方式是通过 ngx_http_complex_value_t 数据类型来存储变量:

[code language="c"]
/*
* add_header 指令调用的处理函数
*/
static char *
ngx_http_headers_add(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_headers_conf_t *hcf = conf;

ngx_str_t *value;
ngx_uint_t i;
ngx_http_header_val_t *hv;
ngx_http_set_header_t *set;
ngx_http_compile_complex_value_t ccv;

value = cf->args->elts;

if (hcf->headers == NULL) {
hcf->headers = ngx_array_create(cf->pool, 1,
sizeof(ngx_http_header_val_t));
if (hcf->headers == NULL) {
return NGX_CONF_ERROR;
}
}

hv = ngx_array_push(hcf->headers);
if (hv == NULL) {
return NGX_CONF_ERROR;
}

hv->hash = 1;
hv->key = value[1];
hv->handler = ngx_http_add_header;
hv->offset = 0;

set = ngx_http_set_headers;
for (i = 0; set[i].name.len; i++) {
if (ngx_strcasecmp(value[1].data, set[i].name.data) != 0) {
continue;
}

hv->offset = set[i].offset;
hv->handler = set[i].handler;

break;
}

if (value[2].len == 0) {
ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
return NGX_CONF_OK;
}

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

/*
* 在这里将变量存入 ccv , 之后存入 hv->value (ngx_http_complex_value_t)
*/
ccv.cf = cf;
ccv.value = &value[2];
ccv.complex_value = &hv->value;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

return NGX_CONF_OK;
}

/*
* 解析变量的过程在 ngx_http_headers_filter
*/
static ngx_int_t
ngx_http_headers_filter(ngx_http_request_t *r)
{
ngx_str_t value;
ngx_uint_t i;
ngx_http_header_val_t *h;
ngx_http_headers_conf_t *conf;

conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);

if ((conf->expires == NGX_HTTP_EXPIRES_OFF && conf->headers == NULL)
|| r != r->main
|| (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_NO_CONTENT
&& r->headers_out.status != NGX_HTTP_MOVED_PERMANENTLY
&& r->headers_out.status != NGX_HTTP_MOVED_TEMPORARILY
&& r->headers_out.status != NGX_HTTP_NOT_MODIFIED))
{
return ngx_http_next_header_filter(r);
}

if (conf->expires != NGX_HTTP_EXPIRES_OFF) {
if (ngx_http_set_expires(r, conf) != NGX_OK) {
return NGX_ERROR;
}
}

if (conf->headers) {
h = conf->headers->elts;
for (i = 0; i < conf->headers->nelts; i++) {
/*
* 将 ngx_http_header_val_s 中的 value (ngx_http_complex_value_t)
* 取出来存入 value(ngx_str_t)
*/
if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
return NGX_ERROR;
}

if (h[i].handler(r, &h[i], &value) != NGX_OK) {
return NGX_ERROR;
}
}
}

return ngx_http_next_header_filter(r);
}

[/code]

四、例:proxy_pass $address 指令实现动态后端

首先要简单说一下nginx内存分配的基本逻辑,nginx的内存分配是由内存池来管理的,每当一个请求产生的时候会为这个请求初始化一个内存池,保存在ngx_http_request_t结构中的pool中。后续与这个请求相关的内存分配都由这个pool来分配内存。当请求处理完成的时候统一释放。这种方式使得开发者不再需要费心思为每次内存分配考虑释放的问题。对于一个变量来说,在分配内存的时候需要事先知道要准备多少内存空间来存放它的值。

当nginx解析配置文件过程中遇到proxy_pass指令的时候, 调用ngx_http_proxy_pass()函数进行指令解析。遇到$address字符串后被识别为一个变量,然后调用ngx_http_script_compile()处理这个变量,处理的过程如下:

  1. 在 var 的红黑树中寻找"address"字符串,获得这个字符串在树中的索引 index
  2. 在plcf->proxy_lengths数组中设置handler ngx_http_script_copy_var_len_code(index) 来解析变量值的长度
  3. 在 plcf->proxy_values 数组中设置handler ngx_http_script_copy_var_code(index)来解析变量的值

当收到用户请求时由ngx_http_proxy_handler()来处理,ngx_http_sina_proxy_eval() -> ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0, plcf->proxy_values->elts) 开始解析这个变量,步骤如下:

  1. 检查cmcf->variables数组,将no_cacheable = 1的项的valid成员置零
  2. 将proxy_lengths中的handler跑一遍,获得变量的总长度
  3. 根据获得的长度从pool中分配内存
  4. 将proxy_values中的handler跑一遍,获得变量的值
  5. 获得 proxy的值后调用 ngx_http_proxy_set_vars(&url, &ctx->vars),将proxy相关的值设置到ctx->vars变量集合中
  6. 初始化一个ngx_http_upstream_resolved_t结构,保存在u->resolved, u->resolved->host 即$address的值
  7. 初始化 upstream buffer及handler
  8. 调用 ngx_http_read_client_request_body() 获得全部请求、

在ngx_http_proxy_create_request()阶段,如果plcf->proxy_length不为空,请求的uri等值由ctx->vars中获取,否则直接从request中获取.

在ngx_http_upstream_init_request()阶段,如果u->resolved不为空且u->resolved->sockaddr为空,说明upstream的地址未解析,因此调用ngx_resolve_name()来发起一个resolver请求,超时默认为30秒, resolver的handler 设置为ngx_http_upstream_resolve_handler(),当nginx的resolver 完成这个地址的DNS解析的时候会调用这个handler,它会完成后续的connect等操作。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值