动态

详情 返回 返回

OpenResty中正則模式匹配的2種方法詳解 - 动态 详情

前言

本文介紹 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 進行了唯一匹配。

Add a new 评论

Some HTML is okay.