Edgelang 語言使用者手冊

目錄

描述

本手冊是 Edge 語言的使用者端文件。

部分本手冊內記錄的特性在當前 edgelang 版本中可能還未實現,edgelang 開發團隊正在快速趕上。如果有疑問,請與 OpenResty Inc. 公司直接聯絡。

本手冊目前依然為草案。有些細節還會更新。

有些大的特性尚未在本手冊中介紹,比如針對很大的請求和響應體的無緩衝的模式匹配和替換。

面向通用 TCP/UDP 代理和 DNS 伺服器的內建的判斷函式和動作函式尚未列出。

習慣用法

為了便於表達,本文中用 edgelang 來代表 Edge 語言。

含有問題的樣例程式碼將在每行開頭前置一個問號 ?。比如:

? true =>
?    say($a = 3);

零件與元件

識別符號

edgelang 裡面的識別符號是一個或多個劃線連線的。一個是一個字母、數字和下劃線組成的序列。不過,下劃線不能出現在字的開頭。下面是一些有效的 edgelang 識別符號的例子:

foo
hisName
uri-prefix
Your_Name1234
a-b_1-c

Edgelang 是大小寫敏感的語言。所以像 fooFoo 這樣的識別符號表示完全不同的東西。

識別符號不能是本語言的關鍵字,但是變數名例外。

回到目錄

關鍵字

Edgelang 有下列關鍵字

ge      gt         le      lt        eq        ne
contains           contains-word     suffix    prefix
my      our        macro   use       INIT      as
rx      wc         qw      phase     END       defer
func    action

回到目錄

變數

Edge 語言變數命名由兩部分組成:一個叫變數字首的特殊字元,後跟一個識別符號。 變數字首用於表示變數的型別。Edge 語言支援下列三種不同的變數字首:

  • $ 用於 標量型別
  • @ 用於 陣列型別
  • % 用於 雜湊/雜湊型別

標量變數儲存簡單的數值,比如數字,字串,布林以及數量等。

陣列變數是包含簡單值或包含其它陣列或雜湊變數的有序的列表。

雜湊變數是一些鍵值對的無序列表。

變數通常用 my 關鍵字宣告。

使用者程式裡定義的每個標量變數,在其生命期裡,只能有一個值型別。 每個變數的值都必須在宣告時顯式指定。下面是七種標量值:

  • Str
  • Num
  • Bool
  • Time
  • Size
  • SizeRate
  • CountRate

使用者必須顯式宣告一個標量變數的型別,如下所示:

my Num $count;
my Str $name;

陣列型別變數必須在宣告時顯式指定其元素的資料型別,如下所示:

my Bool @bool-list;
my Time @time-array;

雜湊型別變數必須在宣告時顯式指定其雜湊 - 鍵型別與雜湊 - 值型別。 雜湊 - 值型別緊跟變數作用域關鍵詞後指定,雜湊 - 鍵型別則在變數名後指定,如下所示:

my Num $city-weight{Str};
my Time $uri-released{Str};

變數宣告的時候可以賦予一個初始值,像下面這樣:

my Num $count = 0;
my Str @domains = qw/ foo.com bar.blah.org /;
my Num %city-weight{Str} = (ShenZhen: 100, Beijing: 50, Shanghai: 30, Guangzhou: 10);

回到目錄

請求範圍內的變數

my 宣告的變數都只有程式碼塊內的作用範圍。 每個請求處理的生命期中的執行中的階段都會有自己的範圍。 如果需要在一個請求的生命期中的多個階段中共享變數,我們可以用 our 替換 my 來宣告客戶的變數。 如下所示:

our Bool $is-mobile;
our Num $my-count;

回到目錄

規則範圍內的變數

使用者可以透過As 表示式引入客戶化定製的規則範圍內的變數。

特殊的子模式捕獲變數,比如 $1$2,$3 這些,也可以透過在規則條件的正則文字內部的捕獲組 (...) 隱含地引入。 跟透過As 表示式引入的變數一樣,這些捕獲變數的作用範圍也是所包含的規則的範圍。

下面是一些例子:

uri-prefix(rx/ \/ ( [a-z]{2} ) \/ ( [a-z]{2} ) /) =>
    say("country: $1, lang: $2");

對於請求 URI /us/en/read.html,這條規則將被觸發並且生成下面的響應體輸出:

country: us, lang: en

對於多條件規則,每個條件都有自己的子模式捕獲變數($1$2 等等)。比如:

uri(rx{ /([a-z]*) });
uri(rx{ /([0-9]*) })
=>
    say("result: $1"),

對於請求 GET /foo,我們會得到

result: foo

而對於請求 GET /123,我們會得到

result: 123

在上面兩種場合,我們都會在特殊變數 $ 裡得到一個有意義的數值。

回到目錄

函式名稱和函式呼叫

Edgelang 大量使用了函式做為規則內的判斷和動作。如果需要,使用者可以定義自己的函式。

函式名稱使用識別符號直接表示,不需要宣告型別印記。

函式呼叫是以函式名跟著一對兒圓括號和圓括號中包圍的任意參數列示的,像下面這樣

say("hello, ", "world")

沒有引數的函式呼叫可以省略圓括號。比如:

uri()

可以簡化成:

uri

引數可以用位置或者名字來傳遞。比如,內建動作函式 say(),像前面的例子說的那樣, 可以接受位置引數,並且當作響應體的一部分輸出。命名引數是透過引數名跟一個冒號做字首傳遞的,如下例

redirect(uri: "/foo", code: 302)

內建函式可能需要以命名引數的方式傳遞一部分引數,以位置引數的方式傳遞另外一部分引數。 具體用法請參考對應內建函式的文件。

至少有一個引數的函式呼叫可以用“方法呼叫”的形式書寫,這個時候第一個參數列現為呼叫者。比如:

say("hello")

可以寫成

"hello".say()

甚至是

"hello".say

多個引數的函式呼叫也可以用類似形式書寫,比如,

say("hello", "world")

語法上等同於:

"hello".say("world")

回到目錄

字串文字

