Nginx filter分析

本文介绍了Nginx的filter机制,包括header filter和body filter。header filter负责计算响应头大小并发送,body filter则涉及内容复制和处理。以gzip模块为例,展示了filter的注册过程。同时,详细解析了ngx_http_copy_filter_module的工作原理,如buffer管理和ngx_output_chain的使用,以及如何处理内存中的buf和文件buf。
摘要由CSDN通过智能技术生成

注:这篇文章整合了我们小组成员(卫越,雕梁,吉兆)的一些博客内容组成。


在CONTENT阶段产生的数据被发往客户端(系统发送缓存区)之前,会先经过过滤。Nginx的filter的工作方式和做鱼有些类似。比如一条鱼,可以把它切成鱼片(也可以切块,切泥),然后通过不同的烹饪方法就得到水煮鱼或者日式生鱼片或者废了等等。同样是一条鱼,加工得到的结果却截然不同,就是因为中间不同的工序赋予了这条鱼各种属性。Nginx的filter也是一个道理,前面的Handler好比这条鱼,filter负责加工,最后得到的HTTP响应就会各种各样,格式可以是JSON或者YAML,内容可能多一些或者少一些,HTTP属性可各异,可以选择压缩,甚至内容可以被丢弃。


对应HTTP请求的响应头和响应体,Nginx分别设置了header filter和body filter。两种机制都是采用链表的方式,不同过滤模块对应链表的一个节点,一般而言一个模块会同时注册header filter和body filter。一个典型的filter模块,比如gzip模块使用类似如下的代码来注册:

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

...
   
static ngx_int_t
ngx_http_gzip_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_gzip_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_gzip_body_filter;

    return NGX_OK;
}

上面的代码中,gzip模块首先在模块的开头声明了两个static类型的全局变量ngx_http_next_header_filter和ngx_http_next_body_filter,在ngx_http_gzip_filter_init函数中,这二个变量分别被赋值为ngx_http_top_header_filter及ngx_http_top_body_filter。而后二者定义在ngx_http.c,并在ngx_http.h头文件中被导出。ngx_http_top_header_filter和ngx_http_top_body_filter实际上是filter链表的头结点,每次注册一个新的filter模块时,它们的值先被保存在新模块的内部全局变量ngx_http_next_header_filter及ngx_http_next_body_filter,然后被赋值为新模块注册的filter函数,而且Nginx filter是先从头节点开始执行,所以越晚注册的模块越早执行。


采用默认编译选项,Nginx默认编译的模块如下:

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_regex_module,
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
    &ngx_http_upstream_module,
    &ngx_http_static_module,
    &ngx_http_autoindex_module,
    &ngx_http_index_module,
    &ngx_http_auth_basic_module,
    &ngx_http_access_module,
    &ngx_http_limit_conn_module,
    &ngx_http_limit_req_module,
    &ngx_http_geo_module,
    &ngx_http_map_module,
    &ngx_http_split_clients_module,
    &ngx_http_referer_module,
    &ngx_http_rewrite_module,
    &ngx_http_proxy_module,
    &ngx_http_fastcgi_module,
    &ngx_http_uwsgi_module,
    &ngx_http_scgi_module,
    &ngx_http_memcached_module,
    &ngx_http_empty_gif_module,
    &ngx_http_browser_module,
    &ngx_http_upstream_ip_hash_module,
    &ngx_http_upstream_keepalive_module,
    &ngx_http_write_filter_module,          /* 最后一个body filter,负责往外发送数据 */
    &ngx_http_header_filter_module,         /* 最后一个header filter,负责在内存中拼接出完整的http响应头,并调用ngx_http_write_filter发送 */
    &ngx_http_chunked_filter_module,        /* 对响应头中没有content_length头的请求,强制短连接(低于http 1.1)或采用chunked编码(http 1.1) */
    &ngx_http_range_header_filter_module,   /* header filter,负责处理range头 */
    &ngx_http_gzip_filter_module,           /* 支持流式的数据压缩 */
    &ngx_http_postpone_filter_module,       /* body filter,负责处理子请求和主请求数据的输出顺序 */
    &ngx_http_ssi_filter_module,            /* 支持过滤SSI请求,采用发起子请求的方式,去获取include进来的文件 */
    &ngx_http_charset_filter_module,        /* 支持添加charset,也支持将内容从一种字符集转换到另外一种字符集 */
    &ngx_http_userid_filter_module,         /* 支持添加统计用的识别用户的cookie */
    &ngx_http_headers_filter_module,        /* 支持设置expire和Cache-control头,支持添加任意名称的头 */
    &ngx_http_copy_filter_module,           /* 根据需求重新复制输出链表中的某些节点(比如将in_file的节点从文件读出并复制到新的节点),
                                               并交给后续filter进行处理 */
    &ngx_http_range_body_filter_module,     /* body filter,支持range功能,如果请求包含range请求,那就只发送range请求的一段内容 */
    &ngx_http_not_modified_filter_module,   /* 如果请求的if-modified-since等于回复的last-modified值,说明回复没有变化,清空所有回复的内容,返回304 */ 
    NULL
};

