forked from openresty/nginx-tutorials
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path01-NginxVariables07.tut
196 lines (157 loc) · 8.22 KB
/
01-NginxVariables07.tut
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
= Nginx Variables (07) =
== Special Value "Invalid" and "Not Found" ==
We have mentioned that the values of Nginx variables can only be of one single
type, that is, the string type, but variables could also have no meaningful
values
at all. Variables without any meaningful values still take a special value
though.
There are two possible special values: "invalid" and "not found".
For example, when a user variable C<$foo> is created but not assigned yet,
C<$foo> takes the special value of "invalid". And when the current URL
query string does not have the C<XXX> argument at all, the built-in variable
L<$arg_XXX> takes the special value of "not found".
Both "invalid" and "not found" are special values, completely different from an
empty string value (C<"">). This is very similar to those distinct special
values in some dynamic programing languages, like C<undef> in Perl, C<nil> in
Lua, and C<null>
in JavaScript.
We have seen earlier that an uninitialized variable is evaluated to an
empty
string when used in an interpolated string, its real value, however, is not an
empty
string at all. It is the "get handler" registered by the L<ngx_rewrite/set>
directive that automatically converts the "invalid" special value into an empty
string. To verify this, let's return to the example we have discussed before:
:nginx
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
When accessing C</foo>, the user variable C<$foo> is uninitialized when used in
the interpolated string for the L<ngx_echo/echo> directive. The output shows
that the variable is evaluated to an empty string:
:bash
$ curl 'http://localhost:8080/foo'
foo = []
From the output, the uninitialized C<$foo> variable behaves just like
taking an empty string value. But careful readers should have already noticed
that, for the request above, there is a warning in the Nginx error log file
(which is F<logs/error.log> by default):
[warn] 5765#0: *1 using uninitialized "foo" variable, ...
Who on earth generates this warning? The answer is the "get handler" of C<$foo>,
registered by the L<ngx_rewrite/set> directive. When C<$foo> is read, Nginx
first checks the value in its container but sees the "invalid" special value,
then Nginx decides to continue running C<$foo>'s "get handler", which first
prints the warning (as shown above) and then returns an empty string value,
which thereafter gets cached in C<$foo>'s value container.
Careful readers should have identified that this process for user variables is
exactly the same as the mechanism we discussed earlier for built-in variables
involving "get handlers" and result caching in value containers. Yes, it is the
same mechanism in action. It is also worth noting that only the "invalid"
special value will trigger the "get handler" invocation in the Nginx core while
"not found" will not.
The warning message above usually indicates a typo in the variable name or
misuse of uninitialized variables, not necessarily in the context of an
interpolated string. Because of the existence of value caching in the variable
container, this warning will not get printed multiple times in the lifetime of
the current request. Also, the L<ngx_rewrite> module provides the
L<ngx_rewrite/uninitialized_variable_warn> directive for disabling this warning
altogether.
=== Testing Special Values of Nginx Variables in Lua ===
As we have just mentioned, the built-in variable L<$arg_XXX> takes the special
value "not found" when the URL argument C<XXX> does not exist, but
unfortunately, it is not easy to distinguish it from the empty string value
directly in the Nginx configuration file, for example:
:nginx
location /test {
echo "name: [$arg_name]";
}
Here we intentionally omit the URL argument C<name> in our request:
:bash
$ curl 'http://localhost:8080/test'
name: []
We can see that we are still getting an empty string value, because this time
it is the Nginx "script engine" that automatically converts the "not found"
special value to an empty string when performing variable interpolation.
Then how can we test the special value "not found"? Or in other
words, how can we distinguish it from normal empty string values? Obviously, in
the following example, the URL argument C<name> does take an ordinary value,
which is a
true empty string:
:bash
$ curl 'http://localhost:8080/test?name='
name: []
But we cannot really differentiate this from the earlier case that does not
mention the C<name> argument at all.
Luckily, we can easily achieve this in Lua by means of the 3rd-party module
L<ngx_lua>. Please look at the following example:
:nginx
location /test {
content_by_lua '
if ngx.var.arg_name == nil then
ngx.say("name: missing")
else
ngx.say("name: [", ngx.var.arg_name, "]")
end
';
}
This example is very close to the previous one in terms of functionality.
We use the L<ngx_lua/content_by_lua> directive from the L<ngx_lua> module to
embed a small piece of our own Lua code to test against the special value of
the Nginx variable C<$arg_name>. When C<$arg_name> takes a special value
(either "not found" or "invalid"), we will get the following output when
requesting C</foo>:
:bash
$ curl 'http://localhost:8080/test'
name: missing
This is our first time meeting the L<ngx_lua> module, which deserves a brief
introduction. This module embeds the Lua language interpreter (or LuaJIT's
Just-in-Time compiler) into the Nginx core, to allow Nginx users directly run
their own Lua programs inside the server. The user can choose to insert
her Lua code into different running phases of the server, to fulfill different
requirements. Such Lua code are either specified directly as literal strings in
the Nginx
configuration file, or reside in external F<.lua> source files (or Lua binary
bytecode
files) whose paths are specified in the Nginx configuration.
Back to our example, we cannot directly write something like C<$arg_name> in
our Lua code. Instead, we reference Nginx variables in Lua by means of the
C<ngx.var> API provided by the L<ngx_lua> module. For example, to reference the
Nginx variable C<$VARIABLE> in Lua, we just write L<ngx_luua/ngx.var.VARIABLE>.
When the Nginx variable C<$arg_name> takes the special value "not found" (or
"invalid"), C<ngx.var.arg_name> is evaluated to the C<nil> value in the Lua
world. It should also be noting that we use the Lua function L<ngx_lua/ngx.say>
to print out the response body contents, which is functionally equivalent to
the L<ngx_echo/echo> directive we are already very familiar with.
If we provide a C<name> URI argument that takes an empty value in the request,
the output is now very different:
:bash
$ curl 'http://localhost:8080/test?name='
name: []
In this test, the value of the Nginx variable C<$arg_name> is a true empty
string, neither "not found" nor "invalid". So in Lua, the expression
C<ngx.var.arg_name> evaluates to the Lua empty string (C<"">), clearly
distinguished from the Lua C<nil> value in the previous test.
This differentiation is important in certain application scenarios. For
instance, some web services have to decide whether to use a column value to
filter the data set by checking the I<existence> of the corresponding URI
argument. For these serives, when the C<name> URI argument is absent, the
whole data set are just returned; when the C<name> argument takes an empty
value, however, only those records that take an empty value are returned.
It is worth mentioning a few limitations in the standard L<$arg_XXX> variable.
Consider using the following request to test C</test> in our previous example
using Lua:
$ curl 'http://localhost:8080/test?name'
name: missing
Now the C<$arg_name> variable still reads the "not found" special value, which
is apparently counter-intuitive. Additionally, when multiple URI arguments with
the same name are specified in the request, L<$arg_XXX> just
returns the first value of the argument, discarding other values silently:
:bash
$ curl 'http://localhost:8080/test?name=Tom&name=Jim&name=Bob'
name: [Tom]
To solve these problems, we can use the Lua function
L<ngx_lua/ngx.req.get_uri_args> provided by the L<ngx_lua> module instead.