字串文字可以用單引號(') 或者雙引號(")包圍,像下面這樣:

"hello, world!"
'foo 1234'

標量和陣列型別可以在雙引號包圍的字串文字中展開,如:

"Hello, $name!"

所以在雙引號字串裡的文字 $ 需要用 \ 逃逸,以避免多餘的展開,比如:

"Hello, \$name!"

在雙引號字串中支援下列逃逸序列:

\a
\b
\f
\n
\r
\t
\\
\0
\$
\@
\%
\'
\"

單引號包圍的字串內不支援變數展開。另外,它們也只識別下列逃逸序列:

\'
\\

在單引號包圍的文字中出現的任何其它 \ 字元,都會被展開成一個文字的 \

回到目錄

數值常量

數值常量可以用下列方式之一書寫:

1527
3.14159
-32
-3.14
78e-3      (帶一個十進位制指數)
0xBEFF     (十六進位制)
0157       (八進位制)

回到目錄

正則文字

正則文字用語宣告一個 Perl 相容的正規表示式值。 它是透過關鍵字 rx 跟著一個引號結構書寫的。 下面是一些例子:

rx{ hello, \w+}
rx/ [0-9]+ /
rx( [a-zA-Z]* )
rx[ ^/abc ]
rx" \d+ - \d+ "
rx' ([^a-z][a-z]+) '
rx! ([^a-z][a-z]+) !

使用者可以隨意使用花括弧,斜槓,圓括弧,中括號,雙引號或者單引號或者感嘆號來包圍正則文字。 它們的效果都是一樣的,只是在正則文字里對應需要轉義的字元不同。比如,在 rx(...) 裡用斜槓(/) 是不需要轉義的。

預設情況下,除了在字元表結構裡(比如 [a-z]),在正則文字值裡使用空白字元是不影響結果的。 這是為了方便使用者把正則格式化得更容易閱讀。

在正則裡可以宣告一個或多個選項,比如

rx:i/hello/

表示一個對模式 hello 的大小寫無關的匹配。類似的:

rx:s/hello, world/

則令模式字串裡的空白字元影響結果。 多個選項可以用同樣方法堆疊起來使用,比如:

rx:i:s/hello, world/

如果沒有選項,前面的 rx 也可以省略,比如:

/\w+/
/hello world/

在 edge 語言的正則文字中,字元 . 會匹配包含換行符 \n 的任何字元,字元 \s 會匹配包含換行符的任意空字元。

在 edge 語言的正則文字中,變數的插值也是支援的。比如:

my Str $foo = "hello";

rx:s/$foo, world/;

其中需要注意的是,在變數中出現捕獲動作會導致非預期的結果,避免這種用法。

回到目錄

萬用字元文字

萬用字元文字是使用 UNIX 風格的萬用字元,與字串發生匹配模式的文字串。 它是透過關鍵字 wc 跟著一個引號結構書寫的,比如下面這樣:

wc{foo???}
wc/*.foo.com/;
wc(/country/??/);
wc"[a-z].bar.org"
wc'[a-z].*.gov'
wc![a-z].*.gov!

和正則文字一樣,萬用字元文字也可以使用靈活的引號字元。

我們支援三種萬用字元元模式:* 可以用於匹配任意子字串, ? 用於匹配任意單字元,而 [...] 匹配字元表。

萬用字元上可以宣告一個活多個選項,比如:

wc:i/hello/

表示一個模式 hello 的大小寫無關的匹配。

回到目錄

引用字

引用字提供了宣告一個字串文字列表值而不需要敲入太多次包圍字串的引號的簡潔方法。

它使用關鍵字 qw 跟著一系列靈活的引號結構的方式書寫的。 比如:

qw/ foo bar baz /

等效於

"foo", "bar", "baz"

和正則文字、萬用字元文字類似,使用者可以在引用字的引號結構裡選擇各種不同的引號字元, 比如 /, {,(, ", 和 '

回到目錄

量綱

帶單位的量綱是 edgelang 內部的一等公民。量綱是透過一個數字跟著包圍在一對方括號裡的單位來宣告的。 比如,

32 [kB/s]

是“每秒 32K 位元組“的數量。

支援下列時間單位:

  • ssec,或 second

  • ms

    毫秒

  • us

    微秒

  • ns

    納秒

  • min

    分鐘

  • h or hour

    小時

  • d or day

  • month

  • year

rreq 的單位是請求數。

支援下列資料大小的單位:

  • B or Byte

    位元組

  • b or bit

資料大小單位可以接受下列幅度字首:

  • k

    1000 倍

  • K or Ki

    1024 倍

  • m

    1000 * 1000 倍

  • M or Mi

    1024 * 1024 倍

  • g

    1000 1000 1000 倍

  • G or Gi

    1024 1024 1024 倍

  • t

    1000 1000 1000 * 1000 倍

  • T or Ti 1024 1024 1024 * 1024 倍

像資料傳輸速率這樣的組合單位可以用斜槓字元連線資料尺寸單位和時間單位來實現。

數量值可以直接轉換成文字,比如下面這個動作

say(32 [hour])

給出下面的響應輸出:

32 [hour]

我們可以在單位之前使用任意算術表示式,比如:

(1.5 + 2) [kB/s]

輸出一個等於 3.5 [kB/s] 的數量。

內建函式 convert-unit() 可用於轉換量綱的單位,只要新的單位不改變原來量綱的物理含義, 就可以轉換。比如:

convert-unit(1 [hour], 'sec')

會得出新的量綱, 3600 [sec],邏輯上是相等的。

內建的 to-num() 函式可以用於從量綱中抽取數字部分。比如:

to-num(32 [hour])

會返回數字 32

回到目錄

布林值

布林值是使用對內建函式 true()false() 的呼叫分別表示的。 所有關係表示式也會計算得出布林值。

在 edgelang 裡,下列數值會被認為是“條件為假”:

  • 數字 0
  • 字串 “0”
  • false() 的值
  • 一個空字串
  • 一個空列表或者陣列
  • 一個空雜湊表

所有其它數值都被認為是“條件為真”。

函式呼叫 true()false() 經常被簡寫為 truefalse

回到目錄

網路地址

Netaddr/網路地址常量支援 CIDR 格式,可以用下列形式之一書寫:

192.168.1.1
192.168.1.1/32  -- 跟 192.168.1.1 一樣
192.168.1.0/24
::ffff:192.1.56.10/96

網路地址也可以使用 關係運算子

回到目錄

任意串

特殊術語 * 表示一個任意 文字。有些內建函式接受任意文字作為引數。

回到目錄

註釋

註釋以字元 # 開頭,直到當前行行尾。比如:

# 這是一個註釋

也支援塊註釋,比如:

#`(
This is a block
comment...
)

注意,反引號和括號必須是緊跟在 # 後面,註釋塊裡也可以使用括號,比如:

#`(
3 * (2 - (5 - 3))
)

回到目錄

運算子

支援下列運算子,按照優先順序排序:

優先順序              運算子
0                   後環繞 [], {}, <>
1                   **
2                   單目 +/-/~, as
3                   * / % x
4                   + - ~
5                   << >>
6                   &
7                   | ^
8                   單目 !, > < <= >= == !=
                    contains, contains-word, prefix, suffix
                    eq ne lt le gt ge
9                   ..
10                  ?:

使用者可以在一個表示式中用圓括號,(),明確地修改運算子相關的優先順序或者明確地修改運算子關聯順序。

回到目錄

算術運算子

Edgelang 支援下面的二進位制算術運算子:

**      冪運算
*       乘
/       除
%       模除
+       加
-       減

比如:

2 ** (3 * 2)        # 得出 64
(7 - 2) * 5         # 得出 25

還支援單目字首運算子 +- 比如:

+(32 + 1)           # 等於 33
-(3.15 * 2)         # 等於 -6.3

回到目錄

字串運算子

Edgelang 支援下面的二目字串運算子:

x       重複一個字串若干次並且把他們連線起來
~       字串連線

比如:

"abc" x 3           # 得出 "abcabcabc"
"hello" ~ "world"   # 得出 "helloworld"

回到目錄

位運算子

支援下列位運算子:

<<          左移
>>          右移
&           位 AND
|           位 OR
^           位 XOR

單目字首運算子 ~ 用於位的 NOT(非操作)。請不要把它和字串連線的二目運算子 ~ 搞混了。

回到目錄

關係運算子

當前的表示式中,使用關係運算子都會得到一個布林結果。 使用關係運算子的表示式叫做關係表示式

下列二目運算子用於匹配網路地址:

~~          包含
!~~         不包含

例如:

client-addr !~~ 192.168.10.0/24 =>
    say("it's not come from internal network");

first-x-forwarded-addr ~~ any(12.34.56.1/24, 23.45.1.1/16) =>
    say("it comes from the backlist");

下列二目運算子按數字含義對比兩個運算元:

>           大於
<           小於
<=          小於等於
>=          大於等於
==          等於
!=          不等於

下面的二目運算子按照文字含義比較兩個字串值:

gt          大於
lt          小於
le          小於等於
ge          大於等於
eq          等於
ne          不等於

還有 3 個特殊的二目字串運算子用於字串值內部的模式匹配:

contains            如果右側的運算元“包含於”左側的運算元,那麼為真

contains-word       如果右側的運算元作為一個字“包含於”左側的運算元種,那麼為真

prefix              如果右側的運算元是左側運算元的“字首”,那麼為真

suffix              如果右側運算元是左側運算元的“字尾”,那麼為真

單目字首運算子 ! 對布林運算元取反

如果比較運算子的右側運算元看著像一個模式,比如說萬用字元或者正則值,那麼會假設在模式中有匹配錨存在。 比如:

uri eq rx{ /foo } =>
    say("hit");

等效於:

uri contains rx{ \A /foo \z } =>
    say("hit");

這裡的正則模式 \A 只匹配字串開頭,而 \z 只匹配結束。 另外,運算子 contains 不會有任何匹配錨的假設。

類似, contains-word 運算子假設在使用者的正則周圍包含 \b 正則錨。

回到目錄

範圍運算子

二目運算子 .. 可用於形成一個範圍表示式,如下所示

1 .. 5          # 等效於 1, 2, 3, 4, 5
'a'..'d'        # 等效於 `a`, 'b', 'c', 'd'

範圍表示式的值是一個在該範圍內所有獨立值的平面列表。

回到目錄

三元運算子

三元關係運算子 ?: 可以用於根據使用者的條件,在兩個使用者表示式之間選擇。

比如:

$a < 3 ? $a + 1 : $a

這個表示式在 $a < 3 為真時計算出 $a +1 的值,否則給出 $a 的值。

回到目錄

下標運算子

後環繞運算子 [] 可以用於給一個陣列取下標。 比如:

my Str @names = ('Tom', 'Bob', 'John');

true =>
    say(@names[0]),  # 輸出 Tom
    say(@names[1]),  # 輸出 Bob
    say(@names[2]);  # 輸出 John

負數下標用於從陣列尾部訪問元素,比如,-1 是最後一個元素,-2 倒數第二個,以此類推。

類似地,後環繞運算子 {} 用於訪問雜湊表,比如:

my Num %scores{Str} = (Tom: 78, Bob: 100, John: 91);

true =>
    say(%scores{'Bob'});    # output 100

後環繞運算子 {}<> 用於以文字字串鍵字名的方式訪問雜湊表,比如 %scores{"John"} 等效於 %scores<John>, 用法詳見 下標運算子

回到目錄

規則

Edgelang 是一種以規則為基礎的語言。實際上每一個 edgelang 程式都是由一組規則組成。

回到目錄

基礎的規則佈局

edgelang 規則由兩部分組成,一個條件,以及一個結果。條件和結果用 => 連線, 整個規則用一個分號字元終止。基本的規則長得像下面這樣:

<condition> => <consequence>;

規則的條件部分可以使用一個或多個關係表示式,像 resp-status == 200。 所有關係表示式用逗號字元連線(,),這樣的意思是把所有關係表示式在一起,也就是說, 所有關係表示式都為真的時候,整個條件為真。條件不能有副作用,這個效果由 edgelang 編譯器強制要求。 因為這個原因,同一個條件中的關係表示式的計算順序的改變並不影響整個條件的結果。

結果部分通常包含一個或多個動作。每個動作都可以有一些副作用,比如修改一些請求的欄位, 執行一個 302 重定向,或者修改當前請求在後臺的路由等等。我們可以為單個動作宣告一大塊的規則 (參閱動作塊一節)。

下面是一個簡單的 edgelang 規則:

uri("/foo") =>
    redirect(uri: "/bar", code: 302);

在條件部分, uri("/foo") 是關係表示式。接受一些引數的 uri() 函式是一個判斷,意思是它只返回真或者假。 uri("/foo") 執行下列判斷:如果當前請求 URI 精確匹配文字字串 /foo,那麼就返回真; 否則返回假。我們在結果中有一個動作。也就是 redirect() 函式呼叫。 這個函式生成一個 302 HTTP 響應,發起一個外部的重定向到同主機的 /bar URI 上頭。 值得說明的是 uri() 函式接受一個位置引數,而 redirect() 函式接受兩個命名引數。 edgelang 內建的函式可以自行判斷它們是接受位置引數還是命名引數,抑或是兩者皆要。

Edgelang 是自由格式的語言,所以你可以自由使用空白字元。 上面例子中結果前面縮排是不影響語義的,只是為了更佳美觀而採用的。 我們完全可以在一行中寫整個規則,比如:

uri("/foo") => redirect(uri: "/bar", code: 302);

這裡 uri() 函式也可以不接受引數,它返回當前請求 URI 的字串形式,比如:

uri() eq "/foo" =>
    redirect(uri: "/bar", code: 302);

這個例子中的關係表示式 uri() eq "/foo" 等同於前面使用的 uri("/foo") 判斷。 eq 部分是一個二目比較運算子,用於比較兩邊的字串是否完全一樣。

值得指出的一點是,不帶引數的 edgelang 函式呼叫可以省略圓括弧,所以 uri() 可以簡寫成 uri, 像下面這樣

uri eq "/foo" =>
    redirect(uri: "/bar", code: 302);

回到目錄

多個關係表示式

使用者也可以在一個條件中宣告多個關係表示式,比如:

uri("/foo"), uri-arg("n") < 1 =>
    exit(403);

在這裡我們在條件裡多了一個關係表示式,也就是 uri-arg("n") < 1,它在 URI 的引數 n 收到一個小於數字 1 的時候返回真。在這個例子裡我們用了另外一個動作,exit(403),在執行的時候立即給客戶端傳送一個 “403 Permission Denied” 響應。請注意在兩個關係表示式中間的逗號的意思是, 意思是如果要整個條件為真,那麼逗號兩側的關係表示式必須都為真。

使用者可以甚至可以在同一個條件裡宣告更多的關係表示式,比如:

uri("/foo"), uri-arg("n") < 1, user-agent() contains "Chrome" =>
    exit(403);

這裡我們有第三個關係表示式,測試了請求頭中的 User-Agent 是否包含子字串 Chrome

回到目錄

多個條件

Edgelang 規則實際上可以執行多個並行的條件,用分號運算子連結。這些條件邏輯上是為當前規則在一起的。

比如:

uri("/foo"), uri-arg("n") < 1;
uri("/bar"), uri-arg("n") >= 4
=>
    exit(403);

這裡只要兩個條件之一為真,那麼就規則就算匹配上了。當然,兩個條件都匹配的話,規則也算匹配上。

回到目錄

多個動作

我們可以在一個結果裡宣告多個動作,參考下面的例子,

uri("/foo") =>
    errlog(level: "warn", "rule matched!"),
    say("response body data with an automatic trailing newline"),
    say("more body data...");

這個例子在結果裡有 3 個動作。第一個呼叫內建的 errlog() 函式向伺服器的錯誤日誌 中寫一行日誌,其級別是 warn。後面兩個動作呼叫 say() 函式為當前請求輸出響應體。

回到目錄

無條件規則

有些規則會選擇無條件執行它們的動作。不過 edgelang 規則,總是要求一個條件部分。 要實現這個無條件出發規則的效果,使用者可以使用總是返回真的判斷函式 true() 作為 條件裡的唯一的關係表示式,比如:

true() =>
    say("hello world");

在這個規則裡,動作 say() 是無條件執行的。

因為無引數函式呼叫可以省略圓括弧,我們更喜歡寫成 true,而不是 true()。 像下面這樣:

true =>
    say("hello world");

回到目錄

多個規則

同一個塊中的多個規則是按順序執行的。先寫的規則先執行。

觀察下面的例子:

uri("/foo") =>
    say("hello");

uri-arg("n") > 3 =>
    say("world");

對於請求 GET /foo?n=4,我們可以得到一個 200 的 HTTP 響應,響應體資料是:

hello
world

不過,多規則的條件部分可能會被 edgelang 編譯器最佳化,這樣就可以同時匹配,甚至可能在規則實際執行之前計算。 這些情況會在 edgelang 編譯器認為安全的時候發生。

回到目錄

語句塊

語句塊通常是用一對花括弧 ({}) 構成,同時也給變數生成一個新的範圍。 每個 edgelang 程式都有一個隱含的頂級語句塊。

在下面的例子裡,我們有兩個獨立的 $a 變數,因為它們屬於不同的語句塊(或者說範圍)。

my Num $a = 3;

{
    my Str $a = "hello";
}

true =>
    say("a = $a");   # output `3` instead of `hello`

規則和變數一樣,也是所在語句塊的詞法物件。語句塊可以用來把相關的規則組合在一起成為一個整體。 在這種設定中,哪些更早執行的規則可以使用特殊動作 done()忽略所有同語句塊中其他後續的規則。 如下例所示:

{
    uri("/test") =>
        print("hello"),
        done;

    true =>
        print("howdy");
}

true =>
    say(", outside!");

對於請求 GET /test,響應體應該是 hello, outside!。 請注意第一個規則中的 done 動作忽略了第二個規則的執行。換句話說, 請求 GET /foo" 會生成輸出 howdy, outside!,因為第一條規則不匹配。

不過,在一個規則結果的中間使用 done() 動作並不忽略同結果之後的動作。它隻影響同一語句塊中的後續規則。

語句塊可以巢狀任意層深度,如下

uri-arg("a") => say("outer-most");

{
    true => say("2nd level");

    {
        uri("/foo") => say("3rd level");
    }
}

回到目錄

動作塊

在規則結果中,語句塊也可以用做動作。這樣的語句塊叫動作塊。 這個結構可以用於宣告巢狀塊。比如:

uri-prefix("/foo/") =>
    {
        uri-arg("a") < 0 =>
            say("negative"),
            done;

        uri-arg("a") == 0 =>
            say("zero"),
            done;

        true =>
            say("positive");
    };

在這個規則裡,如果匹配上條件 uri-prefix("/foo/"),那麼在動作塊中的 3 個規則將順序執行。 換句話說,如果最外層的條件不匹配,那麼執行流是根本不會看其中的規則的。 這個方法可以很方便的把幾個規則規則的公共條件組合起來。這麼寫也可以幫助編譯器生成更有效的機器碼。

其它型別的動作可以跟同一個規則結果中的這樣的動作塊混合在一起,比如:

uri-prefix("/foo/") =>
    {
        uri-arg("a") < 0 => say("negative!");
    },
    done;

回到目錄

As 表示式

使用者可以在規則條件中使用 as 表示式 把表示式的結果定義成一個別名狀的使用者變數。 這些變數可以在規則的條件和/或結果(也就是說,在動作裡)的後面部分使用。

這些變數的可見範圍由包含他們的規則限制。

比如:

uri-prefix("/security01/" as $prefix) =>
    rm-uri-prefix($prefix),
    set-req-host("sec.foo.com");

在上面這裡我們把表示式 "/security01/" 的值別名成了標量變數 $prefix, 然後在我們的 rm-uri-prefix() 動作中不再重複使用前面那個常量字串,而是使用了這個別名變數, 這樣就減少了常量字串值敲錯的風險。如果我們敲錯了變數名,那麼我們會在編譯的時候收到一個編譯錯誤 (缺少變數定義)。所以使用 as 表示式做變數別名,不僅讓程式碼更短,也讓它更安全。

我們也可以用“as 表示式”代表任意表示式的值。比如

uri-arg("uid") as $uid, looks-like-num($uid), $uid > 0 =>
    say("found uid: $uid");

在這個規則裡,我們把 uri-arg("uid") 這個動態表示式的值別名給了使用者定義變數 $uid, 然後在條件後面的關係表示式和規則動作裡引用了這個值。

回到目錄

賦值動作

賦值運算子 = 用於宣告給一個變數或者一個可以是左值的表示式賦值的動作。比如,

my Num $a;

true =>
    $a = 3;

和所有其他動作一樣,賦值表示式本身沒有值。 所以不允許在其他表示式中嵌入一個賦值表示式。比如,下面的例子會生成一個編譯時錯誤:

? my Num $a;
?
? true =>
?     say($a = 3);

這是因為賦值 $a = 3 不返回值並且只能用於一個獨立的動作中。

下面的賦值

$a = $a + 3

可以簡化成使用運算子 +=:

$a += 3

類似的,提供了 *=/=%=x=~= 用於跟二目運算子 */%x,和 ~ 對應。

最後,字尾運算子 ++ 可以用於簡化 +=1 的場合。比如:

$a++

等效於 $a += 1 或者 $a = $a + 1。對應的還有 -- 用於做 -= 1 的簡寫。

和標準 = 運算子類似,所有這些賦值的形式自己並不接受任何數值,只能用於獨立的動作中。

回到目錄

執行時階段

OpenResty® 在不同的執行時階段處理每個客戶端請求。

目前支援下列階段,預設會執行在 rewrite 階段,其他階段需要配合 Defer 塊 來使用。

  • rewrite

    請求改寫和重定向的時候

  • resp-header

    在響應頭準備好的時候

  • resp-body

    響應體處理階段

未來可能會新增更多執行時階段。

回到目錄

Defer 塊

defer 塊是一個特殊的程式碼塊,他們會被延後到指定階段的開頭執行。當前支援的階段有:resp-header, resp-body

注意:使用了 resp-body 的 defer 塊之後,響應頭的 Content-Length 會被清空,強制使用 chunked 模式。並且 defer 塊中不可以巢狀 defer 塊。

看看下面例子:

true =>
    defer resp-header {
        errlog(level: 'error', 'defer log in resp-header');
    };

true =>
    defer resp-body {
        errlog(level: 'error', 'defer log in resp-body');
    };

回到目錄

連線

一個連線是一個單值等效於多值。內建的函式 anyall,和 none 可以用於從一個列數值或者一個雜湊構造連線。 連線提供了一個可以用來表示列表和數值之間關係約束的非常簡單的方法。比如, 如果要測試數值 @foo 中有沒有某個元素大於 3,我們可以寫成

any(@foo) > 3

或者我們想判斷是否所有元素都大於 3:

all(@foo) > 3

使用者也可以直接宣告多個離散的數值,比如:

any(1, 3, 5) <= 1

我們也可以在關係運算子兩邊使用連線:

any(2, 3) > all(-1, 1)

測試一個數值是否未出現在一列數值中,我們可以寫:

$a eq none('foo', 'bar', 'baz')

連線只能用於關係表示式中。

當我們在關係運算子的一邊使用多個數值的時候,可以透過透過 any() 函式自動建立隱含的連線。 比如,當一個陣列值出現在關係運算子的一邊的時候:

@foo > 3

它等效於

any(@foo) > 3

類似的,對於 uri-arg() 這樣可能返回多個值的函式呼叫:

uri-arg("name") eq 'admin'

等效於:

any(uri-arg("name")) eq 'admin'

在連線操作的中使用求反關係運算子有潛在的問題,尤其是翻譯成本地語言之後。 比如下面這個例子:

$a != any(1, 2, 3)

它實際上相當於這樣:

!($a == any(1, 2, 3))

為了避免這種用英語語序理解容易出現的誤會, edgelang 在關係運算子是 ne 或者 !=,並且在右邊使用了 any() 這種場合下, 為使用者自動做這樣的轉換。

連線只能在關係表示式的頂層使用。在函式引數中使用連線是禁止的。

目前還不支援巢狀連線。

回到目錄

虛擬伺服器

一個虛擬伺服器是一個域名或者一個萬用字元的域名,用於表示一個獨立的“主機”。 大量虛擬伺服器或者域名共享同一個 OpenResty® 伺服器例項是很常見的。

每個 edgelang 程式都跟一個虛擬伺服器關聯,虛擬伺服器之間通常都是分離且獨立的。 虛擬伺服器通常並不直接在 edgelang 原始碼內部生命,而是在呼叫 edgelang 編譯器的時候在外部宣告。 在 OpenResty® Edge 平臺的環境下,虛擬伺服器是用應用的概念表示的。在那裡, 當 OpenResty® Edge 呼叫的時候,這些資訊會自動提交給 edgelang 編譯器。

如果給虛擬伺服器宣告瞭一個萬用字元域名,比如 *.foo.com,那麼使用者可以使用內建判斷函式 host() 指定具體的子域名。 比如:

#  *.foo.com 的公共規則放在這裡。。。

host("api.foo.com") => {
    # 子域名 api.foo.com 的規則放在這裡
}, done;

host("blog.foo.com") => {
    # rules for the sub-domain blog.foo.com go here...
}, done;

回到目錄

使用者定義動作

使用者可以定義他們自己的引數化動作,方法是把其他一些動作組合起來。 定義客戶化動作的常用語法像下面這樣:

action <name>(<arg>...) =
    <action1>,
    <action2>,
    ...
    <actionN>;

引數必須要指定資料型別,就像變數宣告時那樣。

比如:

action say-hi(Str $who) =
    say("hi, $who!"),
    exit(200);

然後一個 HTTP 請求會觸發一個 HTTP 200 響應,響應體如下:

hi, Tom!

我們也可以宣告多個引數。

使用者定義動作是一個非常好的在動作中引入你自己的詞彙的方法,這些動作可以用於規則結果中。

也可以定義定義遞迴動作,如:

action count-down(Num $n) =
    say($n),
    $n > 0 ? count-down($n - 1) : say("done");

true => count-down(5);

這樣會生成下面的響應體:

5
4
3
2
1
0
done

遞迴的最大深度會由編譯器限制,以避免無限遞迴。

回到目錄

使用者定義函式

使用者可以定義自己的函式,可以用於規則條件和結果。定義客戶化函式的語法釋這樣的:

func <name>(<arg>...) = <expression>

引數必須要指定資料型別,就像變數宣告時那樣。

= 符號後面的 <expression> 的值是整個函式的值。

參看下面的例子:

func x-powered-by () =
    resp-header("X-Powered-By");

x-powered-by contains rx:i/\b php \b/ =>
    errlog(level: "notice", "found a PHP server: ", x-powered-by);

這個例子定義了自己的函式 x-powered-by,它不接受引數,並且執行計算出表示式 resp-header("X-Powered-By" ) 的值。

使用者定義函式也可以接受引數。參考下面的例子

func bit-is-set(Num $num, Num $pos) =
    $num & (1 << ($pos - 1))

check-bit(3, 1)
=>
    say("the 1st bit is set!");

bit-is-set 函式接受一個數字作為第一個引數,另外接受一個位的位置用於測試該數字的指定位。 如果置頂的二進位制位是 1 則返回真,否則返回假。

需要提醒的是我們有一個內建判斷函式 test-bit,它的功能就是使用者定義函式 bit-is-set() 的功能。

回到目錄

模組

Edge 模組是可以在不同 edgelang 程式之間共享的可重用的 edgelang 原始碼檔案。 模組通常包含各種使用者定義動作和/或使用者定義函式的定義。

要裝在一個模組,使用者的 edgelang 程式可以用 use 語句,像下面這樣:

use Foo;

edgelang 編譯器將在模組搜尋路徑中搜尋一個名字為 Foo.edge 的檔案。 使用者可以在 edgelang 編譯器命令列宣告 -I PATH 選項向預設模組搜尋路徑中增加使用者定製路徑。 比如:

edgelang -I /foo/bar -I /baz/blah test.edge

使用者還可以在命令列上用 -M NAME 選項宣告預裝載的 edge 模組, 像下面這樣:

edgelang -I /path/to/modules -M Foo -M Bar test.edge

回到目錄

呼叫外部程式碼

我們還支援目標語言寫的外部庫。 比如,如果目標語言是 lua,那麼只要使用者具備足夠的許可權,那麼她就可以在自己的 edgelang 程式中 呼叫任意 Lua 模組。

呼叫外部程式碼通常是用內建函式 foreign-call()。 它接受下列命名引數:

  • module

    外部模組名。在 Lua 的場合下,它是 Lua 模組的名字。這個引數是可選的。

  • func

    該模組內、或者目標語言預設的名字空間內的函式名。這個引數是必須的。

位置引數(如果有的話)將被肢解傳遞給指定模組(如果有的話 0 的指定函式。

下面是一個呼叫標準 Lua 模組 mathrandom 函式的例子:

true =>
    say(foreign-call(module: "math", func: "random", 1, 10));

這個例子中的 foreign-call() 等效於下面 Lua 表示式:

math.random(1, 10)

要呼叫外部的 C 庫程式碼,使用者可以使用 LuaJIT 中高效的 FFI 簡單封裝一個 Lua 模組,然後在像普通 Lua 模組一樣去呼叫。

外部程式碼的預設路徑

請參考這篇文件建立全域性 Lua 模組。

回到目錄

內建判斷函式

Edge 語言提供了下列內建判斷函式。

回到目錄

cache-status

語法: cache-status()

返回上游快取狀態。

比如:

true =>
    defer resp-header {
        set-resp-header("Cache-Status", cache-status);
    };

回到目錄

cache-creation-time

語法: cache-creation-time()

階段: resp-header

返回上游自快取建立以來的時間

回到目錄

client-addr

語法: client-addr()

返回客戶端地址。

回到目錄

client-asn

語法: client-asn()

語法: client-asn(asn1, asn2, ...)

當客戶端地址屬於引數中指定的任一自治系統號碼 (autonomous system number) 時返回 true;否則返回 false。

舉例如下:

client-asn("7018", "8023") =>
    say("Welcome, our dear guest!");

當未指定任何引數時,它將返回客戶端的當前自治系統編號 (autonomous system number)。

舉例如下:

client-asn eq "7018" =>
    say("Welcome, our dear guest!");

回到目錄

client-continent

語法: client-continent()

語法: client-continent(continent1, continent2, ...)

如果客戶端地址來自引數宣告的大洲之一,返回真,否則返回假。

下面是所有大洲編碼:

AF = Africa(非洲)
AS = Asia(亞洲)
EU = Europe(歐洲)
NA = North America(北美)
SA = South America(南美)
OC = Oceania(大洋洲)
AN = Antarctica(南極洲)

比如:

client-continent("AS") =>
    say("Welcome, our dear guest from Asia Region!");

如果不宣告任何引數,它會返回客戶端當前的洲名稱:

client-continent eq "AS" =>
    say("Welcome, our dear guest from Asia Region!");

回到目錄

client-country

語法: client-country()

語法: client-country(country1, country2, ...)

如果客戶端地址是引數中宣告的城市之一,則返回真;否則返回假。

你可以從下列地址獲取所有兩字母國家編碼 wikipedia

下面是一個典型的國家程式碼列表:

US = United States of America
CA = Canada
CN = China
RU = Russian Federation
JP = Japan
IN = India
FR = France
DE = Germany

比如:

client-country("CN") =>
    say("Welcome, our dear guest from China!");

如果不宣告任何引數,它會返回客戶端的當前國家名:

client-country eq "CN" =>
    say("Welcome, our dear guest from China!");

回到目錄

client-port

語法: client-port()

返回客戶端埠。

client-province

語法: client-province()

語法: client-province(province1, province2, ...)

如果客戶端地址來自引數宣告的省份之一,則返回真;否則返回假。

比如:

client-province("California") =>
    say("Welcome, our dear guest from California!");

如果不宣告任何引數,它會返回客戶端當前的省份名:

client-province eq "California" =>
    say("Welcome, our dear guest from California!");

檢視所有中國省份名稱代號。

回到目錄

client-city

語法: client-city()

語法: client-city(city1, city2, ...)

如果客戶端地址是來自引數宣告的城市列表之一,則返回真;否則返回假。

比如:

client-city("Los Angeles") =>
    say("Welcome, our dear guest from Los Angeles!");

如果不宣告任何引數,它會返回客戶端所在的當前城市:

client-city eq "Los Angeles" =>
    say("Welcome, our dear guest from Los Angeles!");

回到目錄

client-isp

語法: client-isp()

語法: client-isp(isp1, isp2, ...)

如果客戶端的 ISP 來自引數列表宣告的 ISP 之一,則返回真;否則返回假。

比如:

client-isp("ChinaTelecom") =>
    say("our guest's ISP is ChinaTelecom!");

如果不宣告任何引數,它會返回客戶端當前 ISP 名:、

client-isp eq "ChinaTelecom" =>
    say("our guest's ISP is ChinaTelecom!");

檢視所有中國 ISP。

回到目錄

client-org

語法: client-org() 語法: client-org(org1, org2)

當客戶端地址屬於引數中指定的任一自治系統組織(autonomous system organization)時返回 true;否則返回 false。

舉例如下:

client-org("Korea Telecom", "AT&T Services, Inc.") =>
    say("Welcome, our dear guest!");

當未指定任何引數時,它將返回客戶端的當前自治系統編號。

舉例如下:

client-org eq "AT&T Services, Inc." =>
    say("Welcome, our dear guest!");

回到目錄

client-subnet

語法: client-subnet()

子系統: dns

返回 DNS 查詢裡面客戶端的子網,如果在 DNS 查詢裡沒有發現子網,則返回 nil

它支援 netaddr 常量,比如:

client-subnet ~~ 127.0.0.1/24 =>
    errlog("match");

注意 現在在 DNS 裡只解析 IPv4。

回到目錄

decode-base64

語法: decode-base64(digest)

把輸入字串當作 base64 的編碼解碼。

回到目錄

decode-hex

語法: decode-hex(str)

把輸入字串單數當作一個十六進位制進行解碼。

回到目錄

defined

語法: defined(val)

如果引數值被定義了,返回真;否則返回假。

回到目錄

disable-convert-head-method-to-get

語法: disable-convert-head-method-get()

禁止將 HEAD 請求方法轉換為 GET 方法用於回源。

回到目錄

encode-base64

語法: encode-base64(str)

把輸入的字串引數編碼為 base64 資料。

回到目錄

false

語法: false()

返回布林假值。

回到目錄

first-x-forwarded-addr

語法: first-x-forwarded-addr()

返回請求頭 X-Forwarded-For 中的第一個地址。

回到目錄

host

語法: host()

語法: host(pattern...)

如果不宣告引數,返回請求指定的主機名。

如果宣告瞭引數,這個函式在請求的主機名匹配任意引數指定的模式的時候返回真,這裡匹配的運算子是 eq。 引數的模式可以是正則,文字串,或者萬用字元。

下面是例子:

host("foo.com", wc"*.foo.com") =>
    say("hit!");

這個等效於下面的形式:

host eq any("foo.com", wc"*.foo.com") =>
    say("hit!");

我們推薦用前者,因為它更簡單。

回到目錄

http-time

語法: http-time()

語法: http-time(quantity-val)

Last-ModifiedExpires 這樣的響應頭數值生成 HTTP 時間格式串。

如果沒有宣告引數,它會使用當前時間為預設值。

如果宣告瞭引數,那麼只接受帶時間單位的量詞。

下面是一些例子:

# 1st
true =>
    http-time.say;

# 2nd
true =>
    say(http-time(now));

# 3rd
true =>
    say(http-time(1513068009 [s]));

返回結果是一個字傳,比如 Tue, 12 Dec 2017 08:40:09 GMT

回到目錄

ip-asn

語法: ip-asn(netaddr)

返回指定的 IP 地址的自治系統編號(autonomous system number

舉例如下:

ip-asn("127.0.0.1") eq "7018" =>
    say("Welcome, our dear guest!");

回到目錄

ip-continent

語法: ip-continent(netaddr)

子系統: dns

為指定的網路地址返回大洲名稱,大洲名稱可以用於解析 DNS 查詢。

下面是例子:

ip-continent(client-subnet) eq 'AP' =>
    errlog("match");

回到目錄

ip-country

語法: ip-country(netaddr)

子系統: dns

為指定的網路地址返回國家名稱,國家名稱可以用於解析 DNS 查詢。

下面是例子:

ip-country(client-subnet) eq 'CN' =>
    errlog("match");

回到目錄

ip-province

語法: ip-province(netaddr)

子系統: dns

為指定的網路地址返回省份名稱,省份名稱可以用於解析 DNS 查詢。

下面是例子:

ip-province(client-subnet) eq 'Guangdong' =>
    errlog("match");

回到目錄

ip-city

語法: ip-city(netaddr)

子系統: dns

為指定的網路地址返回城市名稱,城市名稱可以用於解析 DNS 查詢。

下面是例子:

ip-city(client-subnet) eq 'Zhuhai' =>
    errlog("match");

回到目錄

ip-isp

語法: ip-isp(netaddr)

子系統: dns

為指定的網路地址返回 ISP 名稱,ISP 名稱可以用於解析 DNS 查詢。

下面是例子:

ip-isp(client-subnet) eq 'ChinaTelecom' =>
    errlog("match");

回到目錄

ip-org

syntax: ip-org(netaddr)

返回指定的 IP 地址的自治系統組織名稱(autonomous system organization)。

ip-org("127.0.0.1") eq "AT&T Services, Inc." =>
    say("Welcome, our dear guest from AT&T!");

回到目錄

is-empty

語法: is-empty(value)

如果引數值是空(未定義,空字傳或者 true 值)返回真;否則返回假。

回到目錄

inject-csrf-token

語法: inject-csrf-token()

注意:該功能僅適用於表單請求,如果 HTML 頁面上使用 AJAX 請求,CSRF token 將無法成功注入。

該動作會在 Content-Typetext/html 的響應內容末尾新增一段 JavaScript 程式碼。這段程式碼會自動為頁面中的表單請求引數新增 _edge_csrf_token 引數,以便在發起表單請求時攜帶該引數。配合 validate-csrf-token 動作,可以實現 CSRF 防護的效果。只能在 resp-bodydefer 塊中使用該動作。由於要修改響應體,還需要移除 Accept-Encoding 請求頭,以免受到編碼的影響。

下面是一個實現了 CSRF 防護功能的例子:

my Str $csrf-res;

true =>
    rm-req-header("Accept-Encoding"),
    defer resp-body {
        inject-csrf-token();
    },
    $csrf-res = validate-csrf-token(3600),
    {
        $csrf-res ne "ok" =>
            waflog($csrf-res, action: "block", rule-name: "csrf_protection"),
            exit(403);
    };

回到目錄

last-x-forwarded-addr

語法: last-x-forwarded-addr()

返回請求頭 X-Forwarded-For 中的最後一個地址。

回到目錄

looks-like-int

語法: looks-like-int(value)

在引數看上去像整數的時候返回真,也就是說, 要麼是一個看上去像整數的字串,或者是數值本身是一個整數值,或者是一個小數位為 0 的數值。 否則返回假。

下列呼叫會返回真:

looks-like-int(32)
looks-like-int(3.00)
looks-like-int("561")
looks-like-int('0')

回到目錄

looks-like-num

語法: looks-like-num(value)

如果引數值看上去像一個數字,返回真,也就是說要麼是一個像數字的字串, 要麼是是一個數字值。

下列呼叫返回真:

looks-like-num(3.14)
looks-like-num("-532.3")

回到目錄

lower-case

語法: lower-case(value)

返回一個引數的所有字母都轉成小寫字母的字串。

回到目錄

md5-hex

語法: md5-hex(value)

返回一個引數值的十六進位制表現的 MD5 摘要。

回到目錄

escape-uri

語法: escape-uri(str)

返回 str URI 引數編碼後的值。

回到目錄

unescape-uri

語法: unescape-uri(str)

返回 str URI 引數解碼後的值。

回到目錄

str-len

語法: str-len(str)

返回 str 的長度。

回到目錄

modsec-amp

語法: modsec-amp(var)

功能與 Modsecurity 規則裡的 “&” 運算子一致,規則如下:

  • 當傳入變數為空時,返回 0
  • 當傳入變數為標量型別且不為空時,返回 1
  • 當傳入變數為陣列型別且不為空時,返回陣列的長度
my Num $a;
my Str $b = "hello";
my Str @c = ("hello", "world");

true =>
    say(modsec-amp($a)),
    say(modsec-amp($b)),
    say(modsec-amp(@c)),
    done;

回到目錄

match-ip-list

語法: match-ip-list(name: IP_LIST_NAME, IP)

如果 IP 地址符合 IP 列表中的其中一條,則返回真。否則返回假。

IP 列表需要在應用中或者全域性配置中提前建立。指定應用 IP 列表的時候,需要在 IP 列表名稱前加上 app: 的字首。 當指定全域性 IP 列表的時候,需要在 IP 列表名稱前加上 global: 的字首。

使用應用 IP 列表匹配:

match-ip-list(name: "app:ip-list-1", client-addr) =>
    say("matched"),
    done;

使用全域性 IP 列表匹配:

match-ip-list(name: "global:ip-list-1", client-addr) =>
    say("matched"),
    done;

回到目錄

now

語法: now()

從 OpenResty 緩衝的時間(因此不像 Lua 的 date 庫那樣需要系統呼叫)中返回一個紀元開始以來到現在的浮點數表示的秒數(小數部分是毫秒數)。

回到目錄

now-secs

語法: now-secs()

返回標準的 Unix 時間戳,單位為秒。(與 Lua 的 os.date 庫不同,這裡無系統呼叫)

回到目錄

post-arg

語法: post-arg(pattern...)

語法: post-arg(&ast;)

返回使用 pattern 透過 eq 運算子匹配到的 POST 表單引數的值。

引數 pattern 可以是正則,文字串或者萬用字元之一。

post-arg 不支援 multipart/form-data。

下面是一個是用 POST 表單引數 limitrate 的值限制響應體資料傳送率的例子。

post-arg("limitrate") as $rate, looks-like-int($rate) =>
    limit-resp-data-rate($rate [Kb/s], after: 1 [MB]);

如果引數是一個任意值,也就是 *,那麼它返回請求中所有的 POST 表單引數的值。 在布林值上下文中,只要存在 POST 表單引數,其返回值即為真。

回到目錄

random-pick

語法: random-pick(value...)

等機率隨機選出引數值給予返回。

比如,

random-pick("foo", "bar", "baz")

會以相同機率返回 "foo""bar",或者 "baz"

回到目錄

rand

語法: rand()

返回一個範圍在 [0,1) 之間的隨機數。

回到目錄

rand-bytes

語法: rand-bytes(len)

返回一個長度為 len 的隨機字串。

回到目錄

random-hit

語法: random-hit(ratio)

根據 ratio 引數宣告的可能性,返回隨機數的真假。這裡 ratio 的取值必須在範圍 [0,1] 之間。 這裡的 0 意思是永不,而 1 意思是 100%,也就是說,永遠。比如,如果 ratio 是 0.2,那麼這個函式有 20% 的機會返回真, 而其它場合返回假。

回到目錄

referer

語法: referer()

語法: referer(pattern...)

如果不加任何引數呼叫,它返回函式呼叫 req-header("Referer") 的值。

如果宣告瞭一些引數,那麼這些引數就會被當作模式使用。 如果引用者 referer 的值可以用 eq 運算子匹配任意這些模式,就會返回真。

引數 pattern 可以是正則,文字串,也可以是萬用字元。

比如:

referer(wc{*/search.html}, rx{.*?/find\.html}) =>
    say("hit!");

這個規則等效於下面這個無引數呼叫 referer 的形式:

referer-host eq any(wc{*/search.html}, rx{.*?/find\.html}) =>
    say("hit!");

我們推薦前面的形式,因為更簡單。

回到目錄

reg-domain

語法: reg-domain()

語法: reg-domain(pattern...)

如果沒有給出引數,它返回客戶端正在請求的伺服器的註冊域名。比如,像 www.openresty.org 這樣的不是註冊域名,而 openresty.org 是。

如果宣告瞭一些引數,那麼這些引數將會被當作模式看待。 如果引用者的主機名可以用 eq 運算子匹配任意其中的模式,則返回真。 引數 pattern 可以是正則,文字串,也可以是萬用字元。

比如:

reg-domain("openresty.org", "agentzh.org") =>
    say("hit!");

等效於:

reg-domain eq any("openresty.org", "agentzh.org") =>
    say("hit!");

我見建議使用前者,因為前者可以用引用字的語法進一步簡化成下面這樣:

reg-domain(qw/openresty.org agentzh.org/) =>
    say("hit!");

回到目錄

req-charset

語法: req-charset()

返回請求頭 Content-Type 裡面的 charset 引數值(如果有的話)。

回到目錄

語法: req-cookie(pattern...)

語法: req-cookie(&ast;)

返回 cookie 名可以用 eq 運算子和引數 pattern 匹配的 cookie 值。

在布林環境裡,它只會計算出真假兩個值,分別對應 cookie 名匹配上和沒有匹配上引數的情況。

引數 pattern 可以是正則,文字串或者萬用字元之一。

下面是一些例子:

req-cookie("mobile_type") =>
    say("cookie mobile_type is present!");

req-cookie("mobile_type") > 0 =>
    say("cookie mobile_type takes a value greater than 0!");

如果引數是任意值,也就是 *,那麼它返回在請求中的所有 cookie。在布林環境中, 只要請求中有 cookie,就會返回真。

cookie 的名字也可以是類似正則和萬用字元那樣的模式。 在這種情況下,名字匹配任意一個這些模式的 cookie 的值都會被返回。

回到目錄

req-header

語法: req-header(pattern...)

返回名字可以使用 eq 運算子匹配上引數 pattern 的對應請求頭的值。

在布林環境裡,只要有任何請求頭名字匹配上引數 pattern,就返回真,否則返回假。

引數 pattern 可以是正則,文字串或者萬用字元之一。

下面是一些例子:

req-header("X-WAP-Profile", "WAP-Profile") =>
    say("either header X-WAF-Profile or header WAF-Profile is present!");

請求頭的名字也可以是像正則或者萬用字元那樣的模式。在這種場合下, 匹配任意這些模式的請求頭名字都會被選中,並且將其值返回。

回到目錄

duplicate-req-header

語法: duplicate-req-header()

如果存在重複的請求頭名字,這個動作會返回 true,否則返回 false

下面是一些例子:

duplicate-req-header =>
    say("duplicate request headers found!");

回到目錄

max-req-header-name-len

語法: max-req-header-name-len()

返回最長的請求頭名字的長度。

下面是一些例子:

max-req-header-name-len > 100 =>
    say("Found a request header name longer than 100");

回到目錄

max-req-header-value-len

語法: max-req-header-value-len()

返回最長的請求頭值的長度。

下面是一些例子:

max-req-header-value-len > 100 =>
    say("Found a request header value longer than 100");

回到目錄

req-id

語法: req-id()

為當前請求返回請求 id 的值。請求 id 包含一些可以唯一標識一個 OpenResty Edge 中的某個請求的資訊。

請求 id 總是一個 24 字元的字串。

下面是例子:

true =>
    add-resp-header("X-Request-Id", req-id)

回到目錄

req-latency

語法: req-latency()

返回請求的時延,這是一個數量型別的值。

比如 0.01 [s] 意思是 0.01 second

回到目錄

req-bytes

語法: req-bytes()

返回請求的位元組數 (包含請求行/請求頭/請求體)

回到目錄

resp-bytes

語法: resp-bytes()

返回響應的位元組數

回到目錄

req-method

語法: req-method(pattern...)

如果不宣告引數,返回請求方法的字串,比如 GETPOSTDELETE

如果宣告瞭一些引數,那麼這些引數就會被當作模式使用。這個模式用於匹配當前請求方法字串。如果任何其中的使用者模式匹配上了就返回 true;否則返回 false

回到目錄

req-uri

語法: req-uri()

帶有引數的解碼後的請求 URI

下面的例子返回了請求的 URI:

true =>
    say(req-uri());

回到目錄

resp-header

語法: resp-header(pattern...)

返回響應頭中的名字可以用 eq 運算子匹配上引數 pattern 的響應頭數值。

在布林環境中,如果有匹配的響應頭名字,它就只是返回真,否則返回假。

引數 pattern 可以是正則,文字串,或者萬用字元。

比如:

resp-header("X-WAP-Profile", "WAP-Profile") =>
    say("either header X-WAF-Profile or header WAF-Profile is present!");

響應頭名字也可以是類似模式或者萬用字元的東西。在這種場合下, 匹配任何這樣的模式的頭部名字都會被選中,並且返回他們的值。

回到目錄

resp-header-param

語法: resp-header-param(header-name, param-name)

返回指定響應頭中指定頭引數的值。

比如,

resp-header-param("Cache-Control", "s-maxage") =>
   rm-resp-header-param("Cache-Control", "s-maxage");

這段程式碼在響應頭 Cache-Control 裡面出現了 s-maxage 引數的時候就把它刪除。

回到目錄

resp-body

語法: resp-body()

返回響應體的內容。只有在 resp-body 的 defer 塊中可以使用。

比如,

true =>
    defer resp-body {
        errlog("body: ", resp-body);
    };

回到目錄

resp-mime-type

語法: resp-mime-type()

語法: reps-mime-type(pattern...)

如果不宣告引數,則返回響應的 MIME-type,也就是響應頭 Content-Type,但是不包含 任何其它引數,比如 charset=utf-8

如果宣告瞭一些引數,就會把這些引數當作模式看待。 如果響應 MIME-type 數值可以用 eq 運算子匹配任何模式,就會返回真。

引數 pattern 可以是,文字串或者萬用字元之一。

比如:

resp-mime-type("text/html", wc"*javascript") =>
    say("hit!");

這條規則等效於下面的無引數的 resp-mime-type 呼叫:

resp-mime-type eq any("text/html", wc"*javascript") =>
    say("hit!");

我們推薦用前者,因為更簡單。

回到目錄

resp-status

語法: resp-status()

語法: resp-status(code...)

如果沒有宣告引數,它返回當前響應的狀態碼。

如果給出了引數,那麼引數會被用於跟當前響應狀態進行比較的程式碼。 如果匹配上了任何特定的程式碼,則返回真,否則返回假。

比如:

resp-status(404, 500, 502, 503) =>
    say("found a known bad response status code: ", resp-status);

回到目錄

scheme

語法: scheme()

返貨當前請求的協議模式,比如 http 或者 https

比如:

scheme() eq "http" =>
    redirect(scheme: "https", host: host(), uri: req-uri(), code: 307);

回到目錄

server-addr

語法: server-addr()

階段: rewrite resp-header resp-body

返回接受了當前請求的伺服器地址。

注意 此函式不能在 ssl-cert 階段使用,其他階段是可以的。

下面是例子:

true => say("address: ", server-addr);

因你的伺服器監聽地址不同,我們可能得到下列回答:

# IPv4
address: 127.0.0.1

# IPv6
address: ::1

# Unix domain
address: unix:/tmp/nginx.sock

回到目錄

server-port

語法: server-port()

返回接受當前請求的伺服器的埠。

回到目錄

substr

語法: substr(str, start[, end])

返回從角標 start(從 1 開始)開始,到 end 結束指定的子串。

負數角標表示從字串尾部開始定位。比如,-1 表示最後一個字元,-2 表示倒數第二個,以此類推。

如果省略了 end 引數,它意味著所有直到字串尾部的字元。

下面是一些例子:

my Str $s = "hello world";

true =>
    say(substr($s, 1, 5)),       #  輸出: hello
    say(substr($s, 7)),          #  輸出: world
    say(substr($s, -5, -2)),      #  輸出: worl
    say(substr($s, -5));         #  輸出: world

回到目錄

subst

語法: subst(subject, regex, replacement,) 語法: subst(subject, regex, replacement, g: BOOL)

字串替換,將字串 subject 中,正則 regex 匹配的部分替換成 replacement, 預設情況下,只替換第一個命中的。如果需要全域性替換需要指定 g: true

下面是一些例子:

my Str $s = "hello world";

true =>
    say(subst($s, rx/l/, "g")),          #  輸出: heglo world
    say(subst($s, rx/l/, "g", g: true)); #  輸出: heggo worgd

回到目錄

system-hostname

語法: host_name = system-hostname()

返回系統的主機名,與命令列 hostname 返回值相同

例:

true =>
     say("host name: ", system-hostname);

回到目錄

ssl-client-s-dn

語法: client_subject_dn = ssl-client-s-dn()

返回客戶端證書中的“subject DN”字串,比如: CN=client.com,OU=dev,O=orinc,L=xm,ST=fj,C=cn

例如:

true =>
     say("client subject dn: ", ssl-client-s-dn);

回到目錄

ssl-client-i-dn

語法: issuer_subject_dn = ssl-client-i-dn()

返回客戶端證書中的“issuer DN”字串,比如: CN=rootca.com,OU=dev,O=orinc,L=xm,ST=fj,C=cn

例如:

true =>
     say("issuer subject dn: ", ssl-client-i-dn);

回到目錄

ssl-client-serial

語法: client_serial = ssl-client-serial()

返回已經建立的 SSL 連結的客戶端證書的序列號,比如:

045CA7F023CAC0FD592B4D5DE5E7C6AF

例如:

true =>
     say("ssl client serial: ", ssl-client-serial);

回到目錄

ssl-client-verify-result

語法: result = ssl-client-verify-result()

返回客戶端證書認證的結果,結果的返回值包括:

NONE, SUCCESS, FAILED:unable to verify the first certificate

例如:

true =>
     say("result: ", ssl-client-verify-result);

回到目錄

to-int

語法: to-int(value)

語法: to-int(value, method: METHOD)

將數字轉化為整型。字串將會被轉化成 10 進位制整型。綱量將會被移除單位。

其中引數 ceil / floor / round 將會決定其取整演算法。預設為 floor

ceil 表示向上取整,floor 表示向下取整,round 表示四捨六入五成雙。

例如:

true =>
    to-int("10.1", method: "ceil"), # 11
    to-int("10.1", method: "floor"); # 10

回到目錄

to-num

語法: to-num(value)

把引數值轉換成一個數值。字串會以 10 進位制的形式轉換成數字。量綱 會被刪去單位部分。數字會直接透過。

回到目錄

to-hex

語法: to-hex(value)

把引數值轉換成一個十六進位制編碼的字串。

回到目錄

true

語法: true()

返回布林值:真。

回到目錄

ua-contains

語法: ua-contains(pattern...)

這個只是表示式 user-agent contains any(pattern1, pattern2, ...) 的縮寫。

回到目錄

ua-is-mobile

語法: ua-is-mobile()

如果客戶端看上去像一個移動裝置,則返回真,否則返回假。這個是透過客戶端檢查請求頭的 User-Agent 欄位來實現的。

回到目錄

upper-case

語法: upper-case(value)

把一個字串的所有字元都轉換成大寫字母返回。

回到目錄

upstream-addr

語法: $addr = upstream-addr()

返回字串格式的上游地址,形如:192.168.0.1:8080

比如:

true =>
    defer resp-header {
        errlog(level: "warn", upstream-addr);
    };

回到目錄

uri

語法: uri()

語法: uri(pattern...)

如果沒有宣告引數,那麼返回請求的 URI。請注意這個 URI 串不包括任何 URI 引數。

如果宣告瞭一些引數,那麼這些引數會被當作模式看待,如果 URI 值可以用 eq 運算子匹配任意其中的模式, 就返回真。

引數 pattern 可以是正則,文字串或者萬用字元之一。

比如:

# 對請求 URIs `/foo/`, `/bar/`, 和 `/bar/blah`, 條件為真
# 但是對 `/blah/foo/`, `/blah/bar/`, 和 `/bar` 為假:
uri("/foo/", wc"/bar/*") =>
    say("hit!");

這條規則等效於下面形式不帶引數的 uri 判斷函式呼叫:

uri eq any("/foo/", wc"/bar/*") =>
    say("hit!");

我們建議用前者,因為他更簡單。

回到目錄

uri-arg

語法: uri-arg(name)

獲取指定 URI 引數的值。

下面是一個是用 URI 引數 limitrate 的值限制響應體資料傳送率的例子。

uri-arg("limitrate") as $rate, looks-like-int($rate) =>
    limit-resp-data-rate($rate [Kb/s], after: 1 [MB]);

回到目錄

duplicate-uri-arg

語法: duplicate-uri-arg()

如果請求引數中有相同的名字,該動作會返回 true,否則返回 false

duplicate-uri-arg =>
    say("duplicate URI arguments found!");

回到目錄

query-string

語法: query-string()

返回請求中的 查詢字串

例如下面這段 edge,如果請求是 GET /uri?foo=bar&a=b,我們將得到 foo=bar&a=b

true =>
    say(query-string);

回到目錄

sorted-query-string

語法: sorted-query-string()

返回請求中排序後的 查詢字串

例如下面這段 edge,如果請求是 GET /uri?b=2&a=1&c=3,我們將得到 a=1&b=2&c=3

true =>
    say(sorted-query-string);

回到目錄

uri-basename

語法: uri-basename()

語法: uri-basename(pattern...)

如果沒有引數,它返回請求 URI 宣告的基礎名。比如,對於 URI /en/company/about-us.html,它返回 about-us 為基礎名,對於 /static/download/foo.tar.gz, 則返回 foo

如果宣告瞭引數,那麼這些引數會被當成模式對待。 如果 URI 的值可以用 eq 運算子匹配任意其中的引數的話,就返回真。

uri-basename("foo", rx/bar\w+/) =>
    say("hit!");

回到目錄

uri-contains

語法: uri-contains(pattern...)

這是表示式 uri contains any(pattern1, pattern2, ...) 的簡寫。

回到目錄

uri-prefix

語法: uri-prefix(pattern...)

在布林環境裡,它是表示式 uri prefix any(pattern1, pattern2, ...) 的簡寫。

在字串環境裡(比如在 as 表示式),它返回實際可以匹配上的第一個模式的子串。

回到目錄

uri-seg

語法: uri-seg(index...)

語法: uri-seg(&ast;)

這個函式把 URI 的路徑串當作用斜槓(/)分隔的多個,然後返回指定索引的段。 這個段索引從 1 開始,在 URI 裡頭從左到右遞增。

比如,對於請求 URI /foo/bar/baz, uri-seg(1) 返回 foouri-seg(2) 返回 bar,而 uri-seg(3) 返回 baz。可以同時宣告多個索引, 像 uri-seg(2, 5) 裡頭。

如果宣告瞭任意值:* 為唯一的引數,這個函式返回所有 URI 段段路徑值。

回到目錄

uri-suffix

語法: uri-suffix(pattern...)

在布林環境裡,這是 uri suffix any(pattern1, pattern2, ...) 的簡寫。

在字串環境裡 (比如在 as 表示式 裡),它返回實際匹配第一個引數的子串。

回到目錄

user-agent

語法: user-agent()

語法: user-agent(pattern...)

不帶引數的時候,這個函式只是 req-header("User-Agent") 的簡寫。

如果帶引數,則其呼叫等效於 user-agent eq any(pattern1, pattern2, ...),也就是說,檢查使用者的瀏覽器串是否可以用 eq 運算子匹配任意輸入的模式。

回到目錄

uuid-v4

語法: uuid-v4()

生成一個 UUID 版本 4 字串值。

回到目錄

userid

語法: userid()

生成一個使用者 id 的字串值。

回到目錄

內建動作函式

add-req-header

語法: add-req-header(name, value)

語法: add-req-header(name1, value1, name2, value2, ...)

語法: add-req-header(%name-value-pairs)

新增新的請求頭,但不會覆蓋現存請求頭中同名頭。

比如:

true =>
    add-req-header("X-Foo", 1234);

如果你想覆蓋現存同名請求頭,請使用內建動作函式 set-req-header

回到目錄

add-resp-header

語法: add-resp-header(header, value)

語法: add-resp-header(header1, value1, header2, value2, ...)

語法: add-resp-header(%name-value-pairs)

給當前請求增加新的響應頭。不會影響現存請求裡面同名頭。如果你想覆蓋現存頭裡頭同名頭, 請使用 set-resp-header

下面是一些例子:

true =>
    add-resp-header("X-Powered-By", "OpenResty Edge");

給當前請求增加新的響應頭。不會影響現存請求裡面同名頭。如果你想覆蓋現存頭裡頭同名頭, 請使用 set-resp-header

回到目錄

add-uri-arg

語法: add-uri-arg(name, value)

語法: add-uri-arg(name1, value1, name2, value2, ...)

語法: add-uri-arg(%name-value-pairs)

給當前請求增加新的 URI 引數。不會影響 URI 中現存同名引數。如果想覆蓋同名 URI 引數, 請使用 set-uri-arg

下面是一些例子:

true =>
    add-uri-arg("uid", "1234");

回到目錄

add-uri-prefix

語法: add-uri-prefix(prefix)

為當前請求的 URI 新增一個字首字串。

請注意,字首值不需要以斜槓(/)結尾,因為現有的 URI 字串無論如何都必須以斜槓開頭。

下面是一個例子:

true =>
    add-uri-prefix("/en/us");

對於請求 GET /install.html,這條規則會使 URI 變成 /en/us/install.html

回到目錄

apply-std-mime-types

語法: apply-std-mime-types()

語法: apply-std-mime-types(force: true)

根據檔案字尾名設定標準的 Content-Type 響應頭。

預設情況下,僅當源響應頭中無 Content-Type 響應頭時生效。 但使用者可以使用 force: true 選項來覆蓋原有的 Content-Type 響應頭。

回到目錄

basic-authenticate

語法: basic-authenticate(auth-id: AUTH-ID)

啟用 HTTP 的基本認證功能。

auth_id 引數是由授權列表型別和授權列表 ID 組成的。

如果這個授權列表是在應用內配置的,這個引數應該這樣組合 app-auth:<list_id>

如果這個授權列表是在全域性配置中的,那麼引數應該是 global-auth:<list_id>

下面是一個例子:

basic-authenticate(auth-id: "app-auth:1") =>
    say("ok");

basic-authenticate(auth-id: "global-auth:1") =>
    say("ok");

回到目錄

foreign-call

語法: foreign-call(module: <module>, func: <func>, arg...)

語法: foreign-call(func: <func>, arg...)

語法: foreign-call(func: <func>)

發起一個用目標語言(比如 Lua)對外部函式的呼叫。

可選的命名引數 module 宣告外部模組名。 如果省略了這個引數,預設是外部語言的標準名字空間。

func 名引數生命外部呼叫的函式名。這個引數是必須的。

任何其它位置引數都會當作傳遞給外部函式呼叫。

參考 呼叫外部程式碼 獲取更多細節。

回到目錄

enable-edge-captcha

語法: enable-edge-captcha(clearance-time: CLEARANCE-TIME)

語法: enable-edge-captcha(clearance-time: CLEARANCE-TIME, page-template-id: PAGE-TEMPLATE-ID)

為當前請求啟用 Edge 驗證碼。如果請求已經透過驗證,將被允許透過。

clearance-time 引數指定成功驗證的有效期(以秒為單位)。

可選的 page-template-id 引數指定自定義驗證碼頁面模板的 ID。此模板必須預先在全域性頁面模板中配置。

以下是示例:

使用預設模板,不需要指定 page-template-id 引數。

true =>
    enable-edge-captcha(clearance-time: 30),
    done;

指定頁面模板:

true =>
    enable-edge-captcha(clearance-time: 60, page-template-id: 1),
    done;

回到目錄

enable-hcaptcha

語法: enable-hcaptcha(clearance-time: CLEARANCE-TIME)

語法: enable-hcaptcha(clearance-time: CLEARANCE-TIME, page-template-id: PAGE-TEMPLATE-ID)

在使用 hCaptcha 之前,您需要在全域性配置中配置 hCaptcha 的站點金鑰和金鑰:hCaptcha

為當前請求啟用 hCaptcha 驗證。如果請求已經透過驗證,將被允許透過。

clearance-time 引數指定成功驗證的有效期(以秒為單位)。

可選的 page-template-id 引數指定自定義 hCaptcha 頁面模板的 ID。此模板必須預先在全域性頁面模板中配置。

以下是示例:

使用預設模板,不需要指定 page-template-id 引數。

true =>
    enable-hcaptcha(clearance-time: 30),
    done;

指定頁面模板:

true =>
    enable-hcaptcha(clearance-time: 60, page-template-id: 1),
    done;

回到目錄

enable-otel-trace

語法: enable-otel-trace()

該命令用來啟用 otel trace 功能。

舉例如下:

random-hit(0.05) =>
    enable-otel-trace();

回到目錄

enable-proxy-cache

語法: enable-proxy-cache(key: KEY)

為當前請求開啟使用者提供快取鍵字的代理快取,預設的時候代理快取是關閉的。

回到目錄

enable-global-cache

語法: enable-global-cache()

使用全域性快取。全域性快取將在不同的 APP 中共享,預設的時候全域性快取是關閉的。

回到目錄

enable-gateway-gzip

語法: enable-gateway-gzip()

語法: enable-gateway-gzip(enabled)

階段: resp-header

為當前請求動態開啟/關閉 gzip 壓縮。

如果忽略引數,則預設開啟閘道器 gzip。

如果使用了一個 bool 引數,意思是開啟或關閉閘道器 gzip。

下面是例子:

uri-prefix("/css/") =>
    defer resp-header {
        enable-gateway-gzip;
    };

回到目錄

enable-proxy-cache-revalidate

語法: enable-proxy-cache-revalidate()

語法: enable-proxy-cache-revalidate(enabled)

階段: rewrite

是否啟用 proxy_cache_revalidate 功能。

如果忽略引數,則預設開啟 proxy_cache_revalidate 功能。

下面是例子:

true =>
    enable-proxy-cache-revalidate(true);

回到目錄

enable-ssl-client-verify

語法: enable-ssl-client-verify()

開啟客戶端證書認證,如果認證失敗,會返回 400 狀態碼並退出當前請求。

例如:

true =>
    enable-ssl-client-verify();

回到目錄

enforce-proxy-cache

語法: enforce-proxy-cache(time)

set-proxy-cache-default-ttl 類似, 但將忽略響應頭行為,強制快取當前響應 (也就是說,忽略 Cache-Control,Set-Cookie,Expires 等等)。

回到目錄

errlog

語法: errlog(level: LEVEL, msg...)

語法: errlog(msg...)

透過命名引數 level 指定的日誌級別產生一條錯誤資訊。 預設是 error 錯誤級別。

level 可以是下面列表中的值:

  • error
  • warn
  • stderr
  • emerg
  • alert
  • crit
  • notice
  • info
  • debug

錯誤資訊可以是多個字串引數。這個函式會自動把他們連線起來。

一些例子:

true =>
    errlog(level: "alert", "Something bad", " just happened!"),
    errlog("The user is not authorized");

回到目錄

exit

語法: exit(code)

以狀態碼 code 退出當前請求處理。 如果到這個函式呼叫為止還沒有響應資訊傳送出去,那麼這個呼叫會為當前這個狀態碼生成一個預設的錯誤頁面————前提是這個狀態碼可以被識別。

如果想立即終止連線,請使用特殊的退出碼 444

回到目錄

expires

語法: expires(time)

語法: expires(time, force: true)

給響應頭增加或者修改 ExpiresCache-Control 的設定為指定的過期時間。

預設的嘶吼,這個函式只應用於狀態碼是 200, 201, 204, 206, 301, 302, 303, 304, 307 或者 308 的響應。 使用者可以透過宣告命名引數 force:true 來強制用於任何狀態碼。

time 位置引數必須是一個 量綱,接受一個時間單位, 像 [sec] (秒),[min](分鐘),[hour] (小時),或者 [day] (天)。

下面是一個例子:

uri-prefix("/css/") =>
    expires(1 [day]);

這個動作不影響 proxy cache 過期時間。請參考 cache-expires

回到目錄

limit-req-concurrency

語法: limit-req-concurrency(key: KEY, target-n: COUNT, reject-n: COUNT, log-headers: BOOL)

在使用者宣告的鍵字上限制到來請求大併發程度。

在執行這個設定之後,世紀的併發級別將保證不超過有名引數 target-n 的值。如果到來的併發程度在 target-nreject-n 之間,那麼當前的請求將會被延遲一個合適的時間以滿足目標併發級別。

如果到來的請求併發程度已經超過了 reject-n 值,那麼當前請求將立刻被以一個 503 錯誤頁面拒絕(針對 HTTP/HTTPS 應用)或者直接丟包(針對 DNS 應用)。

log-headers 設定為 true 時,錯誤日誌中會記錄請求頭,預設為 false。

下面是例子:

true =>
    limit-req-concurrency(key: client-addr, target-n: 100, reject-n: 200);

回到目錄

limit-req-count

語法: limit-req-count(key: KEY, target-n: NUM, reset-time: SECONDS, log-headers: BOOL)

在使用者宣告的鍵字上,限制在指定時間視窗 SECONDS 內的最多請求數量 NUM

有名引數 key 是可選的,可以是任意的字串。如果省略之,則等效於一個常量鍵字,意味著針對整個應用的限制。

如果請求數超過 NUM,那麼請求控制代碼將會立即以 503 錯誤頁面(對 HTTP/HTTPS 應用)拒絕當前請求,或者是立即丟包(對 DNS 應用)。

log-headers 設定為 true 時,錯誤日誌中會記錄請求頭,預設為 false。

下面是例子:

true =>
    limit-req-count(key: client-addr, target-n: 10, reset-time: 60);

回到目錄

limit-req-rate

語法:

limit-req-rate(key: KEY, target-rate: RATE, reject-rate: RATE,
    reject-action: ACTION,
    hcaptcha-clearance-time: HCAPTCHA-CLEARANCE-TIME,
    edge-captcha-clearance-time: EDGE-CAPTCHA-CLEARANCE-TIME,
    redirect-validate-clearance-time: REDIRECT-VALIDATE-CLEARANCE-TIME,
    error-page-status-code: STATUS-CODE,
    log-headers: BOOL,
    page-template-id: PAGE-TEMPLATE-ID)

在使用者宣告的鍵字上限制請求速率。

有名引數 key 是可選的,可以是任意的字串。如果省略之,則等效於一個常量鍵字,意味著針對整個應用的限制。

有名引數 target-rate 是我們想約束的最大速率。

如果到來的速率大於 reject-rate,那麼請求控制代碼將會立即以 503 錯誤頁面(對 HTTP/HTTPS 應用)拒絕當前請求,或者是立即丟包(對 DNS 應用)。

如果到來的速率在 target-ratereject-rate 之間,這個動作會等待應用一段時間以便讓當前請求匹配 target-rate

reject-rate 的值不能小於 target-rate

兩個速率的數值都必須接受一個類似 [r/s][r/min] 這樣的單位。

有名引數 reject-action 是可選的,支援以下動作:

  • enable_hcaptcha 表示觸發 hCaptcha,引數 HCAPTCHA-CLEARANCE-TIME 表示校驗透過後多長時間不需要再次校驗。

  • enable_edge_captcha 表示觸發 edge captcha,引數 EDGE-CAPTCHA-CLEARANCE-TIME 表示校驗透過後多長時間不需要再次校驗。

  • error_page 表示返回自定義的錯誤頁面,引數 STATUS-CODE 表示返回的狀態碼。

  • close_connection 表示直接斷開連線。與 STATE-CODE 為 444 的 error_page 等價。

  • redirect_validate 表示進行重定向驗證。

  • js_challenge 表示進行 js 挑戰。

  • page_template 表示返回頁面模板,頁面模板透過 ID 進行指定。

log-headers 設定為 true 時,錯誤日誌中會記錄請求頭,預設為 false。

下面是例子:

true =>
    limit-req-rate(key: client-addr,
        target-rate: 10 [r/s],
        reject-rate: 20 [r/s],
        reject-action: "enable_hcaptcha",
        hcaptcha-clearance-time: 50);

使用者可以在一個請求頭裡頭為不同的鍵字發起多個 limit-req-rate 呼叫。

回到目錄

limit-resp-data-rate

語法: limit-resp-data-rate(rate)

語法: limit-resp-data-rate(rate, after: size)

在傳送響應(體)資料的時候限制速率。 位置引數 rate 宣告最高速度的速率。它必須是一個量綱, 接受一個速率單位,比如 [kB/s]

可選的命名引數 after 接收一個帶著尺寸單位(比如 kBmB)的引數。

下面是一個例子:

true =>
    limit-resp-data-rate(100 [kB/s], after 200 [kB]);

請注意小寫的 k 字首代表的是 1000,而大寫的 K 字首的意思是 1024。類似的是,小寫的 b 單位意思是 bit, 而大寫的 B 意思是 byte,意思是一個八位元。

回到目錄

local-time

語法: local-time()

語法: local-time(year: YEAR, month: MONTH, day: MDAY, hour: HOUR, min: MINUTE, sec: SECOND)

返回指定時間(當前時區)的 Unix 時間戳(從協調世界時 1970 年 1 月 1 日 0 時 0 分 0 秒起至現在的總秒數), 注意返回值為有單位的數值。

如東八區的 2019-01-01 00:00:00 會返回 1546272000 [s]

如果呼叫時沒有帶引數,則會返回當前 Unix 時間戳(從協調世界時 1970 年 1 月 1 日 0 時 0 分 0 秒起至現在的總秒數), 返回值同樣是有單位的數值。

如果帶了部分引數,則剩餘的部分將會被預設值填充:

  • YEAR: 0
  • MONTH: 1
  • MDAY: 1
  • HOUR: 0
  • MINUTE: 0
  • SECOND: 0

例:

true =>
    local-time().say; # current timestamp (quantity typed value)

# 1546272000 [s]
true =>
    local-time(year: 2019, month: 1, day: 1, hour: 0, min: 0, sec: 0).say,
    local-time(year: 2019).say;

回到目錄

local-time-day

語法: local-time-day()

返回當前日期中的幾號。

2019-01-02 03:04:05 中會返回 2

true =>
    local-time-day().say;

回到目錄

local-time-hour

語法: local-time-hour()

返回當前時間中的小時數。

例如 2019-01-02 03:04:05 將會返回 3

true =>
    local-time-hour().say;

回到目錄

local-time-min

語法: local-time-min()

返回當前時間的分鐘數。

例如 2019-01-02 03:04:05 將會返回 4

true =>
    local-time-min().say;

回到目錄

local-time-sec

語法: local-time-sec()

返回當前時間的秒數。

例如 2019-01-02 03:04:05 將會返回 5

true =>
    local-time-sec().say;

回到目錄

print

語法: print(msg...)

生成客戶化的響應體資料片段。如果響應頭還沒傳送,那麼它會在響應體之前自動傳送 ———— 這個原因很明顯哈。

say 動作不一樣,這個動作不會在每條使用者訊息後頭附加一個新行字元。

比如:

true =>
    print("hello", ", world!"),
    print(" oh, yeah");

回到目錄

redirect

語法: redirect(uri: URI)

語法: redirect(host: HOST, uri: URI, args: ARGS)

語法: redirect(scheme: SCHEME, host: HOST, uri: URI, code: CODE)

階段: rewrite

傳送 HTTP 重定向響應。它接受下列命名引數:

  • uri

    URI 字串,去掉了所有查詢串字尾,以及去掉了所有主機/模式字首。

  • args

    URI 查詢串或者一個帶著引數鍵值對的表。預設是沒有。

  • host

    將要重定向的主機名。這是可選的,預設是當前伺服器。

  • scheme

    協議模式,比如 httphttps。預設是當前請求的協議模式。

  • code

    準備使用的狀態碼。可以是 301, 302, 303 或者 307。預設是 302。

比如:

uri("/foo") =>
    redirect(uri: "/blah/bah.html");

uri("/foo") =>
    redirect(scheme: "https", host: "a.foo.com", uri: "/blah/bah.html",
             args: "a=1&b=4", code: 301);

回到目錄

replace-resp-filter

語法: replace-resp-filter(string, replacement)

語法: replace-resp-filter(string, replacement, g: BOOL)

語法: replace-resp-filter(regex, replacement)

語法: replace-resp-filter(regex, replacement, g: BOOL)

這個函式用於替換響應體中的內容,可以匹配固定字串或正規表示式模式。它只能在 resp-body 的 defer 塊中使用。

引數說明:

  • stringregex:要匹配的字串或正規表示式。
  • replacement:用於替換匹配內容的字串或使用者定義函式。
    • 如果是字串,可以包含子模式捕獲變數(如 $1$2 等)。
  • g:可選布林引數,預設為 false。
    • 當設為 true 時,替換所有匹配項。
    • 當為 false 時,只替換第一個匹配項。

示例:

  • replace-resp-filter("hello", "hi") - 將第一個 “hello” 替換為 “hi”
  • replace-resp-filter("hello", "hi", g: true) - 將所有 “hello” 替換為 “hi”
  • replace-resp-filter(/\d+/, "number") - 將第一個數字替換為 “number”

這個函式主要用於在將響應傳送給客戶端之前修改響應內容。比如:

host('test1.com') =>
    defer resp-body {
        replace-resp-filter(/\d+/, "number", g: true);
    };

host('test2.com') =>
    defer resp-body {
        replace-resp-filter(/(\d+)/, "number: $1", g: true);
    };

高階用法:

對於更復雜的替換需求,例如需要對子模式捕獲變數進行轉換,可以使用使用者定義函式。以下示例展示瞭如何將捕獲的文字轉換為小寫:

func transform(Str $full-match, Str @groups) =
    "before: $full-match" ~ ', after: ' ~ lower-case(@groups[0]) ~ ' ' ~ lower-case(@groups[1]) ~ @groups[2];

true =>
    defer resp-body {
        replace-resp-filter(rx:i/(hello)\s*(world)(!)/, transform);
    },
    say("HELLO WORLD!");

在這個例子中:

  • transform 函式接收兩個引數:
    • $full-match:完整的匹配內容
    • @groups:子模式捕獲變數陣列
  • @groups[0] 代表第一個捕獲組(HELLO),@groups[1] 代表第二個捕獲組(WORLD),以此類推。
  • 這個示例最終會返回:before: HELLO WORLD!, after: hello world!

透過使用使用者定義函式,您可以實現更靈活和強大的內容替換邏輯。

回到目錄

rewrite-uri-seg

語法: rewrite-uri-seg(index, replacement)

語法: rewrite-uri-seg(index1, replacement1, index2, replacement2, ...)

這個函式把 URI 路徑字串當作一個用斜槓(/)分隔的,然後用指定的下標和在引數 replacement 裡頭的值替換這些段。段下表從 1 開始計,並且在 URI 路徑中從左向右增長。

比如,對於請求 URI /foo/bar/bazrewrite-uri-seg(1, "qux") 生成一個新的 URI /qux/bar/baz,而 rewrite-uri-seg(2, "qux") 生成 /foo/qux/baz。 多個下標可以同時宣告,比如 rewrite-uri-seg(2, "qux", 3, "foo") 生成 /foo/qux/foo

回到目錄

語法: rm-req-cookie(name)

語法: rm-req-cookie(name1, name2, ...)

刪除請求裡 cookie 名和引數匹配的 cookie。

下面是一個例子:

true =>
    rm-req-cookie("foo", "bar");

回到目錄

rm-req-header

語法: rm-req-header(pattern...)

如果請求裡頭的某個欄位的名字可以用 eq 關係運算子匹配上位置引數給出的那些模式,就刪除它們。

模式可以是一個文字串,一個正則或者一個萬用字元。

下面是一個例子:

true =>
    rm-req-header("Authorization", rx/X-.*/, wc/Internal-*/);

這條規則無條件刪除任何名字是 Authorization、任何名字以 X- 開頭或者任何名字以 Internal- 開頭的請求頭。

回到目錄

語法: rm-resp-cookie(name...)

刪除響應頭中一個或多個指定名稱的 Cookie。

如果響應裡頭的 cookie 名可以用 eq 關係運算子匹配上位置引數給出的那些模式,就刪除它們。

這裡是一個例子:

true =>
    rm-resp-cookie("_uid","foo");

上面這條規則無條件刪除響應裡頭名字是 _uidfoo 的 cookie。

如果想要刪除所有 Cookie,可以使用 rm-resp-header("Set-Cookie")

回到目錄

rm-resp-header

語法: rm-resp-header(pattern...)

如果響應頭裡頭的某個欄位的名字可以用 eq 關係運算子匹配上位置引數給出的那些模式,就刪除它們。

模式可以是一個文字串,一個正則,一個萬用字元,或者一個任意值。

下面是一個例子:

true =>
    rm-resp-header("Set-Cookie", rx/X-.*/, wc/Internal-*/);

這條規則無條件刪除任何響應頭名字是 Set-Cookie 、或者名字以 X- 開頭、以 Internal- 開頭的響應頭。

回到目錄

rm-uri-arg

語法: rm-uri-arg(name...)

刪除指定名字的 URI 引數。

true =>
    rm-uri-arg("foo");

回到目錄

rm-uri-prefix

語法: rm-uri-prefix(pattern...)

刪除那些能夠匹配使用者透過引數宣告的第一個模式的 URI 字首。

比如:

true =>
    rm-uri-prefix("/foo/", rx{/foo\d+/});

對請求 URI /foo/hello,這條規則會把 URI 變成 /hello。而對請求 /foo1234/, 這條規則會生成新 URI /

回到目錄

rm-uri-seg

語法: rm-uri-seg(index...)

這個函式把 URI 路徑當作一個用斜槓(/)分隔的多個組成的陣列,然後刪除指定下標的段。 段索引從 1 開始計,在 URI 中從左向右遞增。

比如,對於請求 URI /foo/bar/bazrm-uri-seg(1) 會生成一個新的 URI /bar/bazrm-uri-seg(2) 生成 /foo/baz,而 rm-uri-seg(3) 返回 /foo/bar/。 一次可以宣告多個下標,比如 rm-uri-seg(2,5)

回到目錄

say

語法: say(msg...)

生成客戶化定製的響應體資料塊,並且自動字尾一個新行。 顯然,如果還沒有傳送響應頭,那麼會在傳送響應體資料之前自動傳送響應頭。

如果你不想要背後的新行,那麼可以用 print

比如:

true =>
    say("hello", ", world!"),
    say(" oh, yeah");

回到目錄

set-error-page

語法: set-error-page(resp-body: CONTENT, content-type: CONTENT-TYPE, error_code…)

語法: set-error-page(refetch-url: URL, content-type: CONTENT-TYPE, error_code…)

語法: set-error-page(page-template-id: ID, content-type: CONTENT-TYPE, error_code…)

對指定的錯誤響應碼設定錯誤頁,其中 content-type 欄位可選。

錯誤頁支援兩種設定形式:

  1. resp-body: HTML 內容
  2. refetch-url: 靜態資源 URL
  3. page-template-id: 頁面模板的 ID

注意:不支援同時使用兩個或以上的方式設定錯誤頁,否則會報錯。

支援以下的錯誤響應碼:

  • 403
  • 404
  • 500
  • 501
  • 502
  • 503
  • 504

例:

true =>
   set-error-page(404, resp-body: "<h1>Not Found</h1>");

true =>
   set-error-page(500, refetch-url: "http://example.com/error.html")

回到目錄

set-otel-span-name

語法: set-otel-span-name('name')

phase: rewrite

預設的 span 名稱是 URI。使用該介面設定新的 span 名稱。

舉例如下:

true =>
    set-otel-span-name('new-span-name');

回到目錄

set-proxy-cache-default-ttl

語法: set-proxy-cache-default-ttl(time, status: STATUS)

設定代理快取預設的過期時間,當響應狀態碼為 STATUS 時生效。

預設的 status200,目前僅支援 200, 301, 302

位置引數 time 必須是一個量綱,接收一個時間單位,比如 [sec] (秒),[min] (分鐘),[hour](小時),以及 [day] (天)。

下面是例子:

uri-prefix("/css/") =>
    set-proxy-cache-default-ttl(1 [day]);

這個動作不影響當前響應的 ExpiresCache-Control 響應頭。如果您需要覆蓋瀏覽器的快取時間,可以這樣使用:

uri-prefix("/css/") =>
    set-proxy-cache-default-ttl(1 [day]);
    expires(12 [hour]);

我們可以給節點快取(透過 set-proxy-cache-default-ttl) 和瀏覽器(透過 expires)宣告不同的過期時間。

又見 expires.

回到目錄

set-proxy-cache-use-stale

語法: set-proxy-cache-use-stale('off')

語法: set-proxy-cache-use-stale('http_500', 'invalid_header', ...)

階段: rewrite

修改當前請求的 proxy_cache_use_stale 配置,如果傳入 off 則會關閉 proxy-cache-use-stale 功能。

下面是例子:

true =>
    set-proxy-cache-use-stale('http_500', 'invalid_header');

回到目錄

語法: set-req-cookie(name, value)

語法: set-req-cookie(name1, value1, name2, value2, ...)

設定新的請求 cookie,會覆蓋任何現有同名的 cookie。

比如:

true
=>
    set-req-cookie("foo", "foo", "bar", "bar"),
    say("foo:" ~ req-cookie("foo")),
    say("bar:" ~ req-cookie("bar"));

回到目錄

set-req-header

語法: set-req-header(name, value)

語法: set-req-header(name1, value1, name2, value2, ...)

語法: set-req-header(%name-value-pairs)

設定請求頭,覆蓋任何現有同名的請求頭。

比如:

uri-prefix("/foo/") =>
    set-req-header("X-Debug", 1);

如果你只想新增新的請求頭而不是覆蓋現有的,那麼請使用內建動作 add-req-header

回到目錄

req-body

語法: req-body()

獲取請求體。

比如:

req-body =>
    req-body.say;

回到目錄

set-req-body

語法: set-req-body(body)

設定請求體內容,會覆蓋當前的請求體。

比如:

uri-prefix("/foo/") =>
    set-req-body("foo");

回到目錄

set-proxy-host

語法: set-proxy-host(host)

預設代理注意是當前請求主機。

回到目錄

set-proxy-header

語法: set-proxy-header(header, value)

語法: set-proxy-header(header1, value1, header2, value2, ...)

給代理的伺服器設定代理頭。現有同名頭會被刪除。

你可以用這個函式把一個客戶端和伺服器之間的連線,從 HTTP/1.1 改成 WebSocket,下面是例子:

true =>
    set-proxy-header("Upgrade", "WebSocket",
                     "Connection", "Upgrade");

用這個 API 不能設定的頭列表如下:

  • Content-Length
  • Transfer-Encoding
  • If-Modified-Since
  • If-None-Match

回到目錄

set-proxy-uri

語法: set-proxy-uri(uri, [query-string: QUERY-STRING])

將上游代理伺服器收到的請求 URI 設定為一個新數值,不改變當前 URI。 這裡的 URI 不應該包含任何查詢串或者任何主機/埠部分。query-string 引數是可選的。

true =>
    set-proxy-uri("/foo.html");

true =>
    set-proxy-uri("/foo.html", query-string: "foo=bar");

回到目錄

append-proxy-header-value

語法: append-proxy-header-value(header, value)

語法: append-proxy-header-value(header1, value1, header2, value2, ...)

給代理伺服器追加代理頭。 若對應代理頭欄位非空,則會用 value 追加到代理頭欄位現有數值後,用逗號分隔。 若代理頭欄位為空,代理頭欄位值將會是 value

比如:

true =>
    appear-proxy-header-value("X-Forwarded-For", client-addr);
    # 當原始 `X-Forwarded-For` 請求頭內容為 `192.168.1.1`
    # 且客戶端地址為 `10.10.1.1` 時
    # 代理伺服器收到的 `X-Forwarded-For` 請求頭內容被設定為 `192.168.1.1,10.10.1.1`

回到目錄

block-req

語法:

block-req(key: KEY, target-rate: RATE, reject-rate: RATE,
                    block-threshold: COUNT,
                    observe-interval: COUNT,
                    block-time: TIME,
                    log-headers: BOOL,
                    reject-action: REJECT-ACTION,
                    status-code: STATUS-CODE,
                    clearance-time: CLEARANCE-TIME,
                    page-template-id: PAGE-TEMPLATE-ID)

根據指定的關鍵字來限制請求的頻率。

其中,引數 key 可選。當未設定 key 時,語句就等價於使用常量作為關鍵字。

target-rate 引數代表想要設定的頻率上限。

當請求頻率達到 reject-rate 時,當前請求立即會被拒絕並:

  • HTTP/HTTPS 應用:響應一個 503 頁面
  • DNS 應用:立即丟包

當請求頻率大於 target-rate 且小於 reject-rate 時,當前請求會被延遲處理, 去試圖達到 target-rate 設定的值。

注意:reject-rate 必須大於 target-rate,且兩者都需要帶上類似 [r/s][r/min] 的單位。

observe-interval: 檢測間隔的時間視窗大小,單位為秒 (s) block-threshold: 連續檢測的次數

當請求率在一個檢測間隔內達到了 reject-rate,在接下來的 block-time 時間內的請求, 都會被拒絕並響應一個 503 頁面。

log-headers 設定為 true 時,錯誤日誌中會記錄請求頭,預設為 false。

有名引數 reject-action 是可選的,支援以下動作:

  • enable_hcaptcha 表示觸發 hCaptcha,引數 CLEARANCE-TIME 表示校驗透過後多長時間不需要再次校驗。

  • enable_edge_captcha 表示觸發 edge captcha,引數 CLEARANCE-TIME 表示校驗透過後多長時間不需要再次校驗。

  • error_page 表示返回自定義的錯誤頁面,引數 STATUS-CODE 表示返回的狀態碼。

  • close_connection 表示直接斷開連線。與 STATE-CODE 為 444 的 error_page 等價。

  • redirect_validate 表示進行重定向校驗。引數 CLEARANCE-TIME 表示校驗透過後多長時間不需要再次校驗。

  • js_challenge 表示進行 js 挑戰。

  • page_template 表示返回頁面模板,頁面模板透過 ID 進行指定。

示例:

true =>
    block-req(key: client-addr, target-rate: 10 [r/s], reject-rate: 20 [r/s],
              block-threshold: 2, observe-interval: 30, block-time: 60);

使用者在一個請求處理中,可以呼叫多次 block-req,實現多個約束的請求限制。

回到目錄

這個 API 不能使用的頭的的列表:

  • Host
  • Connection
  • Upgrade
  • Content-Length
  • Transfer-Encoding
  • If-Modified-Since
  • If-None-Match

回到目錄

set-req-host

語法: set-req-host(host)

把請求的 Host 請求頭設定成位置引數 host 的值。 它只是 set-req-header("host", host) 的縮寫。

這個動作不回讓當前請求重新匹配新的虛擬伺服器。它通常只是影響轉發給上級伺服器的 Host 請求頭。

比如:

host("images.foo.com") =>
    set-req-host("images.foo.com.s3.amazonaws.com");

回到目錄

語法: set-resp-cookie(name, value, domain: DOMAIN, path: PATH, http-only: BOOL, expires: TIME, max-age: TIME)

設定一個新的響應 cookie,覆蓋任何現有同名 cookie。

回到目錄

語法: set-resp-cookie-samesite(value)

語法: set-resp-cookie-samesite(value, names: NAMES)

設定響應 cookie 的 SameSite 屬性,value 可以為 StrictLax。預設會修改所有的響應 cookie,可以透過 names 引數來指定要修改的響應 cookie。

下面是一個例子:

true =>
    set-resp-cookie-samesite("Lax", names: ("cookie1", "cookie2"));

回到目錄

set-resp-header

語法: set-resp-header(header, value)

語法: set-resp-header(header1, value1, header2, value2, ...)

語法: set-resp-header(%name-value-pairs)

給當前響應設定響應頭。現存同名響應頭會唄刪除。如果你不想覆蓋現存同名響應頭, 請使用add-resp-header

下面是一個例子:

true =>
    set-resp-header("X-Powered-By", "OpenResty Edge");

回到目錄

set-resp-body

語法: set-resp-body(value)

給當前響應設定響應體,只能在 resp-body 的 defer 塊中使用。

下面是一個例子:

true =>
    defer resp-body {
        set-resp-body("hello world");
    };

回到目錄

capture-resp-body

語法: capture-resp-body(size)

階段: rewrite resp-header

捕獲響應體到日誌變數 $response_body 中,需要設定最大值,單位為位元組。 如果響應體大小超過設定的最大值,只會捕獲部分響應體內容。

接受一個帶 kBmB 這樣的尺寸單位的 size 引數。

下面是一個例子:

true =>
    capture-resp-body(1 [kB]);

true =>
    defer resp-header {
        {
            resp-status(403) =>
                capture-resp-body(8192);
        };
    };

回到目錄

set-resp-status

語法: set-resp-status(code)

把當前響應的狀態碼設定為引數 code 的值。

不要在響應頭已經發出之後呼叫這個動作(比如響應頭已經被 一個 print 或者 say 呼叫觸發)。

下面是一個例子:

req-header("User-Agent") eq "" =>
    set-resp-status(450),
    say("custom body message for 450 status"),
    exit(450),
    done;

回到目錄

set-proxy-retry-condition

語法: set-proxy-retry-condition(...)

階段: rewrite

設定上游重試條件,預設值:error, timeout。 支援的值有: error, timeout, invalid_header, http_500, http_502, http_503, http_504, http_403, http_404, http_429, non_idempotent。

  • error:與伺服器建立連線、向其傳遞請求或讀取響應標頭時發生錯誤;
  • timeout:在與伺服器建立連線、向其傳遞請求或讀取響應標頭時發生超時;
  • invalid_header:伺服器返回了一個空的或無效的響應;
  • http_CODE:伺服器返回了指定的 HTTP 狀態碼;
  • non_idempotent:通常非冪等方法(POST、LOCK、PATCH)的請求只會傳遞給一個伺服器,啟用此選項明確允許重試此類請求;
true =>
    set-proxy-retry-condition('http_404'),
    set-proxy-retries(1),
    done;

示例中如果上游伺服器返回 404 狀態碼,則會進行 1 次重試。

回到目錄

set-proxy-retries

語法: set-proxy-retries(num)

設定重試次數,預設為 0,意味著預設情況下不會重試。

回到目錄

set-proxy-timeouts

語法: set-proxy-timeouts(connect: TIMEOUT?, send: TIMEOUT?, read: TIMEOUT?)

語法: set-proxy-timeouts(connect: TIMEOUT)

語法: set-proxy-timeouts(send: TIMEOUT)

語法: set-proxy-timeouts(read: TIMEOUT)

設定超時時間,預設 connect, send, read 都是 60s。

引數是一個量綱,如:60 [s], 60 [ms]。

回到目錄

set-proxy-recursion-depth

語法: set-proxy-recursion-depth()

語法: set-proxy-recursion-depth(DEPTH)

設定最大的代理遞迴深度,預設值為 -1,即不啟動該功能。

注意 當該功能啟用時,額外的請求頭 OR-Proxy-Recursion-Depth 會發往上游

set-uri

語法: set-uri(uri)

把當前請求 URI 設定為一個新的數值。這個 URI 不應該包含任何查詢串或者任何主機/埠部分。

這個動作主要用於改變那些準備透過代理轉發給上層服務的 URI 請求上。

回到目錄

set-uri-arg

語法: set-uri-arg(name, value)

語法: set-uri-arg(name1, value1, name2, value2, ...)

語法: set-uri-arg(%name-value-pairs)

在當前請求中設定 URI 引數。現存同名 URI 引數會被刪除。如果你不想覆蓋現存同名 URI 引數, 請用 add-uri-arg

下面是一個例子:

true =>
    set-uri-arg("uid", "1234");

回到目錄

set-upstream-name

語法: set-upstream-name(upstream-1, weight-1?, upstream-2?, weight2?)

階段: rewrite

設定一個或多個帶權重上游,這裡的 upstream-1, upstream-2 是上游名稱,在執行時會根據上游名字獲取上游資訊。

如果應用裡沒有這樣的上游,會從全域性上游裡查詢。

uri('/') =>
    set-upstream-name('upstream-1', 1, 'upstream-2', 1);

回到目錄

set-backup-upstream-name

語法: set-backup-upstream-name(upstream-1, upstream-2?)

階段: rewrite

設定應用的備用上游,當主上游錯誤並且符合重試條件的時候,會代理到備用上游。

uri('/') =>
    set-upstream-name('upstream-2'),
    set-proxy-retry-condition('error'),
    set-proxy-retries(1),
    set-backup-upstream-name('upstream-1');

回到目錄

set-upstream-addr

語法: set-upstream-addr(ip: IP, [host: DOMAIN], port: PORT , [scheme: SCHEME])

階段: rewrite

透過地址來設定單個上游,IP 引數和 host 引數不能同時使用。scheme 引數預設是 http

uri('/ip') =>
    set-upstream-addr(ip: '127.0.0.1', port: 80);

uri('/domain') =>
    set-upstream-addr(host: 'localhost', port: 80);

uri('/scheme') =>
    set-upstream-addr(host: 'localhost', port: 443, scheme: 'https');

回到目錄

upstream-has-live-nodes

語法: upstream-has-live-nodes(upstream-name)

階段: rewrite

檢查上游是否健康。

以下情況會返回 true:

  • 存在一個及以上健康的節點
  • 上游沒有開啟健康檢查

以下情況會返回 false

  • 不存在健康節點
  • 不存在指定名稱的上游
upstream-has-live-nodes('upstream-1') =>
    set-upstream-addr(ip: '127.0.0.1', port: 80);

回到目錄

set-upstream-retry-uri

語法: set-upstream-retry-uri(uri)

階段: rewrite

設定代理到上游伺服器失敗時的重試 URL,將會使用原 URI 重試完指定的重試次數後,再使用新的 URI 進行 1 次重試。 引數 uri 支援 edgelang 變數和字串常量。

true =>
    set-upstream-retry-uri("/hello"),
    set-proxy-retry-condition('http_404'),
    done;

示例中如果上游伺服器返回 404 狀態碼,將會使用 /hello 進行重試。

回到目錄

set-max-body-size

語法: set-max-body-size(size)

階段: rewrite

設定本次請求可以接受的最大 POST 體。對於那些具備有效 Content-Length 頭的請求,這個方法將檢查該頭欄位並且把超過 size 的請求立即終止,返回 413 Request Entity Too Large。對於分段的編碼請求和 HTTP/2 請求,此動作將隨著緩衝區處理進行檢查。

如果設定為 0 則關閉此檢查。

接受一個帶 kBmB 這樣的尺寸單位的 size 引數。

true =>
    set-max-body-size( 1 [kB]);  # 1000 bytes

回到目錄

set-access-log-off

語法: set-access-log-off()

階段: rewrite

設定本次請求不記錄 access log.

true =>
    set-access-log-off();

Back to TOC

sleep

語法: sleep(time)

不阻塞地睡眠指定的秒數。我們可以宣告精度為 0.001 秒(也就是 1 毫秒)的精度。

下面是例子:

true =>
    sleep(0.5);

回到目錄

utc-time

語法: utc-time()

語法: utc-time(year: YEAR, month: MONTH, day: MDAY, hour: HOUR, min: MINUTE, sec: SECOND)

返回指定時間 (UTC) 的 Unix 時間戳(從協調世界時 1970 年 1 月 1 日 0 時 0 分 0 秒起至現在的總秒數), 注意返回值為有單位的數值。

如 UTC 的 2019-01-01 00:00:00 會返回 1546272000 [s]

如果呼叫時沒有帶引數,則會返回當前 Unix 時間戳(從協調世界時 1970 年 1 月 1 日 0 時 0 分 0 秒起至現在的總秒數), 返回值同樣是有單位的數值。

如果帶了部分引數,則剩餘的部分將會被預設值填充:

  • YEAR: 0
  • MONTH: 1
  • MDAY: 1
  • HOUR: 0
  • MINUTE: 0
  • SECOND: 0

例:

true =>
    utc-time().say; # current timestamp (quantity typed value)

# 1546272000 [s]
true =>
    utc-time(year: 2019, month: 1, day: 1, hour: 0, min: 0, sec: 0).say,
    utc-time(year: 2019).say;

回到目錄

waf-mark-risk

語法: waf-mark-risk(level: LEVEL, msg: MESSAGE)

這是 WAF 模組的主要動作,用於標記當前請求為惡意請求,以及具體惡意程度, levelmsg 均為選填項。

WAF 的基本邏輯是根據當前請求的匹配資訊,標記不同的風險等級, 系統會自動根據來源 IP 進行彙總,當風險值累計達到攔截閾值等級(在介面上配置)時, 將執行提前設定的攔截動作(在介面上配置)。

level 表示惡意程度,level 可以是:

  1. definite 表示確定是危險請求,肯定將執行攔截動作(無論攔截閾值等級是多少)。
  2. high 表示高危險。
  3. middle 表示一般危險。
  4. low 表示低危險(預設)。
  5. debug 表示除錯規則,無論命中多少次都不會觸發攔截動作。

msg 為該規則的描述,當有請求命中該規則時, 它將出現在 admin 端的 WAF Logs 裡。

比如:

uri contains rx:s{root\.exe}
=>
    waf-mark-risk(level: 'definite');

uri contains any('nessustest', 'appscan_fingerprint')
=>
    waf-mark-risk(msg: 'Request Indicates a Security Scanner Scanned the Site');

回到目錄

waf-config

語法: waf-config(action: NAME, url: URL)

注意:在新版本里不推薦使用這個函式,推薦使用 run-waf-rule-sets

配置 WAF 攔截動作,action 為必填項,可以是這些值:

  1. log: 只是記錄,
  2. reject: 403 拒絕,
  3. redirect: 302 跳轉到指定地址。

actionredirect 時,url 必填,為跳轉的地址。

比如:

uri-prefix("/api/")
=>
    waf-config(action: "redirect", url: "http://foo.com/bar");

uri-prefix("/static/")
=>
    waf-config(action: "reject");

回到目錄

run-waf-rule-sets

語法: run-waf-rule-sets(action: ACTION, url: URL, key: KEY, threshold: THRESHOLD, observe-time: TIME, clearance-time: TIME, page-template-id: ID, name-1, name-2, ...)

執行指定的 WAF 規則集,當在觀察期 observe-time 內,累計的危險值達到閾值 threshold,將執行 action 動作。

危險值是按照 key 來累加的,key 預設是 client-addr

action 動作可以是:

  1. log:只是記錄,同時也是預設值。
  2. block:返回 403 狀態碼。
  3. redirect:302 跳轉到 url 引數指定的地址。
  4. hcaptcha:返回驗證碼頁面,使用 hCaptcha 服務:https://www.hcaptcha.com/
  5. edge-captcha:返回驗證碼頁面,使用 Edge 內建的驗證碼服務。
  6. page-template:返回根據頁面模板渲染後的頁面,支援 ::CLIENT_IP::::HOST::::HCAPTCHA_BOX::::CAPTCHA_BOX:: 等變數。
  7. close-connection:直接關閉 HTTP 連線。
  8. redirect-validate:對請求進行 302 跳轉驗證,驗證不透過則直接返回 403。可以用於防護 DDoS 攻擊。
  9. js-challenge:對請求進行 JS 挑戰。

actionredirect 的時候,url 引數必填。

actionhcaptchaedge-captchapage-template 的時候,才需要 clearance-time。透過驗證之後,在 clearance-time 時間內,具有相同 key 的所有請求都會被允許透過。

示例:

true
=>
    run-waf-rule-sets(action: "hcaptcha",
                      key: client-addr,
                      threshold: 100,
                      observe-time: 60 [s],
                      clearance-time: 300 [s],
                      "14", "15"
                      );

示例中的 1415 分別對應 OpenResty Edge 的 XSS 規則集(application_attack_xss)SQL 注入規則集(application_attack_sqli)

回到目錄

set-ssl-protocols

語法: set-ssl-protocols("protocol1", "protocol2", ...),

設定 HTTPS 請求的 SSL 握手協議。可選的協議型別為:SSLv2、SSLv3、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3。

示例:

true =>
    set-ssl-protocols("TLSv1.1", "TLSv1.2"),
    done;

set-ssl-ciphers

語法: set-ssl-ciphers("cipher1:cipher2:...")

設定 HTTPS 請求的 SSL 演算法。

示例:

true =>
    set-ssl-ciphers("DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"),
    done;

回到目錄

set-uploaded-file-args

語法: set-uploaded-file-args(max-content-len, max-file-count)

設定解析上傳檔案的引數。max-content-len 表示快取的檔案內容長度,0 表示不保留檔案內容,預設值是 0。 檔案內容通常用於 webshell 檢測。 max-file-count 表示快取的上傳檔案數量,0 表示不限制,預設值是 1。

示例:

true =>
    # 102400 = 100KB
    set-uploaded-file-args(max-content-len: 102400, max-file-count: 10);
    done;

回到目錄

uploaded-file-extensions

語法: uploaded-file-extensions()

獲取上傳的副檔名。

示例:

any(uploaded-file-extensions) eq any("txt") =>
    say("found txt file");

回到目錄

uploaded-file-contents

語法: uploaded-file-contents()

獲取上傳的檔案內容。預設不獲取檔案內容,透過 set-uploaded-file-args 設定長度後方可獲取。

示例:

any(uploaded-file-contents) contains any("attack data") =>
    say("found attack data");

回到目錄

uploaded-file-names

語法: uploaded-file-names()

獲取上傳的檔名。

示例:

true =>
    say(uploaded-file-names);

回到目錄

uploaded-file-combined-size

語法: uploaded-file-combined-size()

獲取上傳的檔案大小之和。

示例:

uploaded-file-combined-size > 1024 =>
    say("file too large");

回到目錄

uploaded-file-contents-matched

語法: uploaded-file-contents-matched()

檢查是否所有上傳的檔案的副檔名和內容都匹配。匹配返回 true,否則返回 false

示例:

uploaded-file-contents-matched =>
    say("all file extensions and contents match");

回到目錄

req-args-combined-size

語法: req-args-combined-size()

獲取 URI 和 POST 引數的大小之和。a=1&b=2 將返回 4,分別是 a1b2

示例:

req-args-combined-size > 1024 =>
    say("args too large");

回到目錄

validate-url-encoding

語法: validate-url-encoding(data)

檢查 data 字串是否是非法的 URL 編碼。非法返回 true, 合法返回 false

示例:

validate-url-encoding(req-uri) == false =>
    say("valid request url");

回到目錄

validate-byte-range

語法: validate-byte-range(data, "range1", "range2", ...)

檢查 data 字串中的字元是否都在所要求的範圍 (range1, range2, …) 中。 如果在所要求的範圍返回 false,不在則返回 true

示例:

validate-byte-range(any(uri-arg-names), "1-255") == true =>
    say("invalid uri arg names"),
    done;

回到目錄

validate-csrf-token

語法: validate-csrf-token()

語法: validate-csrf-token(ttl: TTL)

該動作與 inject-csrf-token 動作搭配使用,用於 CSRF 防護。如果請求方法是 HEAD, GET, TRACE, OPTIONS 中的一種,該動作會直接返回 ok。否則會檢查 URI 引數或 POST 表單引數中的 _edge_csrf_token 引數是否有效,如果有效則返回 ok,如果無效則返回相應的錯誤訊息。

錯誤訊息可能有:

  • missing csrf token:沒有 CSRF token 引數。
  • invalid csrf token:CSRF token 非法,可能是被偽造的引數。
  • expired csrf token:CSRF token 引數已過期。

CSRF Token 的有效期可以透過 ttl 引數設定,預設過期時間為 3600 秒。如果該引數為 0,則表示 CSRF Token 永不過期。

示例:

my Str $csrf-res;

true =>
    rm-req-header("Accept-Encoding"),
    defer resp-body {
        inject-csrf-token();
    },
    $csrf-res = validate-csrf-token(3600),
    {
        $csrf-res ne "ok" =>
            waflog($csrf-res, action: "block", rule-name: "csrf_protection"),
            exit(403);
    };

回到目錄

waflog

語法: waflog(msg)

語法: waflog(msg, action: ACTION, rule-name: RULENAME)

生成一條 WAF 日誌,如果需要,可以透過 actionrule-name 引數來指定 WAF 日誌中顯示的攔截動作和規則名稱。

一些例子:

true =>
    waflog("log"),
    waflog("forbidden", action: "block", rule-name: "custom-rule");

回到目錄

http-version

語法: http-version()

獲取 HTTP 請求的版本,取值有 0.9, 1.0, 1.1, 2.0。

一些示例:

http-version() eq "1.1" =>
    say(http-version());

回到目錄

req-line

語法: req-line()

獲取 HTTP 請求行。

一些示例:

true =>
    say(req-line());

輸出類似:GET /test HTTP/1.1

回到目錄

skip-json-values

語法: skip-json-values(uri-arg-values:true, post-arg-values:true, req-cookie-values:true)

WAF 不檢查值為 JSON 字串的 URI 引數或 Post 引數或 Cookie 引數。

一些示例:

true =>
    skip-json-values(uri-arg-values: true),
    run-waf-rule-sets(action: "block", threshold: 0, "12"),
    done;

回到目錄

is-json-string

語法: is-json-string(str)

判斷字串是否是 json 格式的。

一些示例:

is-json-string('{"k":"v"}') =>
    say("is json string"),
    done;

回到目錄

set-proxy-ignore-no-cache

語法: set-proxy-ignore-no-cache(enable)

設定忽略或不忽略 Cache-Control: no-cacheCache-Control: no-store

一些示例:

true =>
    enable-proxy-cache(key: uri),
    set-proxy-cache-default-ttl(1 [min]),
    set-proxy-ignore-no-cache(),
    set-upstream('my-upstream');

回到目錄

set-ngx-var

語法: set-ngx-var(key, value)

此指令用於將鍵值對設定到 ngx.var 中。

一些示例:

true =>
    set-ngx-var("foo", 32);

返回目錄

ngx-var

語法: val = ngx-var(key)

此指令用於從 ngx.var 中獲取指定鍵的值。

一些示例:

true =>
    say(ngx-var("foo"));

返回目錄

set-ctx-var

語法: set-ctx-var(key, value)

此指令用於將鍵值對設定到 ngx.ctx._edge_ctx 中。

一些示例:

true =>
    set-ctx-var("foo", 32);

返回目錄

ctx-var

語法: val = ctx-var(key)

此指令用於從 ngx.ctx._edge_ctx 中獲取指定鍵的值。

一些示例:

true =>
    say(ctx-var("foo"));

返回目錄

run-slow-ratio-circuit-breaker

語法: run-circuit-breaker(key: KEY, window-time: WINDOWN_TIME, open-time: OPEN_TIME, hopen-time: HOPEN_TIME, failure-time: FAILURE_TIME, failure-percent: FAILURE_PERCENT, min-reqs-in-window: MIN_REQS_IN_WINDOW, open-action: OPEN_ACTION, resp-status: RESP_STATUS, resp-body: RESP_BODY)

語法: run-slow-ratio-circuit-breaker(key: KEY, window-time: WINDOWN_TIME, open-time: OPEN_TIME, hopen-time: HOPEN_TIME, failure-time: FAILURE_TIME, failure-percent: FAILURE_PERCENT, min-reqs-in-window: MIN_REQS_IN_WINDOW, open-action: OPEN_ACTION, resp-status: RESP_STATUS, resp-body: RESP_BODY)

語法: run-failure-ratio-circuit-breaker(key: KEY, window-time: WINDOWN_TIME, open-time: OPEN_TIME, hopen-time: HOPEN_TIME, failure-status: FAILURE_STATUS, failure-percent: FAILURE_PERCENT, min-reqs-in-window: MIN_REQS_IN_WINDOW, open-action: OPEN_ACTION, resp-status: RESP_STATUS, resp-body: RESP_BODY)

語法: run-failure-count-circuit-breaker(key: KEY, window-time: WINDOWN_TIME, open-time: OPEN_TIME, hopen-time: HOPEN_TIME, failure-status: FAILURE_STATUS, failure-count: FAILURE_COUNT, min-reqs-in-window: MIN_REQS_IN_WINDOW, open-action: OPEN_ACTION, resp-status: RESP_STATUS, resp-body: RESP_BODY)

這些指令用於啟用熔斷器。當前支援的熔斷器型別有:慢請求比率熔斷器、錯誤比率熔斷器、錯誤計數熔斷器。預設使用“慢請求比率熔斷器”。

不同的熔斷器使用不同的 key 進行標識。

  • window-time: 滑動視窗的時間長度,用於計算錯誤或慢響應比率的統計時間範圍。
  • open-time: 熔斷器跳閘之後,保持開啟狀態的時間,在這段時間內所有請求進行特定的open-action
  • hopen-time: 半開狀態的時間長度,這是熔斷器嘗試恢復之前進行有限請求測試的階段。
  • failure-time: 請求被視為慢請求的時間閾值。
  • failure-status: 請求被視為失敗請求的狀態列表,如 502,503。
  • failure-percent: 觸發熔斷器跳閘的失敗或慢請求的百分比閾值。
  • failure-count: 觸發熔斷器跳閘的失敗請求的數量閾值。
  • min-reqs-in-window: 在滑動視窗時間內必須達到的最小請求次數,才會計算失敗百分比和失敗數量並考慮跳閘。
  • open-action: 當熔斷器處於開啟狀態時執行的動作,當前取值支援 exit 返回預設響應和 redirect 重定向到備用服務等。
  • resp-status: open-action 取值為 exit時,熔斷器開啟後,對請求返回的 HTTP 狀態碼。
  • resp-body: open-action 取值為 exit時,熔斷器開啟後,對請求返回的響應體內容。
  • redirect-url: open-action 取值為 redirect時,熔斷器開啟後,將請求重定向到指定的 URL。

一些示例:

true =>
    run-slow-ratio-circuit-breaker(key: "example", window-time: 60, failure-time: 500,
        failure-percent: 50, min-reqs-in-window: 2);
my Num @status-codes-1 = (502, 503, 504);
true =>
    run-failure-ratio-circuit-breaker(key: "example", window-time: 60, @status-codes,
        failure-percent: 50, min-reqs-in-window: 4),
my Num @status-codes-2 = (502, 503, 504);
true =>
    run-failure-count-circuit-breaker(key: "example", window-time: 60, @status-codes,
        failure-count: 2, min-reqs-in-window: 4),

返回目錄

req-rejected

語法: req-rejected()

此指令在 OpenResty Edge 24.9.1-7 中首次引入,用於檢查 HTTP 請求是否已經被限速動作 limit-req-rate、limit-req-count、limit-req-concurrency、block-req 標記為拒絕。

示例:

req-rejected() =>
    errlog("rejected"),
    exit(503);

示例中對被標記了的請求記錄日誌,並返回 503 狀態碼。

返回目錄

req-header-has-underscore

語法: req-header-has-underscore()

此指令在 OpenResty Edge 24.9.1-7 中首次引入,用於檢查 HTTP 請求頭的 Key 中是否存在下劃線。

示例:

req-header-has-underscore() =>
    exit(400);

示例中禁止 HTTP 請求頭中含有下劃線的請求。

返回目錄

案例分析

案例 1. 給響應頭 Content-Type 新增 charset 屬性

true =>
    defer resp-header {
        {
            resp-header("Content-type") contains any("html", "javascript", "xml"),
            resp-header("Content-type") !contains "charset=utf-8" =>
                set-resp-header("Content-type", resp-header("Content-type") ~ "; charset=utf-8");
        };
    };

案例 2. 當對應的響應頭不存在時新增該響應頭

true =>
    defer resp-header {
        {
            ! resp-header("Access-Control-Allow-Origin") =>
                set-resp-header("Access-Control-Allow-Origin", "*");
        };
    };

案例 3. 替換響應體中的內容

true =>
    defer resp-body {
      replace-resp-filter(rx{http://example.com/}, "https://new.example.com/", g: true);
    };

注意:如果響應已經編碼,可能會無法替換成功,需要設定請求頭 Accept-Encoding 來避免編碼。

案例 4. 向響應體插入 js 指令碼

true =>
    defer resp-body {
        replace-resp-filter(rx{<head>}, "<script>the script you want to insert</script><head>");
    };

注意:如果響應已經編碼,可能會無法替換成功,需要設定請求頭 Accept-Encoding 來避免編碼。

回到目錄

作者

Yichun Zhang <yichun@openresty.com>, OpenResty Inc.

回到目錄

譯者

Laser He <laser@openresty.com>, OpenResty Inc.

版權與許可證

Copyright (C) 2017-2020 by OpenResty Inc. All rights reserved.

本文件為商業所有權文件,包含商業機密資訊。未經版權所有者書面授權,嚴禁以任意形式重新分發此文件。

回到目錄