Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求
off999 2025-01-10 16:06 19 浏览 0 评论
重定向与内部子请求
Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx的rewrite指令的功能之外,还能顺利完成内部子请求、并发子请求等复杂功能。
实战案例运行准备:本节涉及的配置文件为源码工程的nginxlua-demo.conf文件。在运行本节实例前需要修改启动脚本openrestystart.bat(或openresty-start.sh)中的PROJECT_CONF变量的值,将其改为nginx-lua-demo.conf,然后重启OpenRestry。
Nginx Lua内部重定向
ngx_lua模块可以实现Nginx的rewrite指令类似的功能,该模块提供了两个对应的API来实现重定向的功能,主要有:
(1)ngx.exec(uri,args?):内部重定向。
(2)ngx.redirect(uri,status?):外部重定向。
首先看第一个ngx.exec(uri,args?)内部重定向方法,其等价于下面的rewrite指令:
rewrite regrex replacement last;下面是3个使用ngx.exec进行重定向的例子。
第一个例子是一个不带参数的重定向:
#重定向到/internal/sum
ngx.exec('/internal/sum');
第二个例子是一个使用字符串作为追加参数的重定向:
#重定向到/internal/sum?a=3&b=5,并且追加参数c=6
ngx.exec('/internal/sum?a=3&b=5', 'c=6');
第三个例子是一个使用Lua table作为追加参数的重定向:#重定向到/internal/sum,并且追加参数 ?a=3&b=5&c=6
ngx.exec('/internal/sum', {a=3, b=5,c=6});下面是一个完整的ngx.exec重定向的演示例子,通过内部重定向完成3个参数的累加,具体代码如下:
location /internal/sum {
internal; #只允许内部调用
content_by_lua_block {
--通过ngx.var访问Nginx变量
local arg_a = tonumber(ngx.var.arg_a);
local arg_b = tonumber(ngx.var.arg_b);
local arg_c = tonumber(ngx.var.arg_c);
--3个参数值求和
local sum = arg_a + arg_b+ arg_c;
--输出结果
ngx.say(arg_a, "+", arg_b, "+", arg_c, "=",sum);
}
}
location /sum {
content_by_lua_block {
-- local res = ngx.exec("/internal/sum", 'a = 100&b=10&c=1');
-- 内部重定向到/internal/sum
return ngx.exec("/internal/sum", {a = 100, b = 10, c = 1});
}
}以上代码处于nginx-lua-demo.conf文件中,修改后需重启OpenRestry,然后可以使用浏览器访问/sum,具体的访问结果如图8-15所示。
ngx.exec的使用需要注意以下两点:
(1)如果有args参数,参数可以是字符串的形式,也可以是Luatable的形式,代码如下:
ngx.exec("/internal/sum",'a=100&b=5'); --参数是字符串的形式
ngx.exec("/internal/sum", {a=100, b=5}); --参数是Lua table的形式(2)该方法可能不会主动返回,因此建议在调用该方法时显式加上return,代码如下:
return ngx.exec(...)Nginx Lua外部重定向
ngx_lua模块的外部重定向方法为ngx.redirect,它的语法格式如下:
ngx.redirect(uri, status?)ngx.redirect外部重定向方法与ngx.exec内部重定向方法不同,外部重定向将通过客户端进行二次跳转,所以ngx.redirect方法会产生额外的网络流量,该方法的第二个参数为响应状态码,可以传递301/302/303/307/308重定向状态码。其中,301、302是HTTP 1.0协议定义的响应码,303、307、308是HTTP 1.1协议定义的响应码。
如果不指定status值,那么该方法默认的响应状态为302(ngx.HTTP_MOVED_TEMPORARILY)临时重定向。下面是一个通过ngx.redirect方法与rewrite指令达到一模一样跳转效果的实例,代码如下:
location /sum2 {
content_by_lua_block {
-- 外部重定向
return ngx.redirect("/internal/sum?a=100&b=10&c=1");
}
}
location /sum3 {
rewrite ^/sum3 "/internal/sum?a=100&b=10&c=1" redirect;
}以上代码处于nginx-lua-demo.conf文件中,修改后需要重启OpenRestry,然后可以使用浏览器访问/sum2或者/sum3,具体的访问结果如图8-16所示。
如果指定status值为301,那么对应的常量为ngx.HTTP_MOVED_PERMANENTLY永久重定向,对应到rewrite指令的标志位为permanent。下面的例子中,ngx.redirect方法与rewrite指令达到了一模一样的跳转效果,代码如下:
location /sum4 {
content_by_lua_block {
-- 外部重定向
return ngx.redirect("/internal/sum?a=100&b=10&c=1", ngx.HTTP_MOVED_PERMANENTLY);
}
}
location /sum5{
rewrite ^/sum5 "/internal/sum?a=100&b=10&c=1" permanent;
}由于通过浏览器访问时已经发生了二次跳转,因此它的“检查”面板已经查看不到跳转前链接(如/sum4、/sum5)的响应码,但是可以通过抓包工具查看。/sum5的响应码具体如图8-17所示。
下面有一个综合性的跳转演示实例,通过ngx.redirect方法与rewrite指令进行3种方式的外部跳转,跳转到博客园网站(www.cnblogs.com)。
具体的代码如下:
#使用location指令后面的正则表达式进行URL后缀捕获
location ~*/blog/(.*) {
content_by_lua_block {
--使用ngx.redirect方法进行外部重定向
--博客URI为正则捕获组1
return ngx.redirect("https://www.cnblogs.com/"..ngx.var[1]);
}
}
location ~*/blog1/*{
#使用rewrite指令后面的正则表达式进行URL后缀捕获
rewrite ^/blog1/(.*) $1 break;
content_by_lua_block {
--使用ngx.redirect方法进行外部重定向
--博客URI为正则捕获组1
return ngx.redirect("https://www.cnblogs.com/"..ngx.var[1]);
}
}
location ~*/blog2/*{
#使用rewrite指令进行外部重定向,并捕获博客URI
rewrite ^/blog2/(.*) https://www.cnblogs.com/$1 redirect;
}
}以疯狂创客圈社群的博客首页为例,外部跳转演示需要用到的4个地址,分别如下:
#以下为疯狂创客圈社群的博客首页,原地址为
https://www.cnblogs.com/crazymakercircle/p/9904544.html
#以下为 /blog/(.*) 配置块的二次跳转演示地址
http://nginx.server/blog/crazymakercircle/p/9904544.html
#以下为 /blog1/*配置块的二次跳转演示地址
http://nginx.server/blog1/crazymakercircle/p/9904544.html
#以下为 /blog2/*配置块的二次跳转演示地址
http://nginx.server/blog2/crazymakercircle/p/9904544.html通过浏览器访问以上二次跳转演示地址(主机名nginx.server需要指向Nginx的IP),发现都能正常地跳转到原地址(疯狂创客圈社群的博客首页)。
以上代码中,通过location指令、rewrite指令进行了正则捕获,并使用ngx.var[捕获组编号]访问捕获到的捕获组,也就是博客地址的URI部分。
通过浏览器访问以上4个地址,最终的结果都为疯狂创客圈社群的博客首页,只是后面的3个经过了跳转而已。跳转的结果如图8-18所示。
ngx.redirect方法不会主动返回,因此建议在调用该方法时显式加上return,具体如下:
return ngx.redirect("https://www.cnblogs.com/"..ngx.var[1]);ngx.location.capture子请求
Nginx子请求并非HTTP协议的标准实现,是Nginx特有的设计,主要是为了提高内部对单个客户端请求处理的并发能力。
如果某个客户端的请求(可以理解为主请求)访问了多个内部资源,为了提高效率,可以为每一个内部资源访问建立单个子请求,并让所有子请求同时进行。
子请求并不是由客户端直接发起的,它是由Nginx服务器在处理客户端请求时根据自身逻辑需要而内部建立的新请求。因此,子请求只在Nginx服务器内部进行处理,不会与客户端进行交互。
通常情况下,为保护子请求所定义的内部接口,会把这些接口设置为internal,防止外部直接访问。这么做的主要好处是可以让这个内部接口相对独立,不受外界干扰。
发起单个子请求,可以使用的Lua API为ngx.location.capture方法,它的格式如下:
ngx.location.capture (uri, options?)capture方法的第二个参数options是一个table容器,用于设置子请求相关的选项,有如下可以设置的选项:
(1)method:子请求的方法,默认为ngx.HTTP_GET常量。
(2)body:传给子请求的请求体,仅限于string或nil。(3)args:传给子请求的请求参数,支持string或table。
(4)vars:传给子请求的变量表,仅限于table。
(5)ctx:父子请求共享的变量表table。
(6)copy_all_vars:复制所有变量给子请求。
(7)share_all_vars:父子请求共享所有变量。
(8)always_forward_body:用于设置是否转发请求体。
下面是一个综合性实例,包含两个请求接口,具体如下:
外部访问接口:/goods/detail/100?foo=bar。
内部访问接口:/internal/detail/100。外部接口专供外部访问,在准备好必要的请求参数、上下文环境变量、请求体之后,调用内部访问接口获取执行结果,然后返回给客户端。外部接口的演示代码具体如下:
#向外公开的请求
location ~ /goods/detail/([0-9]+) {
set $goodsId $1; #将location的正则捕获组1赋值到变量 $goodsId
set $var1 '';
set $var2 '';
content_by_lua_block {
--解析body参数之前一定要先读取request body
ngx.req.read_body();
--组装uri
local uri = "/internal/detail/".. ngx.var.goodsId;
local request_method = ngx.var.request_method;
--获取父请求的参数
local args = ngx.req.get_uri_args();
local shareCtx = {c1 = "v1", other = "other value"}
local res = ngx.location.capture(uri,{
method = ngx.HTTP_GET,
args = args, --转发父请求的参数给子请求 body = 'customed request body',
vars = {var1 = "value1", var2 = "value2"}, --传递的变量
always_forward_body = true, --转发父请求的request body
ctx = shareCtx, --共享给子请求的上下文table
});
ngx.say(" child res.status :", res.status);
ngx.say(res.body);
ngx.say("<br>shareCtx.c1 =", shareCtx.c1);
}
}内部接口用于模拟上游的服务(如Java容器服务),外部客户端是不能直接访问内部接口的。内部接口的演示代码具体如下:
#内部请求
location ~ /internal/detail/([0-9]+) {
internal; #此指令限制外部客户端是不能直接访问内部接口
#将捕获组1的值放到自定义Nginx变量$goodsId中
set $goodsId $1;
content_by_lua_block {
ngx.req.read_body();
ngx.say(" <br><hr>child start: ");
--访问父请求传递的参数
local args = ngx.req.get_uri_args()
ngx.say(", <br>foo =", args.foo);
--访问父请求传递的请求体
local data = ngx.req.get_body_data()
ngx.say(", <br>data =", data);
--访问Nginx定义的变量
ngx.say(" <br> goodsId =", ngx.var.goodsId);
--访问父请求传递的变量
ngx.say(", <br>var.var1 =", ngx.var.var1);
--访问父请求传递的共享上下文,并修改其属性
ngx.say(", <br>ngx.ctx.c1 =", ngx.ctx.c1);
ngx.say(" <br>child end <hr>");
ngx.ctx.c1 = "changed value by child";
}
}以上代码处于nginx-lua-demo.conf文件中,修改后需重启OpenRestry,然后可以使用浏览器访问/goods/detail/100?foo=bar,具体的访问结果如图8-19所示。
capture方法的第二个参数options是一个table容器,用于设置子请求的选项。options的method属性用于指定子请求的method类型,具体示例如下:
local res = ngx.location.capture(uri,{
method = ngx.HTTP_PUT, --method为PUT类型的请求
...
});method属性值只接收Nginx Lua内部定义的请求类型的常量,如ngx.HTTP_POST表示POST类型的请求,ngx.HTTP_GET表示GET类型的请求。
options的body属性指定子请求的请求体(仅接收字符串值),其请求体的内容仅限于string或nil,具体示例如下:
local res = ngx.location.capture(uri,{
body = ' customed request body', --转发给子请求的请求体
...
});options的args属性用于指定子请求的URI请求参数(可以是字符串或者Lua表容器),具体示例如下:
local res = ngx.location.capture(uri,{
args = ngx.req.get_uri_args(), --将父请求的参数table转发给子请求
...
});上面的例子假定了父请求的类型为HTTP GET,使用ngx.req.get_uri_args()获取父请求的参数列表,原样转发给子请求。
options的vars属性是一个Lua表容器,用于设置传递给子请求中的Nginx变量。具体示例如下:
...
set $var1 ''; #提前定义好变量
set $var2 ''; #提前定义好变量
content_by_lua_block {
...
local res = ngx.location.capture(uri,{
vars = { var1 = "value1", var2 = "value2"}, --传递的Nginx变量
...
});
}在通过vars向子请求中传递Nginx变量时,变量需要提前进行定义,否则将报出变量未定义的错误。
options的ctx上下文属性指定一个Lua表作为子请求的ngx.ctx表。
当然,可以直接将ctx属性值设置为当前请求的ngx.ctx上下文表。
options的ctx使用示例如下:
local c = {c1="v1",other="other value"}
local res = ngx.location.capture(uri,{
...
ctx = c, --设置子请求的ngx.ctx上下文表
});父请求如果修改了ctx表中的成员,那么子请求可以通过ngx.ctx获取;反过来,子请求也可以修改ngx.ctx中的成员,父请求可以通过ctx表获取。通过ctx属性值可以方便地让父请求和子请求进行上下文变量共享。
options的always_forward_body属性用于设置是否转发请求体。当设置为true时,父请求中的请求体request body将转发到子请求。
always_forward_body属性的使用示例如下:
local res = ngx.location.capture(uri,{
method = ngx.HTTP_GET,
always_forward_body = true, --转发父请求的request body
});ngx.location.capture只能发起到当前Nginx服务器的内部路径的子请求,如果需要发起外部HTTP路径的子请求,就需要与location(或者upstream)反向代理配置配合实现。
ngx.location.capture_multi并发子请求
经过解耦之后,微服务架构将提供大量的细粒度接口,一次客户端(例如App、网页端)请求往往调用多个微服务接口才能获取到完整的页面内容。
这种场景下可以通过网关(如Nginx)进行上游接口合并。
在OpenResty中,ngx.location.capture_multi可以用于上游接口合并的场景,该方法可以完成内部多个子请求和并发访问。它的格式如下:
ngx.location.capture_multi ({ {uri, options?}, {uri, options?}, ... })capture_multi可以一次发送多个内部子请求,每一个子请求的参数使用方式与capture方法相同。调用capture_multi前可以把所有的子请求加入一个table容器表中,作为调用参数传入;capture_multi返回后可以将其结果再用花括号“{}”包装成一个table,方便后面的迭代处理。
下面是一个综合性实例,通过capture_multi方法一次并发地请求两个内部接口,具体代码如下:
#发起两个子请求:一个是get;另一个是post
location /capture_multi_demo {
content_by_lua_block {
local postBody = ngx.encode_args({post_k1 = 32, post_k2 = "post_v2"});
local reqs = {};
table.insert(reqs, { "/print_get_param", { args = "a=3&b=4" }});
table.insert(reqs, { "/print_post_param",{ method = ngx.HTTP_POST, body = postBody}});
--统一发并发请求,然后等待结果
local resps = {ngx.location.capture_multi(reqs)};
--迭代结果列表
for i, res in ipairs(resps) do
ngx.say(" child res.status :", res.status,"<br>");
ngx.say(" child res.body :", res.body,"<br><br>");
end
}
}两个内部接口用于模拟上游的服务(如Java容器服务),客户端是不能直接访问内部接口的。两个内部接口的代码具体如下:
#模拟上游接口一:输出get请求的参数
location /print_get_param {
internal;
content_by_lua_block {
ngx.say(" <br><hr>child start: ");
local arg = ngx.req.get_uri_args()
for k, v in pairs(arg) do
ngx.say("<br>[GET ] key:", k, " v:", v)
end
ngx.say(" <br>child end <hr>");
}
}
#模拟上游接口二:输出post请求的参数
location /print_post_param {
internal;
content_by_lua_block {
ngx.say(" <br><hr>child start: ");
ngx.req.read_body() --解析body参数之前一定要先读取body
local arg = ngx.req.get_post_args();
for k, v in pairs(arg) do
ngx.say("<br>[POST] key:", k, " v:", v)
end
ngx.say(" <br>child end <hr>");
}
}两个内部接口的功能很简单,主要是为了获取请求参数(或者请求体),然后输出到客户端。以上代码处于nginx-lua-demo.conf文件中,修改后需要重启OpenRestry,然后可以使用浏览器访问外部接口/capture_multi_demo,具体的访问结果如图8-20所示。
在所有子请求终止之前,ngx.location.capture_multi(...)函数不会返回。此函数的耗时是单个子请求的最长延迟,而不是所有子请求的耗时总和,因为所有子请求是并发执行的。
上面的例子中,利用ngx.location.capture_multi(...)完成了两个子请求并行执行。当两个请求没有先后依赖时,这个方法可以极大地提高请求效率。如果两个请求各自需要500毫秒,顺序执行需要1000毫秒,那么通过并发子请求可以在500毫秒完成两个请求。
本文给大家讲解的内容是Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求
- 下篇文章给大家讲解的是 Nginx/OpenResty详解,Nginx Lua编程, Nginx Lua操作Redis;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
相关推荐
- win7重装系统方法(win7系统重装详细步骤)
-
步骤1、打开云骑士装机大师,点击一键装机下的【立即重装】,检测完毕后点击【下一步】;步骤2、选择windows7下的旗舰版32位,点击【下一步】,自行选择或取消推荐的软件,点击【下一步】;步骤3、备份...
- 电脑桌面啥都没有了怎么回事
-
1、如果我们桌面上什么东西都没有,可以先打开任务管理器,然后顶级左上方的文件,随后新建任务,在打开的界面中输入explorer,点击确认之后,等个几秒钟左右就可以看见桌面上的图标了。 2、另一个方法...
- 如何将电脑恢复出厂设置win7
-
1.首先我们打开电脑找到“计算机”点击打开。2.进入页面然后我们点击“Windows7(C:)”打开C盘。3.我们在C盘界面找到Windows7并点击打开。4.进入到Win7文件夹中找到并双击“Sys...
- u盘存在但是读不出来(u盘显示有内容但读不出来怎么办)
-
u盘能识别,不能读取可能是你关闭了u盘自动读取,取消后即可。步骤:1、在电脑桌面右键点击“计算机”,在出现的菜单中选择“管理”选项2、在弹出的计算机管理窗口,依次打开“计算机管理-服务和应用程序-服务...
- win8家庭中文版下载(windows家庭中文版下载)
-
可以按照以下步骤在Win8上下载和安装Word:1.通过微软官网下载购买,或者通过MicrosoftStore应用商店进行购买和下载。2.下载完成后,打开文件夹,双击setup进行安装。3.安...
- 教大家强制退出苹果id账号(教大家强制退出苹果id账号ipad)
-
1.首先将手机强制关机,并在电脑端安装iTunes并打开。2.用数据线将手机与电脑连接起来,长按手机电源键。3.当出现苹果标志时不要松开电源键,接着按Home键。4.直到屏幕黑屏,松开电源键。5.继续...
- 惠普官网驱动下载官网(惠普驱动官方)
-
在惠普官网下载系统驱动方法如下访问HP官网:www.hp.com找到支持与驱动页面(通常在顶部导航栏的支持或下载中)输入你的HP产品的序列号或选择产品类型和型号选择你的操作系统,然后下载相关的驱动。安...
- 电脑公司取名字大全(电脑行业公司取名)
-
动感网络IT狂人行鱼雷IT网PC宝宝网外有鱼超导技术网PC技术网加点分吧,不然想不出太多,呵呵绿苑计算机协会绿色代表生命,有起航的意义,苑是一个范畴,有地域的意思,是给你们一片天地的意思...
- wifi怎么设置网速快(手机测wifi网速怎么测)
-
wifi加速设置方法步骤如下。1设置网速:浏览器中输入终端地址,输入账号和密码。找到连接设备数目并进行更改,找到信道宽度,选择更快的网速。2提高WiFi网速:进入设置中心,点击WLAN选项。点击高级设...
- 华硕驱动软件叫什么(华硕自带驱动管理软件)
-
q-installer驱动啥意思?华硕q-installer驱动能让用户更加方便地管理各种文件。将会同时显示4个Windows资源管理器的窗口,这样能让用户更加简单地对文件进行一些操作,例如复制,粘贴...
- 懂行的人建议买华为还是惠普电脑
-
华为电脑好。华为笔记本电脑主打的就是轻薄,质感,多屏协同生态,面向商务人群和学生党。目前在售的机型并不多,但每一台机子本身都不错,不过因为被制裁的缘故,定价过高,性价比打折扣。惠普笔记本好,惠普除了家...
- 如何把win系统换成安卓(怎么把系统改为win10)
-
方法/步骤分步:1首先点击打开酷安应用市场;2点击打开位于屏幕右下方的“应用游戏”选项;3选择想要下载的软件或者游戏app,点击进入;4选择“下载”按钮,点击开始下载;5APP下载完成后,系统会自动跳...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
python入门到脱坑 输入与输出—str()函数
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
慕ke 前端工程师2024「完整」
-
失业程序员复习python笔记——条件与循环
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
