Edge Language User Manual

Table of Contents

Description

This manual documents the Edge language from the user's perspective.

Some of the features documented in this manual may not have been implemented in the current edgelang implementation. The edgelang development team is quickly catching up though. When in doubt, please contact the OpenResty Inc. company directly.

This document is still a draft. Many details are still subject to change.

Some big features have not yet been covered, like non-buffered pattern matching and substitutions in very large request and response body data streams.

Builtin predicate functions and actions for generic TCP/UDP proxies and DNS servers have not yet been covered.

Convention

We use the word edgelang for the Edge language throughout this document for convenience.

Example code with problems will get a question mark, ?, at the beginning of each of its lines. For example:

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

Bits and Pieces

Identifiers

Identifiers in edgelang is one or more words connected by dashes. A word is a sequence of alphanumeric characters and underscores. An underscore character cannot appear at the beginning of a word, however. Below are some examples of valid edgelang identifiers:

foo
hisName
uri-prefix
Your_Name1234
a-b_1-c

Edgelang is a case-sensitive language. So identifiers like foo and Foo do mean completely different things.

Identifiers cannot be one of the language keywords, except for variable names.

Back to TOC

Keywords

Edgelang has the following keywords:

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

Back to TOC

Variables

Edgelang variable names consist of two parts: a leading special character called a sigil and a following identifier. The sigil is used to denote the type of the variable. Edgelang supports the following three different sigils:

  • $ is for scalar variables
  • @ is for array variables
  • % is for hash variables

Scalar variables hold simple values like numbers, strings, booleans, and quantities.

Array variables are a ordered list container for simple values and other array or hash values.

Hash variables are an unordered list for key-value pairs.

Variables are usually declared by the my keyword as follows:

my $count;
my @domains;

Variable declarations may take an initial value too, as in:

my $count = 0;
my @domains = qw/ foo.com bar.blah.org /;

Each scalar variable declared in a user program can only have one value type throughout its lifetime. And each variable's value type must be able to be determined at compile time. There are 4 value types for scalar variables:

  • Str
  • Num
  • Quantity
  • Bool

The user can choose to explicitly specify a type for the scalar variable like this:

my Num $count;
my Str $name;

Such user type annotations are usually not required, but are encouraged to avoid any potential ambiguities.

Back to TOC

Request-Scoped Variables

Variables declared by the my keyword only has the scope of a code chunk. Every running phase in a request processing lifetime always has its own scope. To share variables across multiple phases of the same request lifetime, the user can use the our keyword in place of my to declare custom variables. For example:

our $is_mobile;
our Num $my-count;

Back to TOC

Rule-Scoped Variables

The user can also introduce custom rule-scoped variables via as expressions.

Special sub-match capturing variables, $1, $2, $3, and etc, can also be introduced implicitly by using capturing groups (...) inside regex literals of a rule condition. Like variables introduced by as expressions, these capturing variables also have scope of the containing rule.

Below is an example:

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

For request URI /us/en/read.html, this rule will trigger and produce the response body output:

country: us, lang: en

For multi-condition rules, each condition has its own set of $1, $2, and etc. For example:

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

For request GET /foo, we get:

result: foo

and for request GET /123, we get:

result: 123

In both cases, we can get a meaningful value in the $1 special variable.

Back to TOC

Macro Variables

Macro variables are variables used at compile-time in macros statements. They are declared by the macro keyword. For example:

macro @a = (1, 2, 3);
macro Num $count = 5;

Back to TOC

Function Names and Function Calls

The language extensively use functions for predicates and actions in rules. The user can also define their own functions if they want to.

Function names are represented by identifiers directly, no sigils involved.

Function calls are denoted by a function name followed by a pair of parentheses enclosing any arguments, as in:

say("hello, ", "world")

Function calls without any arguments can omit the parentheses. For example:

uri()

can be simplified to:

uri

Arguments can be passed either by positions or by names. The say() builtin action function, for example, accepts positional arguments as the response body message pieces, as the previous example demonstrates. Named arguments are passed with the argument name and a colon character as the prefix, as in:

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

Builtin functions may require certain arguments to be passed by names, and the others passed by positions. Please consult the documentation of specific builtin functions for the actual usage.

Function calls with at least one argument can be rewritten in a "method call" form with the first argument being the invocant. For example:

say("hello")

can be rewritten as:

"hello".say()

or even:

"hello".say

Function calls with more than one argument can also be rewritten in a similar way, for example:

say("hello", "world")

is semantically equivalent to:

"hello".say("world")

Back to TOC

Literal Strings

