-
Notifications
You must be signed in to change notification settings - Fork 437
/
Copy path01-NginxVariables01.tut
292 lines (232 loc) · 10.2 KB
/
01-NginxVariables01.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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
= Nginx Variables (01) =
== Variables as Value Containers ==
Nginx's configuration files use a micro programming language. Many real-world
Nginx configuration files are essentially small programs.
This language's design
is heavily influenced by
Perl and Bourne Shell as far as I can see, despite the fact that it might not
be Turing-Complete and it is declarative in many places. This is a
distinguishing feature of Nginx, as compared
to other web servers
like Apache or Lighttpd. Being a programming language, "variables" are
thus a natural part of it (exceptions do exist, of course, as in pure
functional languages like Haskell).
[[File:value-container.jpg|thumb|alt=Variables are value containers]]
Variables are just containers holding various values in imperative languages
like Perl, Bourne Shell, and C/C++.
And "values" can be numbers like C<3.14>, strings like
C<hello world>, or even complicated things like references to arrays or
hash tables in those languages. For the
Nginx configuration language, however, variables can hold only one type
of values, that is, strings (there is an interesting exception: the 3rd-party
module L<ngx_array_var> extends Nginx variables to hold arrays, but it is
implemented by encoding a C pointer as a binary string value behind the scene).
== Variable Syntax and Interpolation ==
Let's say our F<nginx.conf> configuration file has the following line:
:nginx
set $a "hello world";
We assign a value to the variable C<$a> via the L<ngx_rewrite/set>
configuration directive coming from the standard L<ngx_rewrite> module. In
particular, we assign the string value C<hello world> to C<$a>.
We can see that the Nginx variable name takes a dollar sign (C<$>) in front of
it. This is required by the language syntax: whenever we want to reference an
Nginx variable in the configuration file, we must add a C<$> prefix. This looks
very familiar to those Perl and PHP programmers.
Such variable prefix modifiers may discomfort some Java and C#
programmers, this notation does have an
obvious advantage though, that is, variables can be embedded directly into a
string literal:
:nginx
set $a hello;
set $b "$a, $a";
Here we use the value of the existing Nginx variable C<$a> to construct the
value for the variable C<$b>. So after these two directives complete execution,
the value of C<$a> is C<hello>, and C<$b> is C<hello, hello>. This technique is
called "variable interpolation" in the Perl world, which makes ad-hoc string
concatenation operators no longer that necessary. Let's use the same term for
the Nginx world from now on.
Let's see another complete example:
:nginx
server {
listen 8080;
location /test {
set $foo hello;
echo "foo: $foo";
}
}
This example omits the C<http> directive and C<events> configuration blocks in
the outer-most scope for brevity. To request this C</test> interface via
C<curl>, an HTTP client utility, on the command line, we get
:bash
$ curl 'http://localhost:8080/test'
foo: hello
Here we use the L<ngx_echo/echo> directive of the 3rd party module L<ngx_echo>
to print out the value of the C<$foo> variable as the HTTP response.
Apparently the arguments of the L<ngx_echo/echo> directive does support
"variable interpolation", but we
can not take it
for granted for other directives. Because not all the configuration directives
support "variable interpolation"
and it is
in fact up to the implementation of the directive in that module. Always look
up the documentation to be sure.
=== Escaping "$" ===
We've already learned that the C<$> character is special and it serves as the
variable name prefix, but now consider that we want to output a literal C<$>
character via the L<ngx_echo/echo> directive. The following naive example does
not work at all:
? :nginx
? location /t {
? echo "$";
? }
We will get the following error message while loading this configuration:
[emerg] invalid variable name in ...
Obviously Nginx tries to parse C<$"> as a variable name. Is there a way to
escape C<$> in the string literal? The answer is "no" (it is still the case in
the
latest Nginx stable
release C<1.2.7>) and I have been hoping that we could write something like
C<$$> to obtain a literal C<$>.
Luckily, workarounds do exist and here is one proposed by Maxim Dounin: first
we assign to a variable a literal string containing a dollar sign character
via a configuration directive that does I<not> support "variable interpolation"
(remember that not all the directives support "variable interpolation"?), and
then reference this variable later whenever we need a dollar sign. Here is such
an
example to demonstrate the idea:
:nginx
geo $dollar {
default "$";
}
server {
listen 8080;
location /test {
echo "This is a dollar sign: $dollar";
}
}
Let's test it out:
:bash
$ curl 'http://localhost:8080/test'
This is a dollar sign: $
Here we make use of the L<ngx_geo/geo> directive of the standard module
L<ngx_geo> to initialize the
C<$dollar> variable with the string C<"$">, thereafter variable C<$dollar>
can be used
in places that require a dollar sign. This works because the L<ngx_geo/geo>
directive does not
support "variable interpolation" at all. However, the L<ngx_geo> module
is originally designed to set a Nginx variable to different values according to
the
remote client
address, and in this example, we just abuse it to initialize the C<$dollar>
variable
with the string C<"$"> unconditionally.
=== Disambiguating Variable Names ===
There is a special case for "variable interpolation", that is, when the variable
name is followed directly by characters allowed in variable names (like
letters, digits, and underscores).
In such cases, we can use a special notation to disambiguate the variable name
from the subsequent literal characters, for instance,
:nginx
server {
listen 8080;
location /test {
set $first "hello ";
echo "${first}world";
}
}
Here the variable C<$first> is concatenated with the literal string C<world>.
If it
were written
directly as C<"$firstworld">, Nginx's "variable interpolation" engine (also
known as the "script engine") would try to access the variable
C<$firstworld> instead of C<$first>. To resolve the ambiguity here, curly braces
must be used
around the variable name (excluding the C<$> prefix), as in C<${first}>. Let's
test this sample:
:bash
$ curl 'http://localhost:8080/test'
hello world
== Variable Declaration and Creation ==
In languages like C/C++, variables must be declared (or created) before they
can be used so that the compiler can allocate storage and perform type checking
at compile-time. Similarly, Nginx creates all the Nginx variables while loading
the configuration file (or in other words, at "configuration time"), therefore
Nginx
variables are also required to be declared somehow.
Fortunately the L<ngx_rewrite/set> directive and the L<ngx_geo/geo> directive
mentioned above do have the side effect of declaring or creating Nginx
variables that they will assign values to later at "request time". If we do not
declare a variable this way and use it directly in, say, the L<ngx_echo/echo>
directive, we will get an error. For example,
:nginx
? server {
? listen 8080;
?
? location /bad {
? echo $foo;
? }
? }
Here we do not declare the C<$foo> variable and access its value directly in
L<ngx_echo/echo>. Nginx will just refuse loading this configuration:
[emerg] unknown "foo" variable
Yes, we cannot even start the server!
Nginx variable creation and assignment happen
at completely different phases along the time-line.
Variable creation only occurs when Nginx loads its configuration. On the other
hand, variable assignment occurs when requests are actually
being served. This also means that we can never create new Nginx variables at
"request time".
== Variable Scope ==
Once an Nginx variable is created, it is visible to the entire configuration,
even across different virtual server configuration
blocks, regardless of the places it is declared at. Here is
an example:
:nginx
server {
listen 8080;
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
}
Here the variable C<$foo> is created by the L<ngx_rewrite/set> directive within
C<location /bar>,
and this variable is visible to the entire configuration, therefore we can
reference it in C<location
/foo> without
worries. Below is the result of testing these two interfaces via the C<curl>
tool.
:bash
$ curl 'http://localhost:8080/foo'
foo = []
$ curl 'http://localhost:8080/bar'
foo = [32]
$ curl 'http://localhost:8080/foo'
foo = []
We can see that the assignment operation is only performed in requests that
access C<location /bar>, since the corresponding L<ngx_rewrite/set> directive
is only used in that location. When requesting the C</foo> interface, we always
get an empty value for the C<$foo> variable because that is what we get when
accessing an uninitialized variable.
Another important characteristic that we can observe from this example is that
even
though the scope of Nginx variables is the entire configuration, each request
does have its own version of all those variables' containers. Requests do not
interfere with each other even if they are referencing a variable with the same
name. This is very much like local variables in C/C++ function bodies. Each
invocation of the C/C++ function does use its own version of those local
variables (on the stack).
For instance, in this sample, we request C</bar> and the variable C<$foo> gets
the value C<32>, which does not affect the value of C<$foo> in subsequent
requests to C</foo> (it is still uninitialized!), because they correspond to
different value containers.
One common mistake for Nginx newcomers is to regard Nginx
variables as something shared among all the requests. Even though the scope of
Nginx variable I<names> go across configuration blocks at "configuration time",
its I<value container>'s scope never goes beyond request
boundaries at "request time". Essentially here we do have two different kinds
of scope here.