nginx关闭请求处理ngx_http_finalize_request源码分析

ngx_http_finalize_request

各个http模块在执行完某个操作都需要调用这个函数,来把请求的引用计数减去1,当引用计数为0时才会真正释放一个请求。这个函数实现比较复杂,考虑了各种场景,我们分别来看下这些场景。

 (1)当执行某个操作结束后:

例如把读事件从epoll红黑树中删除时。 这个操作执行完成后,会调用这个函数,表示这个操作已经完成了,需要把这个操作对应的引用计数给减去1:典型的还比如upstream结束,子请求结束等调用。

//由各个http模块调用的,释放http请求的函数

void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)

{

//http请求中某一个动作结束,而请求还有其他的业务要处理,多半是传递NGX_DONE参数,

//函数内部在引用计数为0时才会销毁请求。

    if (rc == NGX_DONE)

{

// 该函数后续会对引用计数进行-1

        ngx_http_finalize_connection(r);

        return;

    }

}

(2)nginx服务器在收到来自客户的请求行与请求头后,会调用http各个模块进行处理。这些模块介入到http处理的11个阶段中。如果一次操作没有执行完这11个阶段,则需要重新执行以便完成剩余的阶段:

void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)

{

//表示请求还需要按照11个http阶段继续处理下去

    if (rc == NGX_DECLINED)

{

//设置空,目的是为了在NGX_HTTP_CONTENT_PHASE阶段调用其它介入模块的处理方法。

//在这个阶段是很多http模块都愿意介入的阶段。将这个指针清空,目的是为了调用这些模块的处理方法

        r->content_handler = NULL;

        r->write_event_handler = ngx_http_core_run_phases;



//继续剩余的11个节点处理

        ngx_http_core_run_phases(r);

        return;

    }

}

为什么需要把请求对象的content_handler指针清空。因为在内容处理阶段,每一个location只能有一个模块设置content_handler回调,但是可以有很多模块设置全局的回调。nginx优先使用这个content_handler回调。看下ngx_http_core_content_phase函数就明白了。

//NGX_HTTP_CONTENT_PHASE阶段的checker方法

ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,

    ngx_http_phase_handler_t *ph)

{

//优先使用location内的content_handler回调

    if (r->content_handler)

{

        ngx_http_finalize_request(r, r->content_handler(r));

        return NGX_OK;

    }

//在content_handler方法不存在时,才使用各个模块提供的全局的回调

    rc = ph->handler(r);

}

(3)在客户端访问的文件不存在时,如果指定了error page选项,则在请求结束时会由http框架构造300以上的错误码,并发送这个特殊的错误页面给客户端浏览器。或者客户端上传文件到服务器成功后,也需要http框架构造201或者204响应码给客户端。

void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)

{

//如果http模块需要http框架构造并发送>=300以上的响应码给客户端

//或者http模块要发送上传文件成功的201,204响应码

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE

        || rc == NGX_HTTP_CREATED

        || rc == NGX_HTTP_NO_CONTENT)

    {

//发送响应码页面

        ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));

        return;

    }

}

 (4)在给客户端发送http响应时,如果一次调度并不能马上发送完所有的响应数据,则会设置请求对象的写回调为ngx_http_writer,下一次写事件被调度执行时,会继续往客户端浏览器发送剩余的http响应内容。

void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)

{

//执行到这里,说明是原始请求

//一次调度没有全部发送完http应答,则重新注册写事件回调以便下次调度时再次发生

if (r->buffered || c->buffered || r->postponed || r->blocked)

{

        ngx_http_set_write_handler(r));

        return;

    }

}



//如果写数据一次性没写完,则重新注册写回调

static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r)

{

    r->read_event_handler = r->discard_body ?

                                ngx_http_discarded_request_body_handler:

                                ngx_http_test_reading;

//写事件回调,下线调度时继续往客户端发送包体

    r->write_event_handler = ngx_http_writer;



//注册写事件到epoll

    ngx_handle_write_event(wev, clcf->send_lowat));



    return NGX_OK;

}

(5)子请求相关逻辑也是一块比较复杂些的逻辑,暂且略过。

(6)正常关闭http请求

会不会马上就关闭请求要看引用计数,引用计数为0则会关闭请求,但并不一定会马上关闭tcp连接,因为有可能开启了keepalive机制或者延迟关闭机制。也就是说引用计数决定是否需要关闭http请求,而keepalive机制或者延迟关闭机制则决定是否需要关闭tcp连接。