Literal strings can be enclosed by either single quote (') or double quote (") characters, as in:

"hello, world!"
'foo 1234'

Scalar and array variables can be interpolated into double-quoted string literals, as in:

"Hello, $name!"

So literal $ characters in a double-quoted string needs to be escaped by \, to avoid unwanted interpolations, as in:

"Hello, \$name!"

The following escaping sequences are supported in double-quoted strings:

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

Single-quoted string literals do not support variable interpolations. In addition, they only recognize the following escaping sequences:

\'
\\

Any other appearances of the \ character in a single-quoted string literal will be interpreted as a literal \.

Back to TOC

Numeric Constants

A numeric constant can be written as one of the following forms:

1527
3.14159
-32
-3.14
78e-3      (with a decimal exponent)
0xBEFF     (hexadecimal)
0157       (octal)

Back to TOC

Regex Literals

A regex literal is for specifying a Perl-compatible regular expression value. It is denoted by the keyword rx with a quoting structure. Below are some examples:

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

The user is free to use curly braces, slashes, parentheses, double quotes, or single quotes in the regex literals. They are all equivalent, except the requirement on what specific quoting characters need to be escaped inside the regex string. For example, in the rx(...) form, use of the slash character (/) inside the regex string does not require any escaping.

Use of whitespace characters in the regex value are not significant by default, except inside a character class construct (e.g., [a-z]). This is to encourage the user to format the regex string for better readability.

One or more options can be specified on the regex, for example:

rx:i/hello/

dictates a case-insensitive match of the pattern hello. Similarly:

rx:s/hello, world/

makes whitespace characters used in the pattern string become significant.

Multiple options can be specified at the same time by stacking them together, as in

rx:i:s/hello, world/

Back to TOC

Wildcard Literals

A wildcard literal is for specifying a string matching pattern using the UNIX-style wildcard syntax. It is denoted by the keyword wc with a quoting structure, for instance:

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

As with regex literals, wildcard literals can also take flexible quoting characters.

Three wildcard meta patterns are supported: * for matching any sub-string, ? for matching any one single character, and [...] for character classes.

One or more options can be specified on the wildcard, for example:

wc:i/hello/

dictates a case-insensitive match of the pattern hello.

Back to TOC

Quoted Words

Quoted words provide a convenient way to specify a list of string literal values without typing too many string enclosing quotes.

It is denoted by the keyword qw and a subsequent flexible quoting construct. For example:

qw/ foo bar baz /

is equivalent to:

"foo", "bar", "baz"

Like regex and wildcard literals, the user can choose from various quoting characters for the quoting construct used in quoted words, i.e., /, {, (, ", and '.

Back to TOC

Quantity Values

Quantity values with units are supported as a first-class citizen. A quantity literal is specified by a number and a unit enclosed by squared brackets. For example:

32 [kB/s]

is a quantity for "32 kilo-bytes per second".

The following time units are supported:

  • s, sec, or second

    second

  • ms

    millisecond

  • us

    microsecond

  • ns

    nanosecond

  • min

    minute (time)

  • h or hour

    hour

  • d or day

    day

  • month

    month

  • year

    year

The r or req unit is for number of requests.

The following data size units are supported:

  • B or Byte

    byte

  • b or bit

    bit

Data size units can take one of the following scale prefixes:

  • k

    x 1000

  • K or Ki

    x 1024

  • m

    x 1000 x 1000

  • M or Mi

    x 1024 x 1024

  • g

    x 1000 x 1000 x 1000

  • G or Gi

    x 1024 x 1024 x 1024

  • t

    x 1000 x 1000 x 1000 x 1000

  • T or Ti x 1024 x 1024 x 1024 x 1024

Compound units for data transfer rates can be formed by a data size unit, a time unit, and a connecting slash character. For example, kB/s and r/s.

Quantity values can be coerced into strings directly, for example, action:

say(32 [hour])

gives the following response output:

32 [hour]

One can use arbitrary arithmetic expressions before the unit part, for example:

(1.5 + 2) [kB/s]

yields a quantity equivalent to 3.5 [kB/s].

The builtin function convert-unit() can be used to convert a quantity value's unit to a new unit as long as the new compatible unit without changing the quantity's physical meaning. For example:

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

will result in the new quantity value, 3600 [sec], which is logically equivalent.

The to-num() builtin function can be used to extract the number part from a quantity value. For instance:

to-num(32 [hour])

will return the number 32.

Back to TOC

Booleans

Boolean values are presented by the values of the builtin function calls true() and false(), respectively. All the relational expressions evaluate to boolean values as well.

In edgelang, the following values are considered "conditional false":

  • number 0
  • string "0"
  • the value of false()
  • an empty string
  • an empty list or array
  • an empty hash table

All other values are considered "conditional true".

Function calls true() and false() are often abbreviated to just true and false.

Back to TOC

Netaddr

Netaddr constant support CIDR format, can be written as one of the following forms:

192.168.1.1
192.168.1.1/32  -- it's same as 192.168.1.1
192.168.1.0/24

NOTICE Only support IPv4 now.

Back to TOC

Aggregates

User can custom defined the aggregates (or statistics) rules.

Back to TOC

Number value

Mainly use it in two ways:

  1. Aggregates the number value, get the result like max, min or sum.
  2. Aggregates the total number of each number value, get the final result by hist-linear() or hist-log().

Back to TOC

Data filters and attributes
  • int: covert the original value to integer value, it will discard the fractional part directly.

  • resolution: counting is always done on values round up to the user-defined "resolution" on the edge node side.

    For example, :resolution(1) means the minimal value unit is 1 and values like 5.31 would be round up to 5. On the other hand, :resolution(0.1) means the minimal value unit is 0.1 and values like 5.31 would be round up to 5.3.

  • top: only collect the number of largest value items, ignore the smaller items.

  • label: just a label to help you view the metric data.

Back to TOC

Statistical aggregates

Computing for Statistical Aggregates. To extract data collected by statistical aggregates, use the syntax format :extractor. extractor can be any of the following extractors:

  • min: minimum value

  • max: maximum value

  • avg: average value

  • count: total number of value

  • sum: summary value

  • hist-linear(L, H): calculate the total number of each different number value.

  • interval: controls data reporting frequency, specifies how often to report data, the default value is 1 sec.

    • L : Low
    • H : High
  • hist-log: represents a base-2 logarithmic histogram.

Examples:

aggregate $req-latency-metrics :label('test 1'), :min, :max, :avg, :count, :sum,
                               :interval(1 [s]);

true =>
    $req-latency-metrics <<< req-latency;

aggregate $req-latency-ms-metrics :min, :max, :avg, :count,
                                  :resolution(1), :sum, :top(100);

true =>
    $req-latency-ms-metrics <<< req-latency * 1000;

# label: test 1
# name                  | value
# ----------------------|------
# min                   | 0.001
# max                   | 1.22
# avg                   | 0.002
# count                 | 1024
# sum                   | 1.5
# INT value
aggregate $resp-status-metrics :label('test 2'), :hist-linear(100, 599),
                               :resolution(1), :top(100);

true =>
    $resp-status-metrics <<< resp-status;

# label: test 2
# value                 | count
# ----------------------|------
# 200                   | 234
# 302                   | 11
# 404                   | 1024
# 500                   | 1
# 503                   | 7
# DOUBLE value
aggregate $req-latency-metrics :label('test 3'), :hist-log(),
                               :resolution(0.1), :top(100);

true =>
    $req-latency-metrics <<< req-latency();

# label: test 3
# value                 | count
# ----------------------|------
# 0.1                   | 1024
# 0.2                   | 1
# 0.4                   | 2
# 0.8                   | 0
# 1.6                   | 1

Back to TOC

Whatever

The special term * represents a whatever literal. Some builtin functions accept whatever literals as their arguments.

Back to TOC

Comments

A comment starts with the character #, and continues to the end of the current line. For example:

# this is a comment

Back to TOC

Operators

The following operators are supported, in the order of their precedence:

Precedence          Operators
0                   post-circumfix [], {}, <>
1                   **
2                   unary +/-/~, as
3                   * / % x
4                   + - ~
5                   << >>
6                   &
7                   | ^
8                   unary !, > < <= >= == !=
                    contains contains-word prefix suffix
                    !contains !contains-word !prefix !suffix
                    eq ne lt le gt ge
9                   ..
10                  ?:

The user may use parentheses, (), to explicitly change the relative precedence or associativity of the operators in a single expression.

Back to TOC

Arithmetic Operators

The language supports the following binary arithmetic operators:

**      power
*       multiplication
/       division
%       modulo
+       addition
-       subtraction

For example:

2 ** (3 * 2)        # evaluates to 64
(7 - 2) * 5         # evaluates to 25

The unary prefix operators + and - are also supported, as in:

+(32 + 1)           # evaluates to 33
-(3.15 * 2)         # evaluates to -6.3

Back to TOC

String Operators

The language supports the following binary string operators:

x       repeat a string for several times and concatenate them together
~       string concatenation

For instance:

"abc" x 3           # evaluates to "abcabcabc"
"hello" ~ "world"   # evaluates to "helloworld"

Back to TOC

Bit Operators

The following binary bit operators are supported:

<<          shift left
>>          shift right
&           bit AND
|           bit OR
^           bit XOR

The unary prefix operator ~ is for the bit NOT operation. Do not confuse it with the binary operator ~ for string concatenation.

Back to TOC

Relational Operators

Use of all relational operators lead to a boolean value for the current expression. Expressions using a relational operators are relational expressions.

The following binary operators compare the two operands numerically:

>           greater than
<           less than
<=          less than or equal to
>=          great than or equal to
==          equal to
!=          not equal to

The following binary operators are for comparing string values alphabetically:

gt          greater than
lt          less than
le          less than or equal to
ge          great than or equal to
eq          equal to
ne          not equal to

There are also 3 special string binary operators for pattern matching in a string value:

contains            holds when the right hand side operator is *contained* in
                    the left hand side operator

contains-word       holds when the right hand side operator is *contained* as
                    a word in the left hand side operator

prefix              holds when the right hand side operator is a *prefix* of
                    the left hand side operator

suffix              holds when the right hand operator is a *suffix* of the
                    left hand side operator

The unary prefix operator ! negates the (boolean) value of the operand.

When the right hand side of the string comparison operators is a pattern like a wildcard or a regex value, then matching anchors are assumed in the pattern. For example:

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

is equivalent to:

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

where the regex pattern \A only matches the beginning of the string while \z only matches the end. The contains operator, on the other hand, assumes no implicit matching anchors.

Similarly, the contains-word operator assumes the surrounding \b regex anchor on both sides of the user regex.

Back to TOC

Range Operator

The binary operator .. can be used to form a range expression, as in:

1 .. 5          # equivalent to 1, 2, 3, 4, 5
'a'..'d'        # equivalent to 'a', 'b', 'c', 'd'

The value of a range expression is a flattened list of all the individual values in that range.

Back to TOC

Ternary Operator

The ternary relational operator ?: can be used to conditionally choose between two user expressions according to a user condition.

For example:

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

this expression evaluates to the value of $a + 1 when the expression $a < 3 is true, or evaluates to $a otherwise.

Back to TOC

Subscript Operators

The post-circumfix operator [] can be used as subscript of an array. For example:

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

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

Negative indexes are used to access elements from the end of the array, for instance, -1 is for the last element, -2 is for the second last one, and etc.

Similarly, the post-circumfix operator {} is used to index a hash table, as in:

my %scores = (Tom: 78, Bob: 100, John: 91);

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

The post-circumfix operator <> is used to index a hash table via literal string keys, for example, %scores<John> is equivalent to %scores{'John'}.

Back to TOC

Rules

Edgelang is a rule-based language. Essentially every edgelang program consists of groups of rules.

Back to TOC

Basic Rule Layout

The edgelang rules come with two basic parts, a condition, and a consequence. The condition and the consequence are connected by =>, and the whole rule is terminated by a semicolon character. The basic form of rule is like this:

<condition> => <consequence>;

The condition part of the rule can take one or more relational expressions, like resp-status == 200. All the relational expressions are connected by the comma character (,), which make all the relational expressions AND'd together, that is, all the relational expressions must hold true for the whole condition to be true. The conditions should have no side effects and this property is enforced by the edgelang compiler. For this reason, the order of valuation of the relational expressions in the same condition do not change the result of the whole condition.

The consequence part usually contains one ore more actions. Each action can have side effects like changing some request aspects, performing a 302 redirect, or changing the route the current request will go in the backend. It is also possible to specify a full block of rules as a single action (see the Action Blocks section).

Below is a simple edgelang rule:

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

In the condition part, uri("/foo") is a relational expression. The uri() function taking some arguments is a predicate, which means it only returns a true or false value. uri("/foo") does the following: if the current request URI matches the /foo literal string precisely, then return true; otherwise return false. We have one action in the consequence, i.e., the redirect() function call. This action generates a 302 HTTP response initiating an external redirect to the /bar URI on the same host. It is worth noting that the uri() function takes a positional argument while the redirect() function takes 2 named arguments. The edgelang builtin functions can determine by themselves whether they accept either positional arguments or named arguments, or even both.

Edgelang is a free format language, so you can use whitespace characters freely. The indentation used before the consequence part in the example above is not significant but just for aesthetic considerations. It is totally valid to write the whole rule in a single line, for example:

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

When the uri() function takes no argument, it returns the current request URI as a string, for example:

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

The relational expression uri() eq "/foo" in this example is equivalent to the uri("/foo") predicate used previously. The eq part is a binary comparison operator that compares whether two string values on the two sides are exactly the same.

It is worth mentioning that edgelang function calls without any arguments can omit its parentheses, so uri() can be abbreviated to uri, as in:

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

Back to TOC

Multiple Relational Expressions

The user can also specify multiple relational expressions in a single condition, for instance:

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

Here we have one more relational expression in the condition, i.e., uri-arg("n") < 1, which matches when the URI argument, n, takes a value less than the number 1. We use a different action, exit(403), in this example, which sends a "403 Permission Denied" response to the client immediately when it is executed. Note the comma between the two relational expressions of the condition, it means AND, and the relational expressions on both sides of the comma must be true at the same time for the whole condition to be true.

The user can specify even more relational expressions in the same condition, as in:

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

We have a 3rd relational expression, that tests whether the value of the User-Agent request header contains the sub-string, Chrome.

Back to TOC

Multiple Conditions

Edgelang rules can actually take multiple parallel conditions, connected by the semicolon operator. These conditions are logically OR'd together for the current rule.

For example:

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

When either of these 2 conditions matches, the rule matches. When both of the conditions match, the rule also matches, of course.

Back to TOC

Multiple Actions

It is also possible to specify multiple actions in the same rule consequence. Consider the following example:

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

This example has 3 actions in the consequence. The first action calls the errlog() builtin function and generates an error log message to the server error log file with the error log level warn. The latter 2 actions call the say() functions to output response body data for the current request.

Back to TOC

Unconditional Rules

Some rules may choose to run their actions unconditionally. An edgelang rule, however, always does require a condition part. To achieve the effect of unconditional rule triggering, the user can use the always-true predicate true() as the sole relational expression in the condition, as in:

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

In this rule, the action say() always runs regardless.

Because edgelang function calls without any arguments can omit their parentheses, it is preferable to write true instead of true(), as in:

true =>
    say("hello world");

Back to TOC

Multiple Rules

Multiple rules specified in the same block are executed in series. The rule written first will be executed first.

Consider the following example:

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

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

For the request GET /foo?n=4, we will get a 200 HTTP response with the body data:

hello
world

The conditions of multiple rules may get optimized by the edgelang compiler, however, to be matched at the same time, and may be evaluated even before any rules are actually executed. This happens when the edgelang compiler finds it safe in doing so.

Back to TOC

Blocks

Blocks are usually formed by a pair of curly braces ({}), which also forming a new scope for variables. Each edgelang program has an implicit top-level block.

In the following example, we have two different $a variables since they each belongs to a different block (or scope):

my $a = 3;

{
    my $a = "hello";
}

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

Rules are also lexical to the containing block, just like variables. Blocks can be used to group closely related rules together, as a whole. In such a setting, some early-executed rules may use the special action done() to skip all the subsequent rules in the same block. The following example demonstrates this:

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

    true =>
        print("howdy");
}

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

For request GET /test, the response body would be hello, outside!. Note how the done action in the 1st rule skips the execution of the 2nd rule. On the other hand, request GET /foo", would yield the output howdy, outside!, since the 1st rule is not matched.

However, the done() action used in the middle of a rule consequence does not skip subsequent actions in the same consequence. It only affects subsequent rules in the same block.

Blocks can be nested to an arbitrary depth, as in:

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

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

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

Back to TOC

Action Blocks

Blocks can also be used as actions in the rule consequence. Such blocks are called action blocks. This can be used to specify nested rules. For example:

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

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

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

In this rule, when the condition uri-prefix("/foo/") is matched, the 3 rules inside the action block are then inspected in series. On the other hand, when the outermost condition does not match, then the execution flow will never bother looking at the inner rules at all. This is a very convenient way of factoring out common conditions of several rules. It also helps the compiler generate more efficient machine code.

Other kinds of actions can be mixed with such action blocks in the same rule consequence, for instance:

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

Back to TOC

As Expressions

The user can use the as expressions to alias values of expressions into custom variables in rule conditions. These variables can later be referenced in subsequent parts of the condition and/or the consequence part of the rule (i.e., being used in actions).

These variables' scope is limited to their containing rules.

For example:

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

Here we alias the value of the expression "/security01/" to our custom scalar variable $prefix, and then we reference this variable in our rm-uri-prefix() action without duplicating the constant string value, thus reducing the risk of introducing typos in the constant string values. If we make a typo in the variable name, for example, we would get a compiler error complaining about lack of variable declarations). So use of as expressions to make variable aliases, not only make the code shorter, but also safer.

We can also use "as expressions" to get values as arbitrary expressions. For instance:

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

In this rule, we alias the value of the dynamic expression, uri-arg("uid"), to a custom variable $uid, and then reference this value in the later relational expressions of the condition, as well as the action of the rule.

Back to TOC

Assignment Actions

The assignment operator = is used to specify an action that assigns a value to a variable or an expression that can be an lvalue. For example:

my $a;

true =>
    $a = 3;

Like all the other actions, an assignment expression has no value for itself. So it is not allowed to embed an assignment expression in other expressions. For example, the following example will yield a compile-time error:

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

This is because the assignment $a = 3 returns no value and it can only be used as a standalone action.

The assignment:

$a = $a + 3

can be simplified using the operator +=:

$a += 3

Similarly, *=, /=, %=, x=, ~= are provided for the binary operators *, /, %, x, and ~, respectively.

Furthermore, the postfix operator ++ can be used to simplify the += 1 case. For example:

$a++

is equivalent to $a += 1 or $a = $a + 1. Similarly, the postfix operator -- is provided as a shorthand for -= 1.

Like the standard = operator, all these assignment variations do not take any values themselves and can only be used as standalone actions.

Back to TOC

Running Phases

OpenResty® processes each client request in different running phases. There are also special phases without any client requests associated, like during server startup or background worker light threads. Each edgelang code chunks usually has a default running phase attached to it, though some edgelang rules' execution may span multiple running phases.

The default running phase for a edgelang code chunk can be explicitly specified by the phase statement, for example:

phase access;

uri-prefix("/some/bad") =>
    exit(403);

specifies a rule under the access running phase. The scope of a phase statement affects all the subsequent code until the next phase statement or the end of the current code block.

The following phases are currently supported, in the order of their execution:

  • ssl-cert

    When serving SSL certificates for the downstream SSL connections during the SSL handshake

  • rewrite

    For request rewrites and redirects

  • access

    For request access control

  • resp-header

    When response header is ready

  • resp-body

    The response body phase

  • log

    For collecting access log data

More running phases may be added in the future.

Back to TOC

INIT Blocks

INIT blocks are a special kind of code blocks for preprocessing the request. They always run before any other code in the same running phase even when they appear later. Multiple INIT blocks run in the same order as they appear in the same running phase code chunks.

INIT blocks in a later running phase do run after all the code of previous phases.

INIT blocks are disabled in the req-body and resp-body running phases.

Almost all of the predicate functions carry exactly the same values across all the running phases of a single request, even when some rule actions change the underlying request data. This convention greatly help automatic rule optimizations and also minimize the chance of different rule sets affecting each other unwittingly. The INIT blocks, however, always force all those predicate functions to re-compute their values immediately after each INIT block's execution, and all the consequences of the actions run in the INIT block will take effect on the predicate function values right away.

Consider the following example:

INIT {
    uri-prefix(rx{ / [a-z]{2} / }) =>
        # remove the first URI path component (or segment)
        rm-uri-seg(1);
}

uri("/blog.html") =>
    say("hello!");

The rule in the INIT block strips any 2-letter URI prefixes like /us/ and /cn/. If the client request is GET /us/blog.html, then the rule outside the INIT block can match successfully. On the other hand, if the first rule is outside the INIT {} block, the second rule will never match since the uri("/blog.html") predicate function still uses the original URI data, /us/blog.html.

Back to TOC

Junctions

A junction is a single value that is equivalent to multiple values. The builtin functions any, all, and none are used to construct junctions from lists of values or arrays. Junctions provide a very concise way to express relation constraints between lists of values. For example, for testing if any elements in array @foo is greater than 3, we can write:

any(@foo) > 3

Or if we want to test if all the elements are greater than 3:

all(@foo) > 3

The user can also specify multiple discrete values directly, for instance:

any(1, 3, 5) <= 1

It is also possible to put junctions on both sides of the relational operator:

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

To test if a value does not appear in a list of values, one can write:

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

Junctions can only be used in relational expressions.

Implicit junctions are automatically created via the any() function when multiple values are used on one side of the relational operator. For example, when an array value appears on one side of the relational operator:

@foo > 3

which is equivalent to:

any(@foo) > 3

Similarly, for function calls like uri-arg() which may return multiple values:

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

It is equivalent to:

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

Use of negative relational operators with junctions is potentially problematic if interpreted naively. Consider the following example:

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

This really means the following for an English speaker:

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

To avoid such surprises for English speakers, edgelang automatically does this transformation for the user when the relational operators ne or != are used and any() is used on the right hand side.

Junctions can only be used on the top level of a relational expression. Use of junctions as function call arguments, for example, are not allowed.

Nested junctions are not supported yet.

Back to TOC

Virtual Servers

A virtual server is a single domain name or wildcard domain name that represents a separate "host". It is not uncommon for a lot of virtual servers or domain names share the same OpenResty® server instance.

Each edgelang program is associated by a single virtual server and each virtual servers are usually separated and isolated. Virtual servers are usually not specified directly inside the edgelang source code, but rather, are specified externally when invoking the edgelang compiler. In the context of the OpenResty® Edge platform, a virtual server is represented by the Application concept there and such information will be automatically fed into the edgelang compiler when OpenResty® Edge invokes it.

When a wildcard domain name, like *.foo.com, is specified as the virtual server, the user can use the host() builtin predicate function to specify conditions on concrete sub-domains. For example:

# common rules for *.foo.com go here...

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

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

Back to TOC

User-Defined Actions

The user can define their own parametric actions by grouping some other actions together. The general syntax for defining custom actions is like below:

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

For example:

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

true =>
    say-hi("Tom");

An HTTP request will trigger an HTTP 200 response with the following body:

hi, Tom!

Multiple parameters can also be specified.

User-defined actions are a great way to introduce your own vocabulary to the actions that you can use in rule consequences.

Recursive actions can also be defined, as in:

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

true => count-down(5);

This will yield the response body output as follows:

5
4
3
2
1
0
done

The maximum depth of recursion is carefully limited by the compiler to avoid infinite recursions.

Back to TOC

User-Defined Functions

The user can define their own functions which can be used both in rule conditions and consequences. The general syntax for defining custom functions is as follows:

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

The value of <expression> after the = sign is the value of the whole function.

Consider the following example:

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);

