前言
本文介绍 OpenResty 的两种正则模式匹配。
首先需要说明的是,OpenResty 套件中包含了两种语法:一种是主要基于 FFI API 实现的 OpenResty 语法,一种是类原生 Lua 脚本语言的语法。
在本文所介绍的内容中,对应以上两种语法的正则模式匹配分别是 ngx.re.find 和 string.find 。
这两种规则起到完全相同的作用:在 subject string 中搜索指定的模式的串,若找到匹配值就返回它的开始位置和结束位置的位数,否则返回两个 nil 空值。需要注意的是,当查找到模式时才会产生两个值,当例如只有一个变量时只会产生开始位置位数或一个 nil 空值。
即使你对 Lua 比较熟悉,也已不再建议使用 string.find 等 Lua 的正则语法。一是因为由于实现不同,Lua 提供的正则表达式的性能相比 ngx.re. 的表现要逊色不少,二是 Lua 的正则语法并不符合 POSIX 规范,而 ngx.re. 则由标准 POSIX 规范进行实现,后者明显更具备通用性和现在意义。
还有一个很重要的原因,相比 string. 的每次都需重新编译一遍,OpenResty 提供的 ngx.re. 规范能够在编译完成后对 Pattern 进行缓存(使用 “o” 参数),并且也能通过 “j” 参数启用 JIT 来进一步提升性能(需 pcre JIT 支持)。
string.find
虽说已经实在没什么要用 string.find 的必要(前浪死在沙滩上),不过我还是打算简单介绍下,因为我现在就是用的这个(原因我在后文会提到)。
-- syntax
from, to, err = string.find(s, pattern, start, [plain])
-- context
init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.\*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
-- example
string.find(ngx.var.http_user_agent, "360")
以上示例的作用就是包含有 “360” 的 UA 进行匹配,匹配命中时返回的值为 匹配串的开始位置和结束位置的位数(从左往右) 。举个例子,使用 ngx.say 对输出值进行显示,先完成以下代码:
-- 定义变量
var = string.find(ngx.var.http_user_agent, "360")
-- 输出
ngx.say("var=" .. var)
把它放到 Nginx 网站的 /example 路径下:
location = /example {
access_by_lua_block {
var = string.find(ngx.var.http_user_agent, "360")
ngx.say("var=" .. var)
}
}
然后使用 curl 测试响应:
# 发个请求,顺便指定 UA 为 360
curl example.com -A "360"
# 返回响应会看到由 ngx.say echo 回来的字符串
# 这里匹配到的 "360" 字符串位于字首,位数是 1
var=1
ngx.re.find
ngx.re.find 规范的优势已经在上文介绍过了,这里介绍下它的基本语法(更多说明可以参看 官方文档 ),以及要发挥它的优势(使用 “o” 参数缓存和使用 pcre JIT)的所需要求。
-- syntax
from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)
-- context
init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.\*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
-- example
ngx.re.find(ngx.var.http_user_agent, "360", "jo")
要使用 ngx.re.* 规范,并且要实现更高性能的话,需要满足三个条件:编译时使用 –with-pcre-jit 参数以启用 pcre JIT 支持;编译时需要 lua-resty-core 支持(直接使用 OpenResty 安装即可);以及使用 Lua 代码时,需要在 init_by_lua 段引入 require 'resty.core.regex' 语句(引入 lua-resty-core API 支持),并在构建代码时将使用 "jo" 参数作为你的习惯,这两个参数提供 pcre JIT 和 Pattern Cache 开关。正如上面 example 中所用的那样。
同样作为前面举例的实现,Lua 代码变成了这样:
-- 定义变量
var = ngx.re.find(ngx.var.http_user_agent, "360", "jo")
-- 输出
ngx.say("var=" .. var)
我的坑
最后来解释下我为什么还在用 string.find 语法。原因比较尴尬,不是我不想用,而是我不能用。我使用了以下代码:
if (ngx.re.find(ngx.var.request_uri, "^/admin/", "jo") ~= nil or ngx.re.find(ngx.var.request_uri, "^/tools/", "jo") ~= nil) then
return ngx.exit(ngx.HTTP_CLOSE)
end
然后我就发现,这个匹配坑我了,我把这段代码单独拿出来时访问 /admin/xxx 或 /tools/xxx 就会被拒,但是我一把它放进代码构筑后就形同虚设。当然我能肯定不是我其它代码的问题,因为换成 string.find 后就好了。
为了确认是不是正则写错的锅,我也做过以下测试:
if (ngx.var.request_uri == "/test1/") then
if (ngx.re.find("/admin/test/", "^/admin/", "jo") ~= nil) then
ngx.say("1=" .. ngx.re.find("/admin/test/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test2/") then
if (ngx.re.find("/admintest/", "^/admin/", "jo") ~= nil) then
ngx.say("2=" .. ngx.re.find("/admintest/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test3/") then
if (ngx.re.find("/artic/", "^/admin/", "jo") ~= nil) then
ngx.say("3=" .. ngx.re.find("/artic/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test4/") then
if (ngx.re.find("/artic", "^/admin/", "jo") ~= nil) then
ngx.say("4=" .. ngx.re.find("/artic", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test5/") then
if (ngx.re.find("/offline/admin/", "^/admin/", "jo") ~= nil) then
ngx.say("5=" .. ngx.re.find("/offline/admin/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test6/") then
if (ngx.re.find("/offline/", "^/admin/", "jo") ~= nil) then
ngx.say("6=" .. ngx.re.find("/offline/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test7/") then
if (ngx.re.find("/admin/", "^/admin/", "jo") ~= nil) then
ngx.say("7=" .. ngx.re.find("/admin/", "^/admin/", "jo"))
end
elseif (ngx.var.request_uri == "/test8/") then
if (ngx.re.find("/adm/in", "^/admin/", "jo") ~= nil) then
ngx.say("8=" .. ngx.re.find("/adm/in", "^/admin/", "jo"))
end
else
if (ngx.var.request_uri == "/test9/") then
if (ngx.re.find("/admin", "^/admin/", "jo") ~= nil) then
ngx.say("9=" .. ngx.re.find("/admin", "^/admin/", "jo"))
end
end
end
测试结果却表明我的写法并没有错,根据 echo 的结果作出的判断是, ^/admin/ 的确对 /admin/xxx 进行了唯一匹配。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。