void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)

{

//执行到这里,表示正在要释放请求了,下面的操作都是释放处理

    r->done = 1;

    r->write_event_handler = ngx_http_request_empty_handler;



//客户端浏览器发送了FIN数据包

    if (c->read->eof)

{

        ngx_http_close_request(r, 0);

        return;

    }



//正常关闭流程(是否关闭请求看引用计数是否为0,是否tcp连接要看是否开启了

//keepalive或者延迟关闭机制)

    ngx_http_finalize_connection(r);

}

ngx_http_finalize_connection---keepalive与延迟关闭机制

ngx_http_finalize_connection函数是一个比ngx_http_close_request更上一层的接口。函数内部最后也会调用ngx_http_close_request来关闭请求。 但这个函数还会判断是否开启了keeaplive机制以及开启了延迟关闭机制。如果开启了这些机制,则http请求会马上被关闭,但这个请求之上的tcp连接则不会马上被关闭。

//释放http请求与连接

static void ngx_http_finalize_connection(ngx_http_request_t *r)

{

    if (r->main->count != 1)

{

//如果是丢弃包体,则设置回调

        if (r->discard_body)

{

            r->read_event_handler = ngx_http_discarded_request_body_handler;

            ngx_add_timer(r->connection->read, clcf->lingering_timeout);

//设置强制关闭连接的时间,避免一直处理丢弃包体操作

//如果超过了这个时间,则http框架不在接收包体后再执行丢弃包体,直接关闭

            if (r->lingering_time == 0)

{

                r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000);

            }

        }

//关闭连接

        ngx_http_close_request(r, 0);

        return;

    }



//执行到这里,引用计数为1,则要准备结束请求了

//keepalive为1表示请求需要释放,但tcp连接还是用复用的

    if (!ngx_terminate

         && !ngx_exiting

         && r->keepalive

         && clcf->keepalive_timeout > 0)

    {

        ngx_http_set_keepalive(r);

        return;

    }

//指向到这里说明keepavlive为0,则需要关闭http请求与tcp连接,到还需要判断是否需要延迟关闭

//如果指定了总是延迟关闭或者等待客户端发完数据在关闭时,则延迟关闭tcp连接

    if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS

        || (clcf->lingering_close == NGX_HTTP_LINGERING_ON

            && (r->lingering_close

                || r->header_in->pos < r->header_in->last

                || r->connection->read->ready)))

    {

        ngx_http_set_lingering_close(r);

        return;

    }



//执行到这里说明没有延时关闭,也没有keepalive机制,则调用底层函数关闭连接

    ngx_http_close_request(r, 0);

}

ngx_http_close_request---调用底层接口释放请求

//关闭一个请求(将引用计数减去1,如果引用计数为0则会真正的关闭请求,否则不会关闭请求)

static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)

{

//在用释放请求时,先把引用计数减1,然后引用计数还不为0则说明可能还有其他http模块需要

//使用到这个请求,此时还不能释放请求,函数直接返回。

    r->count--;

    if (r->count || r->blocked)

{

        return;

    }



//执行到这里说明真正需要关闭请求了,则会释放请求对象

    ngx_http_free_request(r, rc);



//用于关闭tcp连接,并释放这个连接上的资源

    ngx_http_close_connection(c);

}

 调用ngx_http_close_request这个函数有可能并不会马上关闭这个请求,因为其它模块还在使用这个请求时,如果此时释放了,将会导致内存访问越界致命错误.

如果引用计数为0了,则此时是真正要释放请求与连接了。

调用栈

在开始调用free_request的时候,已经确认该request完成并要开始进行释放了,此时response已经返回,无论是否keepalive,该request已经释放,tcp连接可能没释放,由keeplive指定。

#0  ngx_http_free_request (r=0x1f9e090, rc=0) at src/http/ngx_http_request.c:3820

#1  0x00000000004d7116 in ngx_http_set_keepalive (r=0x1f9e090) at src/http/ngx_http_request.c:3257

#2  0x00000000004d65be in ngx_http_finalize_connection (r=0x1f9e090) at src/http/ngx_http_request.c:2908

#3  0x00000000004d618b in ngx_http_finalize_request (r=0x1f9e090, rc=0) at src/http/ngx_http_request.c:2800