This example defines its own function x-powered-by which takes no argument and is evaluated to the value of the expression resp-header("X-Powered-By").

User-defined functions can also take parameters. Consider the following example:

func bit-is-set($num, $pos) =
    $num & (1 << ($pos - 1)) ? true : false;

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

The bit-is-set function takes a number as the first argument and a position of the bit in that number to test. It returns true when the specified bit is set and returns false otherwise.

It is worth mentioning we already have a test-bit builtin predicate function that does exactly what the bit-is-set() user function does in this example.

Back to TOC

Modules

Edge modules are reusable files of edgelang source that can be shared between various different edgelang programs. Modules usually contain various definitions of user-defined actions and/or user-defined functions.

To load a module, the user edgelang program can use the use statement, as follows:

use Foo;

The edgelang compiler will search a file named Foo.edge in the module search paths. The user can specify the -I PATH options on the edgelang compiler command line to add custom paths to the default module search paths. For example:

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

The user can also specify edge modules to preload on the command line with the -M NAME option, as in:

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

Back to TOC

Calling External Code

It is supported to call foreign libraries written in the target language. For example, when the target language is Lua, then the user can call into arbitrary Lua modules from within their edgelang program if they have enough permissions.

Calling external code is often achieved by the foreign-call() builtin function. It takes the following named arguments:

  • module

    The name of the foreign module. In the case of Lua, it's the name of the Lua module. This argument is optional.

  • func

    The name of the function in that module or in the default namespace of the target language. This argument is required.

