nginx怎么处理http请求
发布时间 - 2023-06-03 00:00:00 点击率:次一、event事件与http框架的交互
在接收完http请求行、http请求头部后,会调用ngx_http_process_request这个函数开始处理http请求。因为一个http请求由11个处理阶段组成,而每一个处理阶段都允许多个http模块介入,因此在这个函数中,将调度各个阶段的http模块共同完成这个请求。
//接收到http请求行与请求头后,http的处理流程,是第一个http处理请求的读事件回调
//这个函数执行后,将把读写事件的回调设置为ngx_http_request_handler。这样下次再有事件时
//将调用ngx_http_request_handler函数来处理,而不会再调用ngx_http_process_request了
static void ngx_http_process_request(ngx_http_request_t *r)
{
ngx_connection_t *c;
c = r->connection;
//因为已经接收完http请求行、请求头部了,准备调用各个http模块处理请求了。
//因此需要接收任何来自客户端的读事件,也就不存在接收http请求头部超时问题
if (c->read->timer_set)
{
ngx_del_timer(c->read);
}
//重新设置当前连接的读写事件回调
c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler;
//设置http请求对象的读事件回调,这个回调不做任何的事情。
//那http请求对象的读事件回调,与上面的连接对应的读事件回调有什么关系呢?
//当读事件发生后,连接对应的读事件回调ngx_http_request_handler会被调用,
//在这个回调内会调用http请求对象的读事件回调ngx_http_block_reading,而这个回调是
//不会做任何事件的,因此相当于忽略了读事件。因为已经接收完了请求行请求头,现在要做的是调用各个http模块,
//对接收到的请求行请求头进行处理
r->read_event_handler = ngx_http_block_reading;
//调用各个http模块协同处理这个请求
ngx_http_handler(r);
//处理子请求
ngx_http_run_posted_requests(c);
}ngx_http_process_request函数只会被调用一次。如果一次调度并不能处理完11个http阶段,那会将连接对象对应的读事件、写事件回调设置为ngx_http_request_handler。而请求对象的读事件设置为ngx_http_block_reading, 请求对象的写事件回调设置为ngx_http_core_run_phases, 这个回调在ngx_http_handler内设置。这样在事件再次到来时不会调用
ngx_http_process_request函数处理了。那event事件模块的读写事件回调与http请求对象的读写事件回调有什么关系呢?
//http请求处理读与写事件的回调,在ngx_http_process_request函数中设置。
//这个函数中将会调用http请求对象的读写事件回调。将event事件模块与http框架关联起来
static void ngx_http_request_handler(ngx_event_t *ev)
{
//如果同时发生读写事件,则只有写事件才会触发。写事件优先级更高
if (ev->write)
{
r->write_event_handler(r); //在函数ngx_http_handler设置为:ngx_http_core_run_phases
}
else
{
r->read_event_handler(r); //在函数ngx_http_process_request设置为:ngx_http_block_reading
}
//处理子请求
ngx_http_run_posted_requests(c);
}可以看到,连接对象的读事件回调中,会调用http请求对象的读事件回调。连接对象的写事件回调会调用http请求对象的写事件回调。
图中可看出,在event的读事件发生时,epoll返回后会调用读事件的回调ngx_http_request_handler。在这个读事件回调中,又会调用http框架,也就是http请求对象的读事件回调ngx_http_block_reading,这个http请求对象的读事件回调是不做任何事情的,相当于忽略读事件。因此http框架将会返回到事件模块。那为什么要忽略读事件呢?因为http请求行、请求头部都已经全部接收完成了, 现在要做的是调度各个http模块共同协作,完成对接收到的请求行,请求头部的处理。因此不需要接收来自客户端任何数据了。
对于写事件的处理就复杂多了, 在event的写事件发生时,epoll返回后会调用写事件的回调ngx_http_request_handler,在这个写事件回调中,又会调用http框架,也就是http请求对象的写事件回调ngx_http_core_run_phases。这个http框架的回调会调度介入11个请求阶段的各个http模块的hander方法,共同完成http请求。
二、调度http模块处理请求
在上面代码中,会调度ngx_http_core_run_phases这个函数,使得各个http模块能介入到http请求中来。而这个函数是在ngx_http_handler设置的。
//调用各个http模块协同处理这个请求
void ngx_http_handler(ngx_http_request_t *r)
{
//不需要进行内部跳转。什么是内部跳转? 例如有个location结构,里面的
//
if (!r->internal)
{
//将数组序号设为0,表示从数组第一个元素开始处理http请求
//这个下标很重要,决定了当前要处理的是11个阶段中的哪一个阶段,
//以及由这个阶段的哪个http模块处理请求
r->phase_handler = 0;
}
else
{
//需要做内部跳转
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
//将序号设置为server_rewrite_index
r->phase_handler = cmcf->phase_engine.server_rewrite_index;
}
//设置请求对象的写事件回调,这个回调将会调度介入11个http阶段的各个http模块
//共同完成对请求的处理
r->write_event_handler = ngx_http_core_run_phases;
//开始调度介入11个http阶段的各个http模块
ngx_http_core_run_phases(r);
}而ngx_http_core_run_phases函数就很简单了,调度介入11个http处理阶段的所有http模块的checker方法。
//调用各个http模块协同处理这个请求, checker函数内部会修改phase_handler
void ngx_http_core_run_phases(ngx_http_request_t *r)
{
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
ph = cmcf->phase_engine.handlers;
//调用各个http模块的checker方法,使得各个http模块可以介入http请求
while (ph[r->phase_handler].checker)
{
rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
//从http模块返回ngx_ok,http框架则会把控制全交还给事件模块
if (rc == ngx_ok)
{
return;
}
}假设阶段2有三个http模块介入了http请求, 阶段3有一个模块介入了http请求、阶段4也有一个模块介入了请求。当开始处理阶段2时,将调用阶段2中的所有http模块进行处理,此时phase_handler指向阶段2的开始位置。之后每处理完阶段2中的一个模块时,phase_handler指向阶段2的下一个模块,直到阶段2处理完成。
当阶段2中的所有http模块都处理完成时,phase_handler将指向阶段3
因阶段3只有一个http模块,因此当阶段3中的所有http模块都处理完成时,phase_handler将指向阶段4
那这个handlers数组是什么时候创建的呢? 每一个http模块的checker回调又是做什么呢? 接下来将分析这两个问题
三、11个http请求阶段数组创建
在解析nginx.conf配置文件时,解析到http块时,会调用ngx_http_block这个函数开始解析http块。在这个函数中,也会把所有需要介入到11个http请求阶段的http模块,注册到数组中。
//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//http配置解析完成后的后续处理,使得各个http模块可以介入到11个http阶段
for (m = 0; ngx_modules[m]; m++)
{
if (ngx_modules[m]->type != ngx_http_module)
{
continue;
}
module = ngx_modules[m]->ctx;
if (module->postconfiguration)
{
//每一个http模块的在这个postconfiguration函数中,都可以把自己注册到11个http阶段
if (module->postconfiguration(cf) != ngx_ok)
{
return ngx_conf_error;
}
}
}
}例如ngx_http_static_module静态模块,会将自己介入11个http阶段的ngx_http_content_phase阶段回调设置为ngx_http_static_handler
//静态模块将自己注册到11个http请求阶段中的ngx_http_content_phase阶段
static ngx_int_t ngx_http_static_init(ngx_conf_t *cf)
{
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[ngx_http_content_phase].handlers);
//静态模块在ngx_http_content_phase阶段的处理方法
*h = ngx_http_static_handler;
return ngx_ok;
}例如: ngx_http_access_module访问权限模块,会将自己介入11个http阶段的ngx_http_access_phase阶段回调设置为ngx_http_access_handler
//访问权限模块将自己注册到11个http请求阶段中的ngx_http_access_phase阶段
static ngx_int_t ngx_http_access_init(ngx_conf_t *cf)
{
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[ngx_http_access_phase].handlers);
//访问权限模块在ngx_http_access_phase阶段的处理方法
*h = ngx_http_access_handler;
return ngx_ok;
}上面的这些操作,只是把需要介入到11个http阶段的http模块保存到了ngx_http_core_main_conf_t中的phases成员中,并没有保存到phase_engine中。那什么时候将phases的内容保存到phase_engine中呢? 还是在ngx_http_block函数中完成
//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//初始化请求的各个阶段
if (ngx_http_init_phase_handlers(cf, cmcf) != ngx_ok)
{
return ngx_conf_error;
}
}假设阶段1有一个http模块介入请求,阶段2有三个http模块介入请求、阶段3也有一个http模块介入请求。则ngx_http_init_phase_handlers这个函数调用后,从ngx_http_phase_t phases[11]数组转换到ngx_http_phase_handler_t handlers数组的过程如下图所示:
//初始化请求的各个阶段
static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
//11个http请求阶段,每一个阶段都可以有多个http模块介入。
//这里统计11个节点一共有多个少http模块。以便下面开辟空间
for (i = 0; i < ngx_http_log_phase; i++)
{
n += cmcf->phases[i].handlers.nelts;
}
//开辟空间,存放介入11个处理阶段的所有http模块的回调
ph = ngx_pcalloc(cf->pool,n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
cmcf->phase_engine.handlers = ph;
n = 0;
//对于每一个http处理阶段,给该阶段中所有介入的http模块赋值
for (i = 0; i < ngx_http_log_phase; i++)
{
h = cmcf->phases[i].handlers.elts;
switch (i)
{
case ngx_http_server_rewrite_phase://根据请求的uri查找location之前,修改请求的uri阶段
if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1)
{
cmcf->phase_engine.server_rewrite_index = n; //重定向模块在数组中的位置
}
checker = ngx_http_core_rewrite_phase; //每一个阶段的checker回调
break;
case ngx_http_find_config_phase://根据请求的uri查找location阶段(只能由http框架实现)
find_config_index = n;
ph->checker = ngx_http_core_find_config_phase;
n++;
ph++;
continue;
case ngx_http_rewrite_phase: //根据请求的rui查找location之后,修改请求的uri阶段
if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1)
{
cmcf->phase_engine.location_rewrite_index = n;
}
checker = ngx_http_core_rewrite_phase;
break;
case ngx_http_post_rewrite_phase: //ngx_http_rewrite_phase阶段修改rul后,防止递归修改uri导致死循环阶段
if (use_rewrite)
{
ph->checker = ngx_http_core_post_rewrite_phase;
ph->next = find_config_index;//目的是为了地址重写后,跳转到ngx_http_find_config_phase阶段,根据
//url重写查找location
n++;
ph++;
}
continue;
case ngx_http_access_phase: //是否允许访问服务器阶段
checker = ngx_http_core_access_phase;
n++;
break;
case ngx_http_post_access_phase: //根据ngx_http_access_phase阶段的错误码,给客户端构造响应阶段
if (use_access)
{
ph->checker = ngx_http_core_post_access_phase;
ph->next = n;
ph++;
}
continue;
case ngx_http_try_files_phase: //try_file阶段
if (cmcf->try_files)
{
ph->checker = ngx_http_core_try_files_phase;
n++;
ph++;
}
continue;
case ngx_http_content_phase: //处理http请求内容阶段,大部分http模块最愿意介入的阶段
checker = ngx_http_core_content_phase;
break;
default:
//ngx_http_post_read_phase,
//ngx_http_preaccess_phase,
//ngx_http_log_phase三个阶段的checker方法
checker = ngx_http_core_generic_phase;
}
n += cmcf->phases[i].handlers.nelts;
//每一个阶段中所介入的所有http模块,同一个阶段中的所有http模块有唯一的checker回调,
//但handler回调每一个模块自己实现
for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--)
{
ph->checker = checker;
ph->handler = h[j];
ph->next = n;
ph++;
}
}
return ngx_ok;
}四
、http阶段的checker回调
在11个http处理阶段中,每一个阶段都有一个checker函数,当然有些阶段的checker函数是相同的。对每一个处理阶段,介入这个阶段的所有http模块都共用同一个checker函数。这些checker函数的作用是调度介入这个阶段的所有http模块的handler方法,或者切换到一下个http请求阶段。下面分析下ngx_http_post_read_phase,ngx_http_preaccess_phase,ngx_http_log_phase三个阶段的checker方法。
//ngx_http_post_read_phase,
//ngx_http_preaccess_phase,
//ngx_http_log_phase三个阶段的checker方法
//返回值: ngx_ok,http框架会将控制权交还给epoll模块
ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r,ngx_http_phase_handler_t *ph)
{
ngx_int_t rc;
//调用http模块的处理方法,这样这个http模块就介入到了这个请求阶段
rc = ph->handler(r);
//跳转到下一个http阶段执行
if (rc == ngx_ok)
{
r->phase_handler = ph->next;
return ngx_again;
}
//执行本阶段的下一个http模块
if (rc == ngx_declined)
{
r->phase_handler++;
return ngx_again;
}
//表示刚执行的handler无法在这一次调度中处理完这一个阶段,
//需要多次调度才能完成
if (rc == ngx_again || rc == ngx_done)
{
return ngx_ok;
}
//返回出错
/* rc == ngx_error || rc == ngx_http_... */
ngx_http_finalize_request(r, rc);
return ngx_ok;
}
# nginx
# Event
# 对象
# 事件
# http
# 回调
# 设置为
# 在这个
# 的是
# 会将
# 多个
# 将会
# 跳转
# 是在
# 也有
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
Python面向对象测试方法_mock解析【教程】
javascript如何操作浏览器历史记录_怎样实现无刷新导航
Laravel如何使用Blade组件和插槽?(Component代码示例)
潮流网站制作头像软件下载,适合母子的网名有哪些?
如何快速搭建高效WAP手机网站?
详解Android图表 MPAndroidChart折线图
如何正确选择百度移动适配建站域名?
如何为不同团队 ID 动态生成多个非值班状态按钮
Laravel如何发送系统通知_Laravel Notifications实现多渠道消息通知
Claude怎样写结构化提示词_Claude结构化提示词写法【教程】
如何用腾讯建站主机快速创建免费网站?
如何在不使用负向后查找的情况下匹配特定条件前的换行符
三星网站视频制作教程下载,三星w23网页如何全屏?
Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置
如何解决hover在ie6中的兼容性问题
使用豆包 AI 辅助进行简单网页 HTML 结构设计
原生JS实现图片轮播切换效果
胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?
PHP正则匹配日期和时间(时间戳转换)的实例代码
Linux系统运维自动化项目教程_Ansible批量管理实战
香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧
VIVO手机上del键无效OnKeyListener不响应的原因及解决方法
网站建设保证美观性,需要考虑的几点问题!
深圳网站制作培训,深圳哪些招聘网站比较好?
Javascript中的事件循环是如何工作的_如何利用Javascript事件循环优化异步代码?
如何彻底删除建站之星生成的Banner?
湖南网站制作公司,湖南上善若水科技有限公司做什么的?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
如何制作公司的网站链接,公司想做一个网站,一般需要花多少钱?
深圳网站制作平台,深圳市做网站好的公司有哪些?
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
如何用西部建站助手快速创建专业网站?
用v-html解决Vue.js渲染中html标签不被解析的问题
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
iOS中将个别页面强制横屏其他页面竖屏
千库网官网入口推荐 千库网设计创意平台入口
移动端脚本框架Hammer.js
Laravel如何使用.env文件管理环境变量?(最佳实践)
如何获取上海专业网站定制建站电话?
在线教育网站制作平台,山西立德教育官网?
如何在Windows 2008云服务器安全搭建网站?
Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】
如何用wdcp快速搭建高效网站?
千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】
Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
Laravel如何使用Eloquent进行子查询
Laravel队列由Redis驱动怎么配置_Laravel Redis队列使用教程
上一篇:python添加模块搜索路径方法
下一篇:《爱南宁》查询地铁路线方法
上一篇:python添加模块搜索路径方法
下一篇:《爱南宁》查询地铁路线方法

