nginx命名location跳转的模块上下文继承

目录

  • 1. 缘起
  • 2. 解决方案
    • 2.1 保留指定模块的上下文信息
    • 2.2 获取指定模块的上下文信息
    • 2.3 设置指定模块的上下文信息
    • 2.4 设置模块上下文是否需要继承标记
    • 2.5 对openrety lua代码的支持

1. 缘起

  nginx提供了非常棒的功能,命名location,如文章nginx的location匹配规则中描述,有时候我们可以通过lua脚本(在openresty中)或者自研nginx插件模块,根据相应的业务规则将某些请求转发到特定的命名location中执行相应的业务逻辑。

  假设我们的location配置如下:

	location / {content_by_lua_block { local uri = ngx.var.uri if string.match(uri, "%.mp4$") then ngx.exec("@mp4") elseif string.match(uri, "%.flv$") thenngx.exec("@flv")elsengx.exit(ngx.HTTP_NOT_FOUND)end}}location @mp4 {internal;mp4;           # 开启mp4流媒体功能root ./html;}location @flv {internal;flv;           # 开启flv流媒体功能root ./html;}

  那么nginx会在匹配到以.mp4为后缀的uri时候将请求转发到@mp4的location,当匹配到.flv为后缀的uri时候将请求转发到@flv的location,否则响应404。当然,大家可能认为好像没有必要那么复杂,直接用[[nginx的location匹配规则]]中说的那样直接用location匹配也可以达到以上目的。本案例只是一个简化的情况,如果是在一个提供多租户服务的CDN系统中,一个边缘cache(cache前端可以用nginx来提供)需要配置成千上万的域名,每个域名都会有不同的location规则,如果每个域名都配置一个server,那么会给nginx带来比较大的配置加载的负担,我们一般的实现是只有一个server,一个location匹配所有客户的域名和location,然后,通过lua程序将用户的请求根据动态配置信息转发到几个预先设置好的location中提供不同的服务,譬如MP4流媒体location,flv流媒体location,大文件下载location等等。
  这种情况下,不一定每个客户的mp4文件都是以mp4为后缀的,flv文件是以flv为后缀的,而是需要根据客户的在线配置需求动态配置的,所以可以通过lua程序根据来源域名匹配到相应的规则然后将请求动态转发到对应的命名location中。
  然而,在实践中,我们发现nginx的命名转发功能,会把http模块的上下文信息清空,导致在命名location中ngx_http_reqeust_t对象获取不到转发前的模块上下文,从而使转发前和转发后的上下文信息无法传递,带来一些困扰。nginx的原生代码如下:

ngx_int_t
ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
{ngx_http_core_srv_conf_t    *cscf;ngx_http_core_loc_conf_t   **clcfp;ngx_http_core_main_conf_t   *cmcf;r->main->count++;r->uri_changes--;if (r->uri_changes == 0) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"rewrite or internal redirection cycle ""while redirect to named location \"%V\"", name);ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return NGX_DONE;}if (r->uri.len == 0) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"empty URI in redirect to named location \"%V\"", name);ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return NGX_DONE;}cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);if (cscf->named_locations) {for (clcfp = cscf->named_locations; *clcfp; clcfp++) {ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"test location: \"%V\"", &(*clcfp)->name);if (name->len != (*clcfp)->name.len|| ngx_strncmp(name->data, (*clcfp)->name.data, name->len) != 0){continue;}ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"using location: %V \"%V?%V\"",name, &r->uri, &r->args);r->internal = 1;r->content_handler = NULL;r->uri_changed = 0;r->loc_conf = (*clcfp)->loc_conf;/* clear the modules contexts *//* 清理本request的所有模块的上下文 */ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);ngx_http_update_location_config(r);cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);r->phase_handler = cmcf->phase_engine.location_rewrite_index;r->write_event_handler = ngx_http_core_run_phases;ngx_http_core_run_phases(r);return NGX_DONE;}}ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"could not find named location \"%V\"", name);ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return NGX_DONE;
}

  上述代码中,有一行代码:

    ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);

  它负责清理当前http request的所有模块的上下文信息,从而导致转发前设置的上下文信息,在转发后再去获取的时候就变成了NULL。

2. 解决方案

2.1 保留指定模块的上下文信息

   将函数ngx_http_named_location中的这行代码

ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);

   改成:

/* clear only the modules contexts which are not derivable */for (i = 0; i < ngx_http_max_module; i++){if ( ((uintptr_t)r->ctx[i] & (uintptr_t)0x01u) == 0 ){r->ctx[i] = NULL;}

   意思是如果http request的某个ctx元素中保存的指针地址如果最低位是0,才清理上下文,否则保留上下文信息。因为在系统中指针地址至少是按4byte对齐的,所以最低的两位一定是0,我们这里就是用最低位的0来表示是否需要在命名location跳转的时候保留对应模块的保留上下文信息。

2.2 获取指定模块的上下文信息

   如上文描述,由于上下文指针地址中的值可能复用的一个标记位,实际值不是对应的真正的上下文内存地址,所以需要对原先获取模块上下文信息的宏定义进行改造,原来为:

#define ngx_http_get_module_ctx(r, module)  (r)->ctx[module.ctx_index]

  改成:

#define ngx_http_get_module_ctx(r, module) \(void*)((uintptr_t)(r)->ctx[module.ctx_index] & ~(uintptr_t)1u)

2.3 设置指定模块的上下文信息

   原生的设定上下文代码如下:

#define ngx_http_set_ctx(r, c, module)      r->ctx[module.ctx_index] = c;

这块沿用原生的代码,不用更改。

2.4 设置模块上下文是否需要继承标记

   代码如下:

#define ngx_http_set_ctx_derivable(r,module) \r->ctx[module.ctx_index] = (void*)  \((uintptr_t)ngx_http_get_module_ctx(r,module) | (uintptr_t)1u)#define ngx_http_unset_ctx_derivable(r,module) \r->ctx[module.ctx_index] = (void*) \((uintptr_t)ngx_http_get_module_ctx(r,module) & ~(uintptr_t)1u)

  程序逻辑需要设定某个模块可以被named location跳转继承,那么就调用

ngx_http_set_ctx_derivable(r, module_name)

&emps;  反之,则调用:

ngx_http_unset_ctx_derivable(r, module_name)

2.5 对openrety lua代码的支持

   以上代码修改完成后,已经可以完美支持nginx的c插件模块的上下文的继承设置了,但是对于openresty lua代码,我们还需要对openresty开放相关的接口,或者如果希望强制openresty lua模块每次在named location跳转的时候都需要继承上下文信息,那么可以修改ngx_http_lua_handle_exec代码,如下:


static ngx_int_t
ngx_http_lua_handle_exec(lua_State *L, ngx_http_request_t *r,ngx_http_lua_ctx_t *ctx)
{ngx_int_t               rc;ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"lua thread initiated internal redirect to %V",&ctx->exec_uri);ngx_http_lua_cleanup_pending_operation(ctx->cur_co_ctx);ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 1);ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD;if (r->filter_finalize) {ngx_http_set_ctx(r, ctx, ngx_http_lua_module);}ngx_http_lua_request_cleanup(ctx, 1 /* forcible */);if (ctx->exec_uri.data[0] == '@') {if (ctx->exec_args.len > 0) {ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,"query strings %V ignored when exec'ing ""named location %V",&ctx->exec_args, &ctx->exec_uri);}r->write_event_handler = ngx_http_request_empty_handler;#if 1if (r->read_event_handler == ngx_http_lua_rd_check_broken_connection) {/* resume the read event handler */r->read_event_handler = ngx_http_block_reading;}
#endif/* 设置lua模块的ctx在named_location跳转的时候保持原始的ctx */ngx_http_set_ctx_derivable(r, ngx_http_lua_module);rc = ngx_http_named_location(r, &ctx->exec_uri);if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {return rc;}
......return NGX_DONE;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/681335.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

理解JAVA EE设计模式

理解JAVA EE设计模式 在Web应用程序的设计和开发阶段,开发人员在开发类似的项目时可能会遇到相似的问题。每名开发人员可能会遇到的问题找出不同或相似的解决方案。但是,这导致一些时间和精力浪费在为相似的问题寻找解决方案上。因此,要啊节省时间和精力,需要记录常见问题…

Vulnhub靶场 DC-9

目录 一、环境搭建 二、信息收集 1、主机发现 2、指纹识别 三、漏洞复现 1、dirsearch目录探测 2、sqlmap注入测试 3、文件包含漏洞 4、Knockd敲门服务 5、ssh爆破 ​​​​​​​6、提权 四、提取flag 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a;https://…

红队打靶练习:Alfa:1

下载连接点击此处即可&#xff01; 目录 信息收集 1、arp 2、nmap 3、gobuster WEB web信息收集 FTP登录 smaba服务 crunch密码生成 提权 系统信息收集 权限提升 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, …

消息中间件面试篇

消息中间件 RabbitMQ 消息不丢失 可能导致消息丢失的情况&#xff1a; 生产者发送消息丢失消息队列宕机消费者服务宕机&#xff0c;未接收到消息 生产者确认机制 该机制解决了生产者发送消息有可能丢失的问题。 RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程…

Unity下使用Sqlite

sqlite和access类似是文件形式的数据库&#xff0c;不需要安装任何服务&#xff0c;可以存储数据&#xff0c;使用起来还是挺方便的。 首先需要安装DLL 需要的DLL 我们找到下面两个文件放入Plugins目录 Mono.Data.Sqlite.dll System.Data.dll DLL文件位于Unity的安装目录下的…

「MySQL」事务

简介 定义&#xff1a;事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败 基本操作 方式1 查看/设置事务提交方式 select…

LeetCode、1143. 最长公共子序列【中等,二维DP】

文章目录 前言LeetCode、1143. 最长公共子序列【中等&#xff0c;二维DP】题目链接与分类思路2022年暑假学习思路及题解二维DP解决 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者…