#4  0x00000000004f7762 in ngx_http_upstream_finalize_request (r=0x1f9e090, u=0x1fdef30, rc=0) at src/http/ngx_http_upstream.c:4600

#5  0x00000000004f5b1b in ngx_http_upstream_process_non_buffered_request (r=0x1f9e090, do_write=1)

    at src/http/ngx_http_upstream.c:3708

#6  0x00000000004f58ce in ngx_http_upstream_process_non_buffered_downstream (r=0x1f9e090) at src/http/ngx_http_upstream.c:3640

#7  0x00000000004f424d in ngx_http_upstream_send_response (r=0x1f9e090, u=0x1fdef30) at src/http/ngx_http_upstream.c:3112

#8  0x00000000004f2e98 in ngx_http_upstream_process_header (r=0x1f9e090, u=0x1fdef30) at src/http/ngx_http_upstream.c:2546

#9  0x00000000004f074e in ngx_http_upstream_handler (ev=0x7fb27cf0d280) at src/http/ngx_http_upstream.c:1366

#10 0x00000000004acf00 in ngx_epoll_process_events (cycle=0x1f8c2c0, timer=60000, flags=1)

    at src/event/modules/ngx_epoll_module.c:973

#11 0x000000000049641c in ngx_process_events_and_timers (cycle=0x1f8c2c0) at src/event/ngx_event.c:262

#12 0x00000000004aa325 in ngx_worker_process_cycle (cycle=0x1f8c2c0, data=0x0) at src/os/unix/ngx_process_cycle.c:811

#13 0x00000000004a359a in ngx_spawn_process (cycle=0x1f8c2c0, proc=0x4aa23b <ngx_worker_process_cycle>, data=0x0,

    name=0x7d82ab "worker process", respawn=-3) at src/os/unix/ngx_process.c:199

#14 0x00000000004a9149 in ngx_start_worker_processes (cycle=0x1f8c2c0, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:395

#15 0x00000000004a86bd in ngx_master_process_cycle (cycle=0x1f8c2c0) at src/os/unix/ngx_process_cycle.c:138

#16 0x000000000045d7d0 in main (argc=3, argv=0x7fff9263b3c8) at src/core/nginx.c:418

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Nginx 中安装 ngx_http_mp4_module 模块,需要按照以下步骤进行操作: 1. 确认 Nginx 是否支持 ngx_http_mp4_module 模块 首先需要确认 Nginx 是否支持 ngx_http_mp4_module 模块,可以使用以下命令查看 Nginx 是否已经编译了该模块: ``` nginx -V 2>&1 | grep -o with-http_mp4_module ``` 如果输出结果为 with-http_mp4_module,则说明 Nginx 已经编译了 ngx_http_mp4_module 模块;如果输出结果为空,则说明 Nginx 没有编译该模块。 2. 下载 ngx_http_mp4_module 模块 如果 Nginx 没有编译 ngx_http_mp4_module 模块,需要下载该模块并添加到 Nginx 中。可以从 Github 上下载该模块,链接为:https://github.com/kaltura/nginx-vod-module。 可以使用以下命令将 ngx_http_mp4_module 模块下载到 /opt 目录下: ``` cd /opt git clone https://github.com/kaltura/nginx-vod-module.git ``` 3. 编译 Nginx 并添加 ngx_http_mp4_module 模块 在编译 Nginx 时需要添加 --add-module=/opt/nginx-vod-module 参数来指定 ngx_http_mp4_module 模块所在的目录,具体命令如下: ``` ./configure --prefix=/usr/local/nginx --add-module=/opt/nginx-vod-module make make install ``` 4. 配置 NginxNginx 的配置文件中添加以下内容,即可使用 ngx_http_mp4_module 模块: ``` location /video/ { mp4; mp4_buffer_size 1m; mp4_max_buffer_size 5m; } ``` 其中,/video/ 是视频文件所在的目录。mp4 是 ngx_http_mp4_module 模块提供的指令,表示该目录下的文件都是 MP4 格式的视频文件。 mp4_buffer_size 和 mp4_max_buffer_sizengx_http_mp4_module 模块提供的两个参数,用于控制视频文件的缓存大小。 5. 重启 Nginx 完成以上步骤后,需要重启 Nginx 使配置生效: ``` nginx -s reload ``` 至此,ngx_http_mp4_module 模块安装完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值