The positional arguments (if any) will be passed directly to the specified function in the specified module (if any).

Below is an example of calling the random function in the standard Lua module math:

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

The foreign-call() call in this example is equivalent to the following Lua expression:

math.random(1, 10)

To call external C library code, the user can first write a simple Lua wrapper module using LuaJIT's excellent FFI, and then call into this Lua module as usual.

In the context of OpenResty® Edge platform, only administrators have the permissions to call into foreign code.

Back to TOC

Macros

Macros provide a powerful template mechanism that can help reduce code duplication and generate many similar but still different rules.

For example, the following edgelang rules:

host("qa.foo.com") =>
    rewrite-uri-prefix("/", "/media-qa"),
    done;

host("dev.foo.com") =>
    rewrite-uri-prefix("/", "/media-dev"),
    done;

host("prod.foo.com") =>
    rewrite-uri-prefix("/", "/media-prod"),
    done;

can be simplified to the following:

for 'dev', 'qa', 'prod' -> $env {
    host("$env.foo.com") =>
        rewrite-uri-prefix("/", "/media-$env'),
        done;
}

Here we use the macro construct, for loop statement, to generate these 3 rules with a simple rule template by introducing the $env macro variable. This for loop executes at compile-time and expands the rule template to multiple concrete edgelang rules by substituting different values of $env in the rule template.

Apparently, in this example, use of macro loops greatly simplify the code and avoid code duplication. If the user wants to add more sub-domains in the future, they can just add the subdomain prefixes to the string value list of the for statement.

The macro layer also provides the if statement, for example, we can use if to handle special cases in a for loop:

for 'dev', 'qa', 'prod' -> $env {
    if $env eq 'prod' {
        host("$env.foo.com") =>
            rewrite-uri-prefix("/", "/media'),
            done;

    } else {
        host("$env.foo.com") =>
            rewrite-uri-prefix("/", "/media-$env'),
            done;
    }
}

Here we handle the prod case slightly differently.

Assignments outside of any rule contexts are considered macro-level assignments. For example:

for 'dev', 'qa', 'prod' -> $env {
    macro $host, $dir;
    $host = "$env.foo.com";
    $dir = "media-$env";
    host($host) =>
        rewrite-uri-prefix("/", "/$dir'),
        done;
}

In this example, assignments to $host and $dir are macro-level assignments. Thus the variables $host and $dir are both macro variables, just like the $env variable. Macro-level assignments also run at compile-time. Custom macro variables are declared by the macro keyword. The $env macro variable is implicitly declared by the macro for loop statement.

Macro variables can also be array and hash variables, e.g., @foo and %bar.

The while macro loop is similar to the for macro loop, for example:

macro $i = 0;
while $i < 10 {
    uri-prefix("/post/$i/") =>
        say("This is post $i!");

    $i++;
}

Back to TOC

Builtin Predicate Functions

The Edge language provides the following builtin predicate functions.

Back to TOC

cache-status

syntax: cache-status()

Returns the upstream cache status.

For example:

phase resp-header;

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

Back to TOC

client-addr

syntax: client-addr()

Returns the client address.

Back to TOC

client-region

syntax: client-region()

syntax: client-region(region1, region2, ...)

Returns true when the client address is from one of the regions specified in the arguments; returns false otherwise.

For example:

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

Back to TOC

client-continent

syntax: client-continent()

syntax: client-continent(continent1, continent2, ...)

Returns true when the client address is from one of the continents specified in the arguments; returns false otherwise.

All continent codes are here:

AF = Africa
AP = Asia/Pacific
EU = Europe
NA = North America
SA = South America
OC = Oceania
AN = Antarctica

For example:

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

When no arguments are specified, it will return the current continent name for the client:

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

Back to TOC

client-country

syntax: client-country()

syntax: client-country(country1, country2, ...)

Returns true when the client address is from one of the countries specified in the arguments; returns false otherwise.

you can get all two-letter country codes from wikipedia.

The following are some typical country codes:

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

For example:

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

When no arguments are specified, it will return the current country name for the client:

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

Back to TOC

client-port

syntax: client-port()

Returns the client port.

Back to TOC

client-province

syntax: client-province()

syntax: client-province(province1, province2, ...)

Returns true when the client address is from one of the provinces specified in the arguments; returns false otherwise.

For example:

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

When no arguments are specified, it will return the current province name for the client:

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

You can get all Chinese province codes form name-code-chart.md.

Back to TOC

client-city

syntax: client-city()

syntax: client-city(city1, city2, ...)

Returns true when the client address is from one of the cities specified in the arguments; returns false otherwise.

For example:

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

When no arguments are specified, it will return the current city name for the client:

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

Back to TOC

client-isp

syntax: client-isp()

syntax: client-isp(isp1, isp2, ...)

Returns true when the client ISP is from one of the ISPs specified in the arguments; returns false otherwise.

For example:

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

When no arguments are specified, it will return the current ISP name for the client:

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

You can get some typical Chinese ISP codes form name-code-chart.md.

Back to TOC

client-subnet

syntax: client-subnet()

subsystem: dns

Returns the client subnet in DNS queries, and returns nil when not found subnet in DNS queries.

It support netaddr constant, for example:

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

NOTICE Only parse IPv4 in DNS queries now.

Back to TOC

decode-base64

syntax: decode-base64(digest)

Decodes the input string argument as a base64 digest.

Back to TOC

encode-base64

syntax: encode-base64(str)

Encodes the input string argument to a base64 digest.

Back to TOC

decode-hex

syntax: decode-hex(str)

Decodes the input string argument as a hexadecimal digest.

Back to TOC

defined

syntax: defined(val)

Returns true when the argument value is defined; returns false otherwise.

Back to TOC

false

syntax: false()

Returns the boolean false value.

Back to TOC

host

syntax: host()

syntax: host(pattern...)

When no arguments are specified, returns the value of the host name specified by the request.

When arguments are specified, this function returns true when the request host name matches any of the patterns specified by the arguments using the eq operator. The argument pattern can be one of regexes, literal strings, or wildcards.

Below is an example:

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

This is equivalent to the following form:

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

The former style is recommended since it is simpler.

Back to TOC

http-time

syntax: http-time()

syntax: http-time(quantity-val)

Generates HTTP time formatted string for response header values like Last-Modified and Expires.

When no argument is specified, it'll use the current time as default value.

When argument is specified, only accepts quantity typed value with time unit.

Below are the examples:

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

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

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

Returns value is a string, like Tue, 12 Dec 2017 08:40:09 GMT.

Back to TOC

ip-continent

syntax: ip-continent(netaddr)

subsystem: dns

Returns the continent name for specified netaddr, which can be parsed from DNS queries.

Below is an example:

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

Back to TOC

ip-country

syntax: ip-country(netaddr)

subsystem: dns

Returns the country name for specified netaddr, which can be parsed from DNS queries.

Below is an example:

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

Back to TOC

ip-province

syntax: ip-province(netaddr)

subsystem: dns

Returns the province name for specified netaddr, which can be parsed from DNS queries.

Below is an example:

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

Back to TOC

ip-city

syntax: ip-city(netaddr)

subsystem: dns

Returns the city name for specified netaddr, which can be parsed from DNS queries.

Below is an example:

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

Back to TOC

ip-isp

syntax: ip-isp(netaddr)

subsystem: dns

Returns the isp name for specified netaddr, which can be parsed from DNS queries.

Below is an example:

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

Back to TOC

is-empty

syntax: is-empty(value)

Returns true when the argument value is empty(not defined, empty string or true value); returns false otherwise.

Back to TOC

looks-like-int

syntax: looks-like-int(value)

Returns true when the argument value looks like an integer, i.e., either a string value whose content looks like an integer or the value itself is an integer value or a number with a zero decimal part. Returns false otherwise

The following calls will all yield true:

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

Back to TOC

looks-like-num

syntax: looks-like-num(value)

Returns true when the argument value looks like a number, i.e., either a string value whose content looks like a number or the value itself is a number.

The following calls will all yield true:

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

Back to TOC

lower-case

syntax: lower-case(value)

Returns a string with all the characters in the string argument converted to lower-case letters.

Back to TOC

md5-hex

syntax: md5-hex(value)

Returns a hexadecimal representation of the MD5 digest of the argument value.

Back to TOC

now

syntax: now()

Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the epoch for the current time stamp from the OpenResty cached time (no syscall involved unlike Lua's date library).

Back to TOC

random-pick

syntax: random-pick(value...)

Returns a uniformed random pick of the argument values.

For example,

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

will return either "foo", "bar", or "baz" with equal probability.

Back to TOC

rand

syntax: rand()

Returns a random number in the range [0, 1].

Back to TOC

random-hit

syntax: random-hit(ratio)

Returns true randomly according to the probability specified by the ratio argument. The ratio value must be in the range [0, 1] where 0 means never while 1 means 100%, i.e., always. For example, when ratio is 0.2, this function returns true by a chance of 20% and false otherwise.

Back to TOC

referer

syntax: referer()

syntax: referer(pattern...)

When it is called without any arguments, it returns the value of the function call req-header("Referer").

When some arguments are specified, these arguments are treated as patterns. It returns true when the referer value matches any of the patterns using the eq operator.

The argument pattern can be one of regexes, literal strings, or wildcards.

For example:

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

This rule is equivalent to the following form feeding no arguments to the referer call:

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

The former style is recommended since it is simpler.

Back to TOC

referer-host

syntax: referer-host()

syntax: referer-host(pattern...)

Returns the host part in the Referer request header value.

When some arguments are specified, these arguments are treated as patterns. It returns true when the referer host value matches any of the patterns using the eq operator.

The argument patterns can be either regexes, literal strings, or wildcards.

For example:

referer-host("www.facebook.com", "m.facebook.com", "facebook.com") =>
    say("hit!");

This rule is equivalent to the following form feeding no arguments to the referer-host call:

referer-host eq any("www.facebook.com", "m.facebook.com", "facebook.com"
) =>
    say("hit!");

The former style is recommended since it is simpler. The former style can be further simplified to the following using the quoted-words syntax:

referer-host(qw/www.facebook.com m.facebook.com facebook.com/) =>
    say("hit!");

The optional opt-prefix named argument can be specified to indicate an optional prefix for any of the pattern arguments. For example:

referer-host(opt-prefix: "www.", "foo.com", "bar.org")

is equivalent to:

referer-host("www.foo.com", "foo.com", "www.bar.org", "bar.org")

Back to TOC

reg-domain

syntax: reg-domain()

syntax: reg-domain(pattern...)

When no arguments are given, it returns the registered domain name in the server host the client is requesting. For example, sub-domain names like www.openresty.org is not a registered domain, while openresty.org is.

When some arguments are specified, these arguments are treated as patterns. It returns true when the referer host value matches any of the patterns using the eq operator. The argument patterns can be either regexes, literal strings, or wildcards.

For example:

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

is equivalent to:

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

The former style is recommended since it is simpler. The former style can be further simplified to the following using the quoted-words syntax:

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

Back to TOC

req-charset

syntax: req-charset()

Returns the charset parameter value (if any) in the request header Content-Type.

Back to TOC

syntax: req-cookie(pattern...)

syntax: req-cookie(*)

Returns the values of the request cookies whose names match any of the pattern arguments using the eq operator.

In boolean context, it simply evaluates to true when there is any matching request cookie names, and evaluates to false otherwise.

The argument pattern can be one of regexes, literal strings, or wildcards.

Below is an example:

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!");

When the argument is a whatever value, i.e., *, it returns the values of all the cookies brought with the request. In boolean context, it simply evaluates to true when there is any request cookies.

The cookie names can also be patterns like regexes and wildcards. In such cases, cookie names which match any of these patterns will be chosen and their values will be returned.

Back to TOC

req-header

syntax: req-header(pattern...)

Returns the values of the request headers whose names match any of the pattern arguments using the eq operator.

In boolean context, it simply evaluates to true when there is any matching request header names, and evaluates to false otherwise.

The argument pattern can be one of regexes, literal strings, or wildcards.

Below is an example:

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

The header names can also be patterns like regexes and wildcards. In such cases, header names which match any of these patterns will be chosen and their values will be returned.

Back to TOC

req-id

syntax: req-id()

Returns the value of the request id for the current request. The request id contains information that can be used to uniquely identify a request within an OpenResty Edge 2 installation.

The request id is always a string of 24 characters.

Below is an example:

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

Back to TOC

req-latency

syntax: req-latency()

Returns the latency of the request, it's a quantity typed value.

For example 0.01 [s] means 0.01 second.

Back to TOC

first-x-forwarded-addr

syntax: first-x-forwarded-addr()

Returns the first address in the X-Forwarded-For request header.

Back to TOC

last-x-forwarded-addr

syntax: last-x-forwarded-addr()

Returns the last address in the X-Forwarded-For request header.

Back to TOC

req-method

syntax: req-method(pattern...)

When no arguments are specified, returns the request method string like GET, POST, and DELETE.

When arguments are specified, these arguments are treated as patterns matching against the current request method string. Returns true when any of the user pattern matches; returns false otherwise.

Back to TOC

req-tld

syntax: req-tld()

Returns the top-level domain name (like .org, .com, and .us) of the current server host the client requests.

Back to TOC

resp-header

syntax: resp-header(pattern...)

Returns the values of the response headers whose names match any of the pattern arguments using the eq operator.

In boolean context, it simply evaluates to true when there is any matching response header names, and evaluates to false otherwise.

The argument pattern can be one of regexes, literal strings, or wildcards.

For example:

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

The header names can also be patterns like regexes and wildcards. In such cases, header names which match any of these patterns will be chosen and their values will be returned.

Back to TOC

resp-header-param

syntax: resp-header-param(header-name, param-name)

Returns the value of the specified header parameter in the specified response header.

For instance:

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

It removes the s-maxage parameter from the Cache-Control response header when it exists.

Back to TOC

resp-mime-type

syntax: resp-mime-type()

syntax: reps-mime-type(pattern...)

When no arguments are specified, returns the MIME-type of the response, i.e., the value of the Content-Type response header, excluding any parameters like charset=utf-8.

When some arguments are specified, these arguments are treated as patterns. It returns true when the response MIME-type value matches any of the patterns using the eq operator.

The argument pattern can be one of regexes, literal strings, or wildcards.

For example:

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

This rule is equivalent to the following form feeding no arguments to the resp-mime-type call:

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

The former style is recommended since it is simpler.

Back to TOC

resp-status

syntax: resp-status()

syntax: resp-status(code...)

When no arguments are specified, it returns the status code of the current response.

When arguments are specified, these arguments are treated as code to be compared with the current response status code. Returns true when any of the specified code is matched; returns false otherwise.

For example:

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

Back to TOC

scheme

syntax: scheme()

Returns the protocol scheme of the current request, like http and https.

Back to TOC

server-addr

syntax: server-addr()

phase: rewrite access proxy resp-header resp-body log

Returns the address of the server which accepted current request.

NOTICE It cannot work under the phase ssl-cert, the other phases are fine.

Here is an example:

phase rewrite;

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

We may get one of the following answers, it depends on your server's listening address:

# IPv4
address: 127.0.0.1

# IPv6
address: ::1

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

Back to TOC

server-port

syntax: server-port()

Returns the port of the server which accepted current request.

Back to TOC

server-region

syntax: server-region(region...)

Returns true when the server is within any of the regions specified by the arguments. Returns false otherwise:

server-region("Asia", "US West", "Pacific") =>
    resolve-origin("dc3.foo.com");

Back to TOC

single

syntax: single(value)

Returns true if the value is a single primitive value (like a single number, string, and quantity value); returns false otherwise.

This function is usually used to ensure the singleness of certain values. For example, to ensure there is only one value for the URI argument uid, we can write:

single(uri-arg("uid") as $v) =>
    say("uid arg is single: $v");

To negate the condition:

!single(uri-arg("uid") as @v) =>
    say("uid arg is not single: @v");

Back to TOC

substr

syntax: substr(str, from[, len])

Returns the sub-string starting from the subscript from (0-based) with the length specified by the len argument.

Negative subscripts indicate positions from the end of the string. For example, -1 means the last character, -2 means the second last one, and etc.

When the len argument is omitted, it means all the characters until the end of the string.

Below are some examples:

my $s = "hello world";

true =>
    say(substr($s, 0, 5)),       #  output: hello
    say(substr($s, 6)),          #  output: world
    say(substr($s, -5, 4)),      #  output: worl
    say(substr($s, -5));         #  output: world

Back to TOC

system-hostname

syntax: host_name = system-hostname()

Returns the host name of system, and it is the same as the return value of command hostname.

For example:

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

Back to TOC

to-num

syntax: to-num(value)

Converts the argument value to a number. Strings will be converted to numbers according to the 10-base representation. Quantity values will get their unit part stripped off. Numbers will just get through.

Back to TOC

true

syntax: true()

Returns the boolean true value.

Back to TOC

ua-contains

syntax: ua-contains(pattern...)

This is just a shorthand for the expression user-agent contains any(pattern1, pattern2, ...).

Back to TOC

ua-is-mobile

syntax: ua-is-mobile()

Returns true when the client looks like a mobile device; returns false otherwise. This is achieved by checking the User-Agent request header sent by the client.

Back to TOC

upper-case

syntax: upper-case(value)

Returns a string with all the characters in the string argument converted to upper-case letters.

Back to TOC

upstream-addr

syntax: addr = upstream-addr()

Returns the upstream address in string format, like 192.168.0.1:8080.

For example:

phase resp-header;

true =>
    errlog(level: "warn", upstream-addr);

Back to TOC

uri

syntax: uri()

syntax: uri(pattern...)

When no arguments are specified, returns the URI of the request. Note that the URI string does not include any URI arguments.

When some arguments are specified, these arguments are treated as patterns. It returns true when the URI value matches any of the patterns using the eq operator.

The argument pattern can be one of regexes, literal strings, or wildcards.

For example:

# the condition is true for request URIs `/foo/`, `/bar/`, and `/bar/blah`,
# but the condition is false for `/blah/foo/`, `/blah/bar/`, and `/bar`:
uri("/foo/", wc"/bar/*") =>
    say("hit!");

This rule is equivalent to the following form feeding no arguments to the uri predicate:

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

The former style is recommended since it is simpler.

Back to TOC

uri-arg

syntax: uri-arg(pattern...)

syntax: uri-arg(*)

Returns the values of the URI arguments whose names match any of the pattern arguments using the eq operator.

The argument pattern can be one of regexes, literal strings, or wildcards.

Below is an example for using the value of the URI argument limitrate to limit the response body data sending data rate:

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

Here is another example to remove any URI arguments whose name match the specified regex pattern:

uri-arg(rx/(_[0-9]+)/) =>
    rm-uri-arg($1);

When the argument is a whatever value, i.e., *, it returns values of all the URI arguments in the request. In boolean context, it simply evaluates to true when there is any URI arguments.

Back to TOC

uri-basename

syntax: uri-basename()

syntax: uri-basename(pattern...)

Without any arguments, it returns the basename of the resource specified in the request URI. For example, for the URI /en/company/about-us.html, it returns about-us as the base name. And for /static/download/foo.tar.gz, it returns foo.

When some arguments are specified, these arguments are treated as patterns. It returns true when the URI value matches any of the patterns using the eq operator. For example:

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

Back to TOC

uri-contains

syntax: uri-contains(pattern...)

This is a shorthand for the expression uri contains any(pattern1, pattern2, ...).

Back to TOC

uri-ext

syntax: uri-ext()

syntax: uri-ext(pattern...)

Without arguments, this function returns the file extension of the resource specified by the request URI. For example, for request URI /en/company/about-us.html, this function returns the value .html. And for /static/download/foo.tar.gz, the return value will be .tar.gz.

When arguments are specified, uri-ext(pattern1, pattern2, ...) is equivalent to uri-ext eq any(pattern1, pattern2, ...).

For example:

uri-ext(".html", ".htm") =>
    say("found an html page!");

is equivalent to:

uri-ext eq any(".html", ".htm") =>
    say("found an html page!");

Back to TOC

uri-prefix

syntax: uri-prefix(pattern...)

In boolean context, this is a shorthand for the expression uri prefix any(pattern1, pattern2, ...).

In string context (like in a as expression), it returns the sub-string that actually matches the first pattern that can be matched.

Back to TOC

uri-seg

syntax: uri-seg(index...)

syntax: uri-seg(*)

This function treats the URI path string as multiple segments separated by slashes (/) and returns the segments of the specified indexes. The segment indexes are form 1, and increments from left to right of the URI path.

For example, for request URI /foo/bar/baz, uri-seg(1) returns foo, uri-seg(2) returns bar, and uri-seg(3) returns baz. Multiple indexes can be specified at the same time as well, as in uri-seg(2, 5).

When the whatever value, *, is specified as the sole argument, this function returns all the URI path segment values.

Back to TOC

uri-suffix

syntax: uri-suffix(pattern...)

In boolean contexts, this is a shorthand for the expression uri suffix any(pattern1, pattern2, ...).

In string context (like in a as expression), it returns the sub-string that actually matches the first pattern that can be matched.

Back to TOC

user-agent

syntax: user-agent()

syntax: user-agent(pattern...)

Without any arguments, this function is just a shorthand for req-header("User-Agent").

With some arguments, the call is equivalent to user-agent eq any(pattern1, pattern2, ...), i.e., for checking whether the user agent string matches any of the user patterns with the operator eq.

Back to TOC

uuid-v4

syntax: uuid-v4()

Generates a UUID version 4 string value.

Back to TOC

userid

syntax: userid()

Generates a user id string value.

Back to TOC

Builtin Action Functions

add-cache-key-prefix

syntax: add-cache-key-prefix(prefix)

Adds the value of the argument prefix to the current cache key for the proxy cache. The proxy cache is for caching HTTP responses.

Back to TOC

add-req-header

syntax: add-req-header(name, value)

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

syntax: add-req-header(%name-value-pairs)

Adds new request headers, without overriding any existing request headers of the same names.

For example:

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

If you want to override any existing request headers, please use the set-req-header builtin action instead.

This action cannot affect the values of those predicate functions fetching request header information unless this action is executed in an INIT {} block and those predicate functions fetching request header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

add-resp-header

syntax: add-resp-header(header, value)

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

syntax: add-resp-header(%name-value-pairs)

phase: rewrite access content resp-header

Adds new response headers to the current request. Existing headers with the same name are not affected. If you want to override existing same-name headers, please use set-resp-header instead.

Below is an example:

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

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

add-uri-arg

syntax: add-uri-arg(name, value)

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

syntax: add-uri-arg(%name-value-pairs)

Adds new URI arguments to the current request. Existing URI arguments with the same name are not affected. If you want to override existing same-name URI arguments, please use set-uri-arg instead.

Below is an example:

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

This action cannot affect the values of those predicate functions fetching URI argument information unless this action is executed in an INIT {} block and those predicate functions fetching URI argument information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

add-uri-prefix

syntax: add-uri-prefix(prefix)

Adds a new prefix string to the current request URI.

Note that the prefix value does not need to end with a slash (/) because the existing URI string must already starts with a slash anyway.

Consider the following example:

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

For request GET /install.html, this rule will make the URI become /en/us/install.html.

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

block-req

syntax: block-req(key: KEY, target-rate: RATE, reject-rate: RATE, block-threshold: COUNT, observe-interval: COUNT, block-time: time)

Limits the request rate around the specified user key.

The named argument key is optional. When omitted, it is equivalent to a constant key.

The named argument target-rate is the maximum rate we want to shape into.

When the incoming rate exceeds reject-rate, the request handler will immediately reject the current request with a 503 error page (for HTTP/HTTPS applications) or drop the packet immediately (for DNS applications).

When the incoming rate is between the target-rate and the reject-rate, this action will wait an appropriate amount of time to match the target-rate for the current request.

The value of reject-rate must be no smaller than target-rate.

Both of the rate values must take a unit like [r/s] and [r/min].

observe-interval is used to set the size of time window for each observation interval in seconds; and block-threshold is used to set the number of consecutive observation intervals.

When the incoming rate exceeds reject-rate in each successive observation interval, the request handler will immediately block request with a 503 error page in block-time seconds.

Below is an example:

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);

The user can initiate multiple block-req calls for different keys and rates in a single request handler.

Back to TOC

foreign-call

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

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

syntax: foreign-call(func: <func>)

Initiates a call into external functions in the target language (like Lua).

The optional named argument module specifies the foreign module name. If omitted, defaults to the standard namespace used by the foreign language.

The func named argument specified the function name of the foreign call. This argument is required.

Any positional arguments will be passed into the foreign function call as arguments.

See Calling External Code for more details.

Back to TOC

errlog

syntax: errlog(level: LEVEL, msg...)

syntax: errlog(msg...)

Produces an error log message with the specified log level via the named argument level. When level is omitted, defaults to the error log level.

The message part can be multiple string arguments. This function will concatenate them automatically.

Some examples:

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

Back to TOC

exit

syntax: exit(code)

Exits the current request's processing with the status code code. If no response has sent yet upon this call, this call will also generate a default error page for the specified status code if it is recognized.

To shutdown the connection immediately, use the special exit code 444.

Back to TOC

expires

syntax: expires(time)

syntax: expires(time, force: true)

Adds and modifies the response headers Expires and Cache-Control for the specified expiration time.

By default, this only applies to responses with the status code 200, 201, 204, 206, 301, 302, 303, 304, 307, or 308. But user can enforce it for any status code by specifying the named argument, force: true.

The time positional argument must be a quantity value taking a time unit, like [sec] (for seconds), [min] (for minutes), [hour] (for hours), and [day] (for days).

Below is an example:

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

This action does not affect the proxy cache expiration time. See cache-expires also.

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

limit-req-concurrency

syntax: limit-req-concurrency(key: KEY, target-n: COUNT, reject-n: COUNT)

Limits the incoming request's concurrency level at the user-supplied key.

The actual concurrency level after running this action will be guaranteed to be no more than the target-n named argument value. When the incoming concurrency level is between target-n and reject-n, the current request will be delayed by an appropriate amount of time to satisfy the target concurrency level.

When the incoming request concurrency level is exceeding the reject-n value, then the current request will be immediately rejected with a 503 error page (for HTTP/HTTPS applications) or drop the packet (for DNS applications).

Below is an example:

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

Back to TOC

limit-req-data-rate

syntax: limit-req-data-rate(rate)

syntax: limit-req-data-rate(rate, after: size)

Limits the data rate when receiving request (body) data. The positional argument rate specifies the rate for the maximum receiving speed. It must be a quantity value taking a rate unit, like [kB/s].

The optional named argument, after, takes a size argument with a size unit like kB and mB.

Below is an example:

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

Note that the lower-case k prefix means a scale of 1000 while the upper-case K prefix means 1024. Similarly, the lower-case b unit means bit, while upper-case B means byte, i.e., octet.

Back to TOC

limit-req-rate

syntax: limit-req-rate(key: KEY, target-rate: RATE, reject-rate: RATE)

Limits the request rate around the specified user key.

The named argument key is optional. When omitted, it is equivalent to a constant key.

The named argument target-rate is the maximum rate we want to shape into.

When the incoming rate exceeds reject-rate, the request handler will immediately reject the current request with a 503 error page (for HTTP/HTTPS applications) or drop the packet immediately (for DNS applications).

When the incoming rate is between the target-rate and the reject-rate, this action will wait an appropriate amount of time to match the target-rate for the current request.

The value of reject-rate must be no smaller than target-rate.

Both of the rate values must take a unit like [r/s] and [r/min].

Below is an example:

true =>
    limit-req-rate(key: client-addr, target-rate: 10 [r/s], reject-rate: 20 [r/s]);

The user can initiate multiple limit-req-rate calls for different keys and rates in a single request handler.

Back to TOC

limit-resp-data-rate

syntax: limit-resp-data-rate(rate)

syntax: limit-resp-data-rate(rate, after: size)

Limits the data rate when sending response (body) data. The positional argument rate specifies the rate for the maximum sending speed. It must be a quantity value taking a rate unit, like [kB/s].

The optional named argument, after, takes a size argument with a size unit like kB and mB.

Below is an example:

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

Note that the lower-case k prefix means a scale of 1000 while the upper-case K prefix means 1024. Similarly, the lower-case b unit means bit, while upper-case B means byte, i.e., octet.

Back to TOC

print

syntax: print(msg...)

Generates custom response body data pieces. When the response header is not sent yet, the response will be automatically sent before sending out body data, for obvious reasons.

Unlike the say action, this action does not append a newline character to the user messages.

For example:

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

Back to TOC

redirect

syntax: redirect(uri: URI)

syntax: redirect(host: HOST, uri: URI, args: ARGS)

syntax: redirect(scheme: SCHEME, host: HOST, port: PORT, uri: URI, code: CODE)

phase: rewrite access

Sends an HTTP redirect response. It takes the following named arguments:

  • uri

    The URI string, excluding any querystring suffixes or host/scheme prefixes.

  • args

    The URI querystring or a hash table with the argument key-value pairs.Defaults to none.

  • host

    The host name to be redirected to. This is optional. Defaults to the current server.

  • port

    The port to be redirected to. This is optional. Defaults is 80 for http and 443 for https.

  • scheme

    The protocol scheme, like http and https. Defaults to the current request's protocol scheme.

  • code

    The status code to be used. It should be either 301, 302, 303, or 307. Defaults to 302.

For example:

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

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

Back to TOC

resolve-upstream

syntax: resolve-upstream(domain)

Resolves the user-supplied domain name and uses the resulting IP addresses to form a new upstream cluster for the current OpenResty Edge proxy.

Back to TOC

rewrite-resp-redirect-host

syntax: rewrite-resp-redirect-host(pattern..., new-host)

For HTTP redirect responses (301, 302, 303, and 307), when the host name in their Location response header matches any of the patterns specified by the all arguments but the last one, then this action replaces the matched host name with the user-supplied one specified in the last argument.

For example:

host("foo.com") =>
    rewrite-resp-redirect-host(wc"*.bar.com", "a.foo.com", host);

Here we substitute the current host name (being foo.com) for any redirected responses' target host names matching any of the patterns wc"*.bar.com" and "a.foo.com".

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

syntax: rewrite-resp-cookie-host(pattern..., new-host)

For HTTP responses with Set-Cookie headers, when any Domain cookie parameter values match any of the patterns specified by the all arguments but the last one, then this action replaces all the matched domain values with the user-supplied one specified in the last argument.

For example:

host("foo.com") =>
    rewrite-resp-cookie-host(wc"*.bar.com", "a.foo.com", host);

Here we substitute the current host name (being foo.com) for any cookie Domain parameter values matching any of the patterns wc"*.bar.com" and "a.foo.com".

This action cannot affect the values of those predicate functions fetching response header/cookie information unless this action is executed in an INIT {} block and those predicate functions fetching response header/cookie information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rewrite-suffix

syntax: rewrite-suffix(subj, pattern, replacement)

Replaces the string suffix in the string argument subj matching the pattern pattern, with the specified replacement argument value. Does nothing if the pattern does not match any suffix of the subject string.

For example:

true =>
    say(rewrite-suffix("a.b.foo.com", "b.foo.com", "blah.com"))

This rule produces the response body data output:

a.blah.com

The pattern argument can also be a regex value or a wildcard value.

Back to TOC

rewrite-uri-prefix

syntax: rewrite-uri-prefix(pattern, replacement)

Replaces the string prefix in the current request URI matching the pattern pattern, with the specified replacement argument value. Does nothing if the pattern does not match any prefixes of the current URI.

The pattern argument can also be a regex value or a wildcard value.

For example:

uri-prefix("/wap/") =>
    rewrite-uri-prefix($prefix, "/some-new-wap/");

This rule replaces the URI prefix /wap/ with /some-new-wap/. It can be a bit more efficient to rewrite this rule as follows using rm-uri-seg and add-uri-prefix though:

uri-prefix("/wap/") =>
    rm-uri-seg(1),
    add-uri-prefix("/some-new-wap");

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rewrite-uri-seg

syntax: rewrite-uri-seg(index, replacement)

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

This function treats the URI path string as multiple segments separated by slashes (/) and replaces the segments of the specified indexes, with the specified replacement argument values. The segment indexes are form 1, and increments from left to right of the URI path.

For example, for request URI /foo/bar/baz, rewrite-uri-seg(1, "qux") yields a new URI /qux/bar/baz, and rewrite-uri-seg(2, "qux") yields /foo/qux/baz. Multiple indexes can be specified at the same time as well, for example rewrite-uri-seg(2, "qux", 3, "foo") yields /foo/qux/foo.

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

syntax: rm-req-cookie(pattern...)

syntax: rm-req-cookie(*)

Removes request cookies whose names matching any of the user patterns specified by the positional arguments, using the eq relational operator.

The patterns can be either a literal string, a regex, a wildcard, or a whatever value.

Here is an example:

true =>
    rm-req-cookie("_uid", rx/_track\d+/, wc/seed*/);

This rule unconditionally removes any request cookies with the name _uid, any names consisting of _track and several digits, or any names started with seed.

In case of a whatever value, this action removes all the cookies brought by the current request. For example:

uri-prefix("/public/") =>
    rm-req-cookie(*);

This action cannot affect the values of those predicate functions fetching request header/cookie information unless this action is executed in an INIT {} block and those predicate functions fetching request header/cookie information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-req-header

syntax: rm-req-header(pattern...)

Removes request headers whose names matching any of the user patterns specified by the positional arguments, using the eq relational operator.

The patterns can be either a literal string, a regex, or a wildcard.

Here is an example:

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

This rule unconditionally removes any request headers with the name Authorization, any names started with X-, or any names started with Internal-.

This action cannot affect the values of those predicate functions fetching request header information unless this action is executed in an INIT {} block and those predicate functions fetching request header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

syntax: rm-resp-cookie(pattern...)

Removes response cookies whose names matching any of the user patterns specified by the positional arguments, using the eq relational operator.

The patterns can be either a literal string, a regex, a wildcard, or a whatever value.

Here is an example:

true =>
    rm-resp-cookie("_uid", rx/_track\d+/, wc/seed*/);

This rule unconditionally removes any response cookies with the name _uid, any names consisting of _track and several digits, or any names started with seed.

In case of a whatever value, this action removes all the cookies brought by the current response. For example:

uri-prefix("/public/") =>
    rm-resp-cookie(*);

The rm-resp-cookie(*) action is equivalent to rm-resp-header("Set-Cookie" ).

This action cannot affect the values of those predicate functions fetching response header/cookie information unless this action is executed in an INIT {} block and those predicate functions fetching response header/cookie information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-resp-header

syntax: rm-resp-header(pattern...)

Removes response headers whose names matching any of the user patterns specified by the positional arguments, using the eq relational operator.

The patterns can be either a literal string, a regex, or a wildcard.

Here is an example:

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

This rule unconditionally removes any response headers with the name Set-Cookie, any names started with X-, or any names started with Internal-.

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-resp-header-param

syntax: rm-resp-header-param(header-name, param-name)

Removes the header parameter of the specified parameter name from the specified response header.

For example:

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

This removes any s-maxage=xxx parameters from the current response's Cache-Control header value.

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-uri-arg

syntax: rm-uri-arg(name...)

syntax: rm-uri-arg(*)

Removes the URI arguments by their names.

When a Whatever value (*) is specified, it removes all the URI arguments.

This action cannot affect the values of those predicate functions fetching URI argument information unless this action is executed in an INIT {} block and those predicate functions fetching URI argument information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-uri-prefix

syntax: rm-uri-prefix(pattern...)

Removes the URI prefix matching the first one in the user-supplied patterns specified by arguments.

For example:

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

For request URI /foo/hello, this rule will turn the URI into /hello. And for request /foo1234/, this rule will yield the new URI /.

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

rm-uri-seg

syntax: rm-uri-seg(index...)

This function treats the URI path string as multiple segments separated by slashes (/) and removes the segments of the specified indexes. The segment indexes are form 1, and increments from left to right of the URI path.

For example, for request URI /foo/bar/baz, rm-uri-seg(1) yields a new URI /bar/baz, rm-uri-seg(2) yields /foo/baz, and rm-uri-seg(3) returns /foo/bar/. Multiple indexes can be specified at the same time as well, as in rm-uri-seg(2, 5).

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

say

syntax: say(msg...)

Generates custom response body data pieces with a trailing newline character automatically appended. When the response header is sent yet, the response will be automatically sent before sending out body data, for obvious reasons.

If you do not want a trailing newline character to be appended, please use the print action instead.

For example:

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

Back to TOC

scan-req-body

syntax: scan-req-body { rule... }

TODO

Back to TOC

scan-resp-body

syntax: scan-resp-body { rule... }

TODO

Back to TOC

enable-proxy-cache

syntax: enable-proxy-cache(key: KEY)

Enable proxy cache with the user-supplied cache key for the current request, proxy cache is disabled by default.

Back to TOC

set-proxy-cache-default-ttl

syntax: set-proxy-cache-default-ttl(time)

Sets the default expiration time for the proxy cache.

The time positional argument must be a quantity value taking a time unit, like [sec] (for seconds), [min] (for minutes), [hour] (for hours), and [day] (for days).

Below is an example:

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

This action does not affect the current response's Expires or Cache-Control response headers. If you want both, then you should write:

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

It is possible to specify different expiration times for the cache and for the client.

See also expires.

Back to TOC

enforce-proxy-cache

syntax: enforce-proxy-cache(time)

Similar to set-proxy-cache-default-ttl, but will enforce caching the current response regardless of the response header settings (i.e., ignoring Cache-Control, Set-Cookie, Expires and etc).

Back to TOC

enable-gateway-gzip

syntax: enable-gateway-gzip()

syntax: enable-gateway-gzip(enabled)

phase: resp-header

Dynamically turns on/off gzip compression for the current request.

When no argument is specified, means to enable gateway gzip.

When a bool argument is specified, means to enable or disable gateway gzip.

Below is an example:

phase resp-header;

uri-prefix("/css/") =>
    enable-gateway-gzip;

Back to TOC

set-mime-type

syntax: set-mime-type(type)

Sets the MIME type for the response body data. For example, text/plain, text/html, and etc.

For example:

uri-suffix(".mp4") =>
    set-mime-type("video/mp4");

The user should not specify any charset parameter in this value. See set-resp-charset instead.

This action affects the value of the current response's Content-Type response header sent to the client.

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-req-header

syntax: set-req-header(name, value)

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

syntax: set-req-header(%name-value-pairs)

Sets request headers, overriding any existing request headers of the same names.

For example:

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

If you want to add new request headers without overriding existing ones, please use the add-req-header builtin action instead.

This action cannot affect the values of those predicate functions fetching request header information unless this action is executed in an INIT {} block and those predicate functions fetching request header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-proxy-host

syntax: set-proxy-host(host)

The default proxy host is the current request host.

Back to TOC

set-proxy-header

syntax: set-proxy-header(header, value)

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

Sets proxy headers to the proxied server. Existing headers with the same name will be removed.

You can use this function to turn a connection between a client and server from HTTP/1.1 into WebSocket, below is an example:

true =>
    set-proxy-header("Upgrade", "WebSocket",
                     "Connection", "Upgrade");

List of headers which can not be set using this API:

  • Content-Length
  • Transfer-Encoding
  • If-Modified-Since
  • If-None-Match

Back to TOC

append-proxy-header-value

syntax: append-proxy-header-value(header, value)

syntax: append-proxy-header-value(header1, value1, header2, value2, ...)

Appends proxy header value to the proxied server. When the header field is not empty, the header field will the existing value with the value appended to it, separated by a comma. Otherwise the header filed will be the value.

Like:

true =>
    appear-proxy-header-value("X-Forwarded-For", client-addr);
    # will pass `192.168.1.1,10.10.1.1` as `X-Forwarded-For` to the proxied server,
    # when the original `X-Forwarded-For` is `192.168.1.1` and client-addr is `10.10.1.1`.

List of headers which can not used in this API:

  • Host
  • Connection
  • Upgrade
  • Content-Length
  • Transfer-Encoding
  • If-Modified-Since
  • If-None-Match

Back to TOC

set-req-host

syntax: set-req-host(host)

Sets the request Host header to the value of the host positional argument. This is just a shorthand for set-req-header("Host", host).

This will not make the current request re-match new virtual machines. But rather, it usually just affects the Host request forwarded to the upstream servers.

For example:

host("images.foo.com") =>
    set-req-host("images.foo.com.s3.amazonaws.com");

This action cannot affect the values of those predicate functions fetching request header information unless this action is executed in an INIT {} block and those predicate functions fetching request header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

syntax: set-resp-cookie(name, value, domain: DOMAIN, path: PATH, http-only: BOOL, expires: TIME, max-age: TIME)

Sets a new response cookie, overriding any existing cookies of the same name.

Back to TOC

set-resp-header

syntax: set-resp-header(header, value)

syntax: set-resp-header(header1, value1, header2, value2, ...)

syntax: set-resp-header(%name-value-pairs)

phase: rewrite access content resp-header

Sets response headers to the current response. Existing headers with the same name will be removed. If you do not want to override existing same-name headers, please use add-resp-header instead.

Below is an example:

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

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-resp-status

syntax: set-resp-status(code)

Sets the current response's status code to the value of the code argument.

Do not call this action after the response header is already sent out (like triggered by a print or say call).

This action cannot affect the values of those predicate functions fetching response header information unless this action is executed in an INIT {} block and those predicate functions fetching response header information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-uri

syntax: set-uri(uri)

Sets the current request URI to a new value. The URI value should not contain any query string or host/port parts.

This action is mainly useful for changing the request URI to be forwarded to the upstream via the proxy.

This action cannot affect the values of those predicate functions fetching request URI information unless this action is executed in an INIT {} block and those predicate functions fetching URI information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-uri-arg

syntax: set-uri-arg(name, value)

syntax: set-uri-arg(name1, value1, name2, value2, ...)

syntax: set-uri-arg(%name-value-pairs)

Sets URI arguments in the current request. Existing URI arguments with the same name will be removed. If you do not want to override existing same-name URI arguments, please use add-uri-arg instead.

Below is an example:

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

This action cannot affect the values of those predicate functions fetching URI argument information unless this action is executed in an INIT {} block and those predicate functions fetching URI argument information run after that INIT {} block. See INIT blocks for more details.

Back to TOC

set-max-body-size

syntax: set-max-body-size(size)

phase: rewrite access

Sets the size of maximum POST body this request will accept. For requests with valid Content-Length header, this method will check immediately and terminate request processing with 413 Request Entity Too Large if it is greater than size. For chunked encoding requests and HTTP/2 requests, this will check as the buffers are being processed.

If it is set to 0 it will disable this check.

Takes a size argument with a size unit like kB and mB.

true =>
    set-max-body-size( 1 [kB]);  # 1000 bytes

Back to TOC

sleep

syntax: sleep(time)

Sleeps for the specified seconds without blocking. One can specify time resolution up to 0.001 seconds (i.e., one millisecond).

Below is an example:

true =>
    sleep(0.5);

Back to TOC

subst-resp-body-matched

TODO

Back to TOC

test-bit

syntax: test-bit(num, bit-pos)

Tests if a particular bit is set in the num argument. The bit position starts from 1. The 1st bit is the least significant bit. Returns true when that bit is set (i.e., being 1); returns false otherwise.

For example:

true =>
    say(test-bit(2, 1)),   # false
    say(test-bit(2, 2)),   # true
    say(test-bit(2, 3));   # false

This example can be rewritten using the method syntax as well:

true =>
    test-bit(2, 1).say,   # false
    test-bit(2, 2).say,   # true
    test-bit(2, 3).say;   # false

Back to TOC

waf-mark-risk

syntax: waf-mark-risk(level: LEVEL, msg: MESSAGE)

Mark the request as evil with the optional level and msg.

The basic workflow of WAF is as follows:

  1. mark a request as evil with risk level,
  2. we will sum up the risk based on the remote address,
  3. we will do the block action (configured in the admin site) when the sum risk reaches the threshold score (configured in the admin site).

The level argument can be:

  1. definite means the current request is definite dangerous, will do the block action anyway (no matter what the threshold level is).
  2. high means high risk.
  3. middle means middle risk.
  4. low means low risk (default).
  5. debug means debug rule, will not do the block action anyway.

The msg argument is used to describe the WAF rule. It will be shown in the admin site when a request matches this rule.

For example:

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');

Back to TOC

waf-config

syntax: waf-config(action: NAME, url: URL)

Configure the waf block action, action is required, it can be:

  1. log, means log only,
  2. reject, means 403 reject,
  3. redirect, means 302 redirect to the specified url.

The url is required when the action is redirect.

like:

uri-prefix("/api/")
=>
    waf-config(action: "redirect", url: "http://foo.com/bar");

uri-prefix("/static/")
=>
    waf-config(action: "reject");

Back to TOC

Case Study

TODO

Back to TOC

Author

Yichun Zhang <yichun@openresty.com>, OpenResty Inc.

Back to TOC

Copyright (C) 2017 by OpenResty Inc. All rights reserved.

This document is proprietary and contains confidential information. Redistribution of this document without written permission from the copyright holders is prohibited at all times.

Back to TOC