Day32 贪心算法part02

买卖股票的最佳时机 太牛了我&#xff0c;随随便便双指针秒杀 md题解里面双指针都没用直接for循环秒杀 跳跃游戏 写成这样纯粹是没有看到第一次跳跃必须从第一个开始 class Solution:def canJump(self, nums: List[int]) -> bool:if len(nums) 1:return Truefor i in …

autojs自动化刷视频脚本

视频展示 视频 //悬浮窗 // var window floaty.rawWindow( // <frame gravity"center" bg"#ff00ff"> // <button id"action" w"300dp" h"300dp"> // 按钮 // </button> // </fram…

python+django咖啡网上商城网站

全网站共设计首页、咖啡文化、咖啡商城、个人信息、联系我们5个栏目以及登录、注册界面&#xff0c;让用户能够全面的了解中国咖啡咖啡文化宣传网站以及一些咖啡知识、文化。 栏目一首页&#xff0c;主要放置咖啡的起源及发展进程的图文介绍&#xff1b;栏目二咖啡文化&#xf…

第三百二十三回

文章目录 1. 概念介绍2. 使用方法2.1 基本用法2.2 缓冲原理 3. 示例代码4. 内容总结 我们在上一章回中介绍了"FadeInImage组件"相关的内容&#xff0c;本章回中将介绍CachedNetworkImage组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

Linux-Vim的使用,快速入门Vim,Linux入门教程,精讲Linux

Vim的三种模式 输入模式&#xff0c;键入 i 或 a 或 o 都可以进入输入模式。 普通模式&#xff0c;打开Vim默认的模式。 命令模式&#xff0c;键入 : 进入命令模式。 注意&#xff1a;按下 ESC 可以从输入模式或命令模式退回到普通模式 退出 vim &#xff0c;需要在普通模式下…

InnoDB主键索引的B+Tree高度是多少?存储数据量是多少?

BTree示意图如下&#xff1a; 图片来源&#xff1a;BTree和BTree详解_bbrtt的结构图-CSDN博客 假设一行数据大小是1k&#xff0c;一页可以存储16行这样的数据。InnoDB的指针占用6个字节的空间&#xff0c;主键假设为bigint&#xff0c;占用字节数为8&#xff0c;那么可得公式…

Day 44 | 动态规划 完全背包、518. 零钱兑换 II 、 377. 组合总和 Ⅳ

完全背包 题目 文章讲解 视频讲解 完全背包和0-1背包的区别在于&#xff1a;物品是否可以重复使用 思路&#xff1a;对于完全背包问题&#xff0c;内层循环的遍历方式应该是从weight[i]开始一直遍历到V&#xff0c;而不是从V到weight[i]。这样可以确保每种物品可以被选择多次…

unity 点击事件

目录 点击按钮&#xff0c;显示图片功能教程 第1步添加ui button&#xff0c;添加ui RawImage 第2步 添加脚本&#xff1a; 第3步&#xff0c;把脚本拖拽到button&#xff0c;点击button&#xff0c;设置脚本的变量&#xff0c; GameObject添加 Component组件 点击按钮&am…

如何才能学好JVM?——零基础入门篇

1. JVM是什么&#xff1f; JVM是Java Virtual Machine的简称&#xff0c;它是一个虚拟的计算机&#xff0c;专门为执行Java程序而设计。 你可以想象它是一个能够运行Java字节码的平台&#xff0c;无论你的程序在Windows、Mac还是Linux上&#xff0c;它们都能通过JVM在这些系统…

linuxqq关闭主面板后无法再次打开的问题

文章目录 前言解决方案强调一点 前言 听说QQ出了linux版&#xff0c;所以来试试。结果试试就逝世。这次记录一个关闭后没办法打开的解决办法。 解决方案 刚安装好后如果点了关闭&#xff0c;系统托盘里也没有&#xff0c;点击图标又是重新登录。当然&#xff0c;我们最简单、…

C语言—简单的if语句

1.输入你的身高和体重&#xff0c;测试你的健康状况。 计算bmi的值&#xff0c; bmi &#xff08;体重/身高的平方) 如果bmi 小于18.5&#xff0c;则显示“偏瘦&#xff0c;注意加强营养” 如果bmi 在18.…

5.深入理解箭头函数 - JS

什么是箭头函数&#xff1f; 箭头函数是指通过箭头函数表达式创建的函数&#xff0c;是匿名函数。 箭头函数表达式的语法更简洁&#xff0c;但语义有差异&#xff0c;所以用法上也有一些限制。尽管如此&#xff0c;箭头函数依旧被广泛运用在需要执行“小函数”的场景。 箭头…

Paper - VQGAN: Taming Transformers for High-Resolution Image Synthesis 简读

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136055085 VQGAN: Taming Transformers for High-Resolution Image Synthesis, CVPR 2021 VQGAN: 改良 Transformer 模型以实现高清图像合成 源码…