从模块的命名可以很容易看出哪些模块是filter模块,一般而言Nginx的filter模块名以filter_module结尾,普通的模块名以module结尾。上面的列表从下往上看,ngx_http_not_modified_filter_module实际上filter链的第一个节点,而ngx_http_write_filter_module是最后一个节点。filter模块的执行顺序特别重要,比如数据经过gzip模块后就变成了压缩之后的数据,如果在gzip模块后面运行的filter模块需要再查看数据的原始内容就不可能了(除非再做解压),第三方模块会被Nginx注册在ngx_http_copy_filter_module之后,ngx_http_headers_filter_module之前。这样设定的原因是为了确保一些模块比如gzip filter,chunked filter,copy filter运行在filter链的开头或尾部。


Nginx header filter

通常Nginx调用ngx_http_send_header函数来发送响应头,看下它的实现:

ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
    if (r->err_status) {
        r->headers_out.status = r->err_status;
        r->headers_out.status_line.len = 0;
    }

    return ngx_http_top_header_filter(r);
}

上面的代码中调用了ngx_http_top_header_filter,也就是header  filter的头节点,按照上一节介绍的顺序,ngx_http_not_modified_filter_module是最后一个注册的filter模块,而最后定义的会最先执行,初始化之后,它实际上是ngx_http_not_modified_header_filter函数:

static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    if (r->headers_out.status != NGX_HTTP_OK
        || r != r->main
        || r->headers_out.last_modified_time == -1)
    {
        return ngx_http_next_header_filter(r);
    }

    if (r->headers_in.if_unmodified_since) {
        return ngx_http_test_precondition(r);
    }

    if (r->headers_in.if_modified_since) {
        return ngx_http_test_not_modified(r);
    }

    return ngx_http_next_header_filter(r);
}

而在ngx_http_not_modified_header_filter函数中,它会调用模块内部定义的函数指针变量ngx_http_next_header_filter,而该变量保存的是上一模块注册的header filter函数,同样的下一个header filter函数内部也会调用其模块内部的ngx_http_next_header_filter,直到调用到最后一个header filter - ngx_http_header_filter。

ngx_http_header_filter,这个filter负责计算响应头的总大小,并分配内存,组装响应头,并调用ngx_http_write_filter发送。Nginx中,header filter只会被调用一次,ngx_http_header_filter函数中首先会检查r->header_sent标识是否已经被设置,如果是的话,则直接返回;否则设置该标识,并发送响应头。另外如果是子请求的话,也会直接退出函数。


 Nginx body filter

Nginx中通常调用ngx_http_output_filter函数来发送响应体,它的实现如下:

ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t          rc;
    ngx_connection_t  *c;

    c = r->connection;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http output filter \"%V?%V\"", &r->uri, &r->args);

    rc = ngx_http_top_body_filter(r, in);

    if (rc == NGX_ERROR) {
        /* NGX_ERROR may be returned by any filter */
        c->error = 1;
    }

    return rc;
}

body filter链调用的原理和header filter一样,和ngx_http_send_header函数不同的是,上面的函数多了一个类型为ngx_chain_t *的参数,因为Nginx实现的是流式的输出,并不用等到整个响应体都生成了才往客户端发送数据,而是产生一部分内容之后将其组织成链表,调用ngx_http_output_filter发送,并且待发送的内容可以在文件中,也可以是在内存中,Nginx会负责将数据流式的,高效的传输出去。而且当发送缓存区满了时,Nginx还会负责保存未发送完的数据,调用者只需要对新数据调用一次ngx_http_output_filter即可。

 

ngx_http_copy_filter_module

ngx_http_copy_filter_module是响应体过滤链(body filter)中非常重要的一个模块,这个filter模块主要是来将一些需要复制的buf(可能在文件中,也可能在内存中)重新复制一份交给后面的filter模块处理。先来看它的初始化函数: 

static ngx_int_t
ngx_http_copy_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_copy_filter;

    return NGX_OK;
}

可以看到,它只注册了body filter,而没有注册header filter,也就是说只有body filter链中才有这个模块。

该模块有一个命令,命令名为output_buffers,用来配置可用的buffer数和buffer大小,它的值保存在copy filter的loc conf的bufs字段,默认数量为1,大小为32768字节。这个参数具体的作用后面会做介绍。

Nginx中,一般filter模块可以header filter函数中根据请求响应头设置一个模块上下文(context),用来保存相关的信息,在body filter函数中使用这个上下文。而copy filter没有header filter,因此它的context的初始化也是放在body filter中的,而它的ctx就是ngx_output_chain_ctx_t,为什么名字是output_chain呢,这是因为copy filter的主要逻辑的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值