nginx过滤器模块

        nginx服务器给客户端发送响应时,包括http响应头部、http响应包体内容。可以调用http框架提供的两个函数ngx_http_send_header,ngx_http_output_filter,分别用于给客户端发送http响应头部、http响应包体。这两个函数会调用各个过滤器模块,对将要发送给客户端的响应头部、响应包体进行过滤处理。例如: 是否需要添加http响应头部字段、是否需要断点续传、是否只发送一个区间块的数据、响应包体是否需要压缩等。

一、过滤器模块链表创建

        nginx服务器有一个全局的变量ngx_http_top_header_filter,这是一个函数指针变量。 也是http响应头部过滤链表的头部。对于每一个过滤模块,都会有一个静态的变量ngx_http_next_header_filter,同样这也是一个函数指针变量,指向过滤链表的下一个节点。例如:在由6个过滤器模块组成的http响应头部过滤链表内存布局如下:


        从这张图可以看出,链表采用的是头插法。也就是说第一个过滤器模块变成了链表的尾节点,而最后一个过滤器模块变成了链表的首节点。同样的http响应包体组成的过滤链表内存布局和上图是类似的,都采用的是头插法。只不过http响应过滤包体链表头指针为一个全局的函数指针ngx_http_top_body_filter,每一个http过滤模块的静态变量ngx_http_next_body_filter则组成响应包体链表的next指针。

        下面从代码角度分析下这个过滤链表是如何创建的。以http响应头部过滤链表的创建为例。

        ngx_http_header_filter_module是第一个http响应头部过滤模块,在这个模块的ngx_http_header_filter_init函数中,将ngx_http_top_header_filter函数指针指向ngx_http_header_filter, 此时这个为链表的第一个节点。

static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_top_header_filter = ngx_http_header_filter;

    return NGX_OK;
}
        ngx_http_chunked_filter_module是第二个http响应头部过滤模块,在这个模块的ngx_http_chunked_filter_init函数中将ngx_http_next_header_filter指向ngx_http_top_header_filter,也就是第一个http响应过滤头部模块,而ngx_http_top_header_filter指向本模块的回调ngx_http_chunked_header_filte,使用头部插入法构成一个链表。

static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;   //next指针指向之前的模块
    ngx_http_top_header_filter = ngx_http_chunked_header_filter;//top指针指向当前这个模块的回调
    return NGX_OK;
}
         其它过滤器模块插入到链表的过程是类似的,这里就不再对每一个过滤模块如何插入到链表进行详细分析了, 没有这个必要。
二、常见http过滤模块的功能

        过滤模块是比较简单的模块,对于每一个过滤模块,代码量也不过几百行而已。仔细分析很容易看懂,这里就不对这些过滤模块进行详细分析了。但需要总结下几个常见过滤模块是做什么的。

三、发送响应头部

        ngx_http_send_header函数用于发送http响应头部,这个是http框架提供的接口,可以被http模块调用。看下这个函数的实现。

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);
}

        这个函数会从http响应头部过滤器链表头部开始遍历,使用各个过滤器模块对响应头部进行处理。每一个过滤器模块如果处理完成后, 都会调用ngx_http_next_header_filter开始进入下一个过滤器模块进行处理, 最终把处理后的响应头部发送给客户端。而最后一个http响应头部过滤器链表节点的回调为:ngx_http_header_filter,看下这个函数的实现。

//1、统计http响应头部的长度
//2、开辟http响应头部的缓冲区空间
//3、将http响应头部的各个头部信息拼装到buf缓冲区中,然后将这个缓冲区的数据发送给客户端  
static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r)
{
/********1、以下是为了统计响应头部的总大小         **********/
/*********目的是为了开辟一个应答包头大小的缓冲区 **********/	
    len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1 + sizeof(CRLF) - 1;
	
	//统计状态码字符串的长度
	//status_line存放的是状态码的内容,例如:"302 Moved Temporarily"
    if (r->headers_out.status_line.len)
	{
        len += r->headers_out.status_line.len;
        status_line = &r->headers_out.status_line;
    } 

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
	//服务器的版本信息长度
    if (r->headers_out.server == NULL) 
	{
        len += clcf->server_tokens ? sizeof(ngx_http_server_full_string) - 1:
                                     sizeof(ngx_http_server_string) - 1;
    }
	
/********2、开辟一个buf结构,buf大小为应答包头的大小**********************/
    b = ngx_create_temp_buf(r->pool, len);
    if (b == NULL) 
	{
        return NGX_ERROR;
    }

/********3、以下操作将http应答包头的各个字段拷贝到开辟的缓冲区***********/

	//拷贝HTTP1.1 200 OK状态行
    /* "HTTP/1.x " */
    b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
    if (status_line) 
	{
        b->last = ngx_copy(b->last, status_line->data, status_line->len);

    } 
    *b->last++ = CR; *b->last++ = LF;
	
	//拷贝应答包体长度字段
    if (r->headers_out.content_length == NULL
        && r->headers_out.content_length_n >= 0)
    {
        b->last = ngx_sprintf(b->last, "Content-Length: %O" CRLF,r->headers_out.content_length_n);
    }

	//构造发送缓冲区
    out.buf = b;
    out.next = NULL;
	
/********4、发送http响应头部信息给客户端***********/
    return ngx_http_write_filter(r, &out);
}

四、发送响应包体

        ngx_http_output_filter函数用于向客户端发送http响应包体数据。这个是http框架提供的接口,可以被http模块调用。看下这个函数的实现:

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

	//发送响应包体
    rc = ngx_http_top_body_filter(r, in);

    return rc;
}
        这个函数会从http响应包体过滤器链表头部开始遍历,使用各个过滤器模块对响应包体进行处理。 每一个过滤器模块如果处理完成后, 都会调用ngx_http_next_body_filter开始进入下一个过滤器模块进行处理, 最终把处理后的响应包体发送给客户端。而最后一个http响应包体过滤器链表节点的回调为:ngx_http_write_filter,看下这个函数的实现。

        ngx_http_write_filter这个函数会将响应包体发送给客户端,如果一次没法发送完成,则会保存未发送完成的数据到链表中,同时删除已经发送完成的链表数据。

        现在假设应答包体链表由5个待发送的数据组成,则内存布局如下:


        在一次发送过程中,只有一部分数据发送给了客户端浏览器,还有一部分数据没有发送完成,需要下一次才能发送给浏览器。则内存布局如下: 图中可以看出,已经发送完的数据缓冲区pos与last指针都指向缓冲区的末尾。需要注意的是第三个数据结点,这个结点的数据并没有全部发送完,pos指针指向待发送的位置。下一次发送时将从第三个节点的pos位置开始,把数据发送给客户端浏览器。


        当再一次发送数据时,之前已经发送完的数据结点已经被删除了。将从第一个有效节点的pos位置开始发送剩余的数据给客户端浏览器,内存布局如下图:



ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
	//计算out缓冲区占用字数数
    for (cl = r->out; cl; cl = cl->next) 
	{
        ll = &cl->next;
        size += ngx_buf_size(cl->buf);
    }

    /* add the new chain to the existent one */
	//将本次待发送的缓冲区加到out尾部,并计算出out总长度
    for (ln = in; ln; ln = ln->next) 
	{
        cl = ngx_alloc_chain_link(r->pool);
        cl->buf = ln->buf;
        *ll = cl;
        ll = &cl->next;
		//计算这个链表节点缓冲区数据大小
        size += ngx_buf_size(cl->buf);
    }
    *ll = NULL;

	//待发送的响应包体没有达到阈值,则暂时不发送
    if (!last && !flush && in && size < (off_t) clcf->postpone_output) 
	{
        return NGX_OK;
    }
	//需要延迟发送响应,用于限速功能,本次不发送的会,对客户端来讲,自然速度降低了
    if (c->write->delayed) 
	{
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }

	//判断是否需要限速
    if (r->limit_rate) 
	{
        limit = r->limit_rate * (ngx_time() - r->start_sec + 1)
                - (c->sent - clcf->limit_rate_after);

		//limit小于等于0,表示超发的字节数,需要限速
        if (limit <= 0) 
		{
            c->write->delayed = 1;		//需要延迟发送响应
            ngx_add_timer(c->write, (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));
            c->buffered |= NGX_HTTP_WRITE_BUFFERED;
            return NGX_AGAIN;
        }
		//计算本次最大可以发送多少数据给客户端
        if (clcf->sendfile_max_chunk
            && (off_t) clcf->sendfile_max_chunk < limit)
        {
            limit = clcf->sendfile_max_chunk;
        }
    }
	else 
	{
        limit = clcf->sendfile_max_chunk;
    }

	//调用ngx_linux_sendfile_chain发送数据,返回值是未发送完的数据
    chain = c->send_chain(c, r->out, limit);//ngx_writev_chain

	//清空已经发送的缓冲
    for (cl = r->out; cl && cl != chain; /* void */)
	{
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

	//保存未发送完成的数据,待下次事件触发后在发送给客户端
    r->out = chain;

    return NGX_OK;
}

        到此过滤器模块已经分析完成了。过滤器模块还是比较简单的,代码量也不太,读者可以好好分析上图中的这6个过滤器模块的实现方式。下一篇文章将分析nginx是如何接收http请求包体的,如果不需要接收请求包体,那nginx又是如何丢弃http请求包体的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值