11= Nginx Variables (06) =
22
3- There are a few subtleties with Nginx builtin variable in the context of
4- subrequest.
5-
6- We have learnt from L<vartut/ (03)>, many builtin variables are not simple
7- value
8- containers, they become special because the variables are hooked with "
9- get/set handler".
10- Even if a value container can be opt-in, it is used as cache to avoid repeated
11- calculations.
12- L<ngx_core/$args> is such a variable, it uses "get/set handler" to calculate
13- the URL
14- parameter string for the current request. When it is a subrequest, L<ngx_core/$args>
15- should be calculated accordingly and return the subrequest's URL parameter
16- string. We
17- can check it with following example:
3+ == Built-in Variables in Subrequests ==
4+
5+ There are some subtleties involved in using Nginx built-in variables in the
6+ context of a subrequest. We will discuss the details in this section.
7+
8+ === Built-in Variables Sensitive to the Subrequest Context ===
9+
10+ We already know that most built-in variables are not simple value containers.
11+ They behave differently than user variables by registering "get
12+ handlers" and/or "set handlers". Even when they do own a value container, they
13+ usually just use the container as a result cache for their "get handlers". The
14+ L<ngx_core/$args> variable we discussed earlier, for example, just uses its
15+ "get handler" to return the URL query string for the current request. The
16+ current request here can also be a subrequest, so when reading
17+ L<ngx_core/$args> in a subrequest, its "get handler" should naturally return
18+ the query string for the subrequest. Let's see such an example:
1819
1920 :nginx
2021 location /main {
@@ -26,30 +27,28 @@ can check it with following example:
2627 echo "sub args: $args";
2728 }
2829
29- We print the value of variable L<ngx_core/$args> in C<location /main> by
30- command
31- L<ngx_echo/echo>, then we issue a subrequest to C<location /sub> by command
32- L<ngx_echo/echo_location>.
33- You might have noticed that the subrequest is given a second parameter,
34- which designates
35- the URL parameters (C<a=1&b=2>). Again in C<location /sub> directive, the
36- value of L<ngx_core/$args>
37- is printed.
30+ Here in the C</main> interface, we first echo out the value of
31+ L<ngx_core/$args> for the current request, and then use
32+ L<ngx_echo/echo_location> to initiate a subrequest to C</sub>. It should be
33+ noted that here we give a second argument to the L<ngx_echo/echo_location>
34+ directive, to specify the URL query string for the subrequest being fired (the
35+ first argument is the URI for the subrequest, as we already know). Finally, we
36+ define the C</sub> interface and print out the value of L<ngx_core/$args> in
37+ there. Querying the C</main> interface gives
3838
3939 :bash
4040 $ curl 'http://localhost:8080/main?c=3'
4141 main args: c=3
4242 sub args: a=1&b=2
4343
44- Clearly, L<ngx_core/$args> prints C<c=3> in C<location /main>, which is
45- the URL parameter for the
46- main request. And it prints C<a=1&b=2> in C<location /sub>, which is the
47- URL parameter for the subrequest.
48- These are exactly what we were expecting.
44+ It is clear that when L<ngx_core/$args> is read in the main request (to
45+ C</main>), its value is the URL query string of the main request; whereas when
46+ in the subrequest (to C</foo>), it is the query string of the subrequest,
47+ C<a=1&b=2>. This behavior indeed matches our intuition.
4948
50- Just like L<ngx_core/$args>, builtin variable L<ngx_core/$uri> is calculated
51- as subrequest's URI in
52- subrequest context.
49+ Just like L<ngx_core/$args>, when the built-in variable L<ngx_core/$uri> is
50+ used in a subrequest, its "get handler" also returns the (decoded) URI of the
51+ current subrequest:
5352
5453 :nginx
5554 location /main {
@@ -61,27 +60,26 @@ subrequest context.
6160 echo "sub uri: $uri";
6261 }
6362
64- Sending request to C<location /main> we have :
63+ Below is the result of querying C< /main>:
6564
6665 :bash
6766 $ curl 'http://localhost:8080/main'
6867 main uri: /main
6968 sub uri: /sub
7069
71- The result is what we'd expected .
70+ The output is what we would expect .
7271
73- Reality is imperfect, not all the builtin variable is calculated from current
74- request, a minority of builtin variables only calculate their values from
75- the
76- main request. Builtin variable L<ngx_core/$request_method>, provided by
77- module
78- L<ngx_http_core>, is one of them.
72+ === Built-in Variables for Main Requests Only ===
7973
80- When variable L<ngx_core/$request_method> is devalued, the HTTP method
81- of "main
82- request" is always obtained. The HTTP method can be C<GET>, C<POST> etc,
83- let's
84- test it:
74+ Unfortunately, not all built-in variables are sensitive to the context of
75+ subrequests. Several built-in variables always act on the main request even
76+ when they are used in a subrequest. The built-in variable
77+ L<ngx_core/$request_method> is such an exception.
78+
79+ Whenever L<ngx_core/$request_method> is read, we always get the request method
80+ name (such as C<GET> and C<POST>) for the main request, no matter whether the
81+ current request is a
82+ subrequest or not. Let's test it out:
8583
8684 :nginx
8785 location /main {
@@ -93,39 +91,39 @@ test it:
9391 echo "sub method: $request_method";
9492 }
9593
96- For this case, variable L<ngx_core/$request_method> is printed in both
97- C<location /main> and C<location /sub>. Subrequest is initiated to C<location
98- /sub>
99- from within C<location /main> by L<ngx_echo/echo_location>. Again we use
100- the C<curl> utility to send a C<POST> request to C<location /main>
94+ In this example, the C</main> and C</sub> interfaces both output the value of
95+ L<ngx_core/$request_method>. Meanwhile, we initiate a C<GET> subrequest to
96+ C</sub> via the L<ngx_echo/echo_location> directive in C</main>. Now let's do a
97+ C<POST> request to C</main>:
10198
10299 :bash
103100 $ curl --data hello 'http://localhost:8080/main'
104101 main method: POST
105102 sub method: POST
106103
107- Command C<curl> has a C<--data> option, which designates the request data,
108- meanwhile it lets the request use HTTP method C<POST>. The test shows no
109- surprises, no matter where L<ngx_core/$request_method> is devalued, the
110- HTTP method of main request is obtained: C<POST>.
111-
112- Hey, would it be the case where variables are devalued and cached for variable
113- L<ngx_core/$request_method> ? so that it is calculated in "main request"
114- and
115- referenced again in the "subrequest" ? Think again. We have learnt back
116- in
117- L<vartut/ (05)>, each request has its own copies of variables. Module L<ngx_echo>
118- fully respects this rule and the subrequest it initiates forbids the variable
119- referencing its counterpart in parent request. So back to our example,
120- even if
121- the calculation has been cached (in fact nothing is cached here), it shall
122- have
123- no impact on the variables on subrequest to C<location /sub>.
124-
125- We can adjust our example a little bit, i.e. to print L<ngx_core/$request_method>
126- after the subrequest has been initiated in C<location /main>. This helps
127- answering
128- above question.
104+ Here we use the C<--data> option of the C<curl> utility to specify our POST
105+ request body, also this option makes C<curl> use the C<POST>
106+ method for the request. The test result turns out as we predicted:
107+ the variable L<ngx_core/$request_method> is evaluated to the main request's
108+ method name, C<POST>, despite its use in a C<GET> subrequest.
109+
110+ Some readers might challenge our conclusion here by pointing out that we did
111+ not rule out the possibility that the value of L<ngx_core/$request_method> got
112+ cached at its first reading in the main request and what we were seeing in the
113+ subrequest was actually the cached value that was evaluated earlier in the main
114+ request. This concern is unnecessary, however, because we have also learned
115+ that the variable container required by data caching (if any) is always bound
116+ to the
117+ current request, also the subrequests initiated by the L<ngx_echo> module
118+ always disable variable container sharing with their parent requests.
119+ Back to the previous example, even if the built-in variable
120+ L<ngx_core/$request_method> in the main request used the value container as the
121+ data cache (actually it does not), it cannot affect the subrequest by any means.
122+
123+ To further address the concern of these readers, let's slightly modify the
124+ previous example by putting the L<ngx_echo/echo> statement for
125+ L<ngx_core/$request_method> in C</main> I<after> the L<ngx_echo/echo_location>
126+ directive that runs the subrequest:
129127
130128 :nginx
131129 location /main {
@@ -137,21 +135,22 @@ above question.
137135 echo "sub method: $request_method";
138136 }
139137
140- Test again:
138+ Let's test it again:
141139
142140 :bash
143141 $ curl --data hello 'http://localhost:8080/main'
144142 sub method: POST
145143 main method: POST
146144
147- The result is almost same as before , except the ordering of prints has
148- been reversed for parent request and subrequest. (because we reversed the
149- statements in C<location /main> .
145+ No change in the output can be observed , except that the two output lines
146+ reversed the order (since we exchange the order of those two L<ngx_echo>
147+ module's directives) .
150148
151- So we cannot correctly retrieve the HTTP method of subrequest by evaluating
152- L<ngx_core/$request_method>, yet we can use 3rd party module L<ngx_echo>
153- and
154- its variable L<ngx_echo/$echo_request_method> for the purpose.
149+ Consequently, we cannot obtain the method name of a subrequest by reading the
150+ L<ngx_core/$request_method> variable. This is a common pitfall for freshmen
151+ when dealing with method names of subrequests. To overcome this limitation, we
152+ need to turn to the built-in variable L<ngx_echo/$echo_request_method> provided
153+ by the L<ngx_echo> module:
155154
156155 :nginx
157156 location /main {
@@ -163,30 +162,32 @@ its variable L<ngx_echo/$echo_request_method> for the purpose.
163162 echo "sub method: $echo_request_method";
164163 }
165164
166- Finally this is what we'd have wanted :
165+ We are finally getting what we want :
167166
168167 :bash
169168 $ curl --data hello 'http://localhost:8080/main'
170169 main method: POST
171170 sub method: GET
172171
173- So parent request prints C<POST>, and subrequest prints C<GET>. Each
174- reflects its own HTTP method.
172+ Now within the subrequest, we get its own method name, C<GET>, as expected, and
173+ the main request method remains C<POST>.
174+
175+ Similar to L<ngx_core/$request_method>, the built-in variable
176+ L<ngx_core/$request_uri> also always returns the (non-decoded) URL for the main
177+ request. This is more understandable, however, because subrequests are
178+ essentially faked requests inside Nginx, which do not really take a non-decoded
179+ raw URL.
175180
176- Builtin variable L<ngx_core/$request_uri>, like L<ngx_core/$request_method>,
177- always returns the encoded URL of "main request" no matter the context
178- it
179- is evaluated. This is normal since subrequests are Nginx internal abstractions
180- in which an encoded request URL has no specific meanings.
181+ === Variable Container Sharing and Value Caching Together ===
181182
182- If it were the case you would have worried, that builtin variable is cached
183- in between parent request and subrequests, it is as bitter as hell. Since
184- we
185- have already learnt in L<vartut/ (05)> that module L<ngx_auth_request>
186- allows
187- its subrequest to share the same copy of variables with its parent request,
188- let's
189- have guts for the following dreadful case :
183+ In the previous section, some of the readers were worried about the case that
184+ variable container sharing in subrequests and value caching for variable's "get
185+ handlers" were working together. If it were indeed the case, then it would be a
186+ nightmare because it would be really really hard to predict what is going on by
187+ just looking at the configuration file. In previous sections, we already
188+ learned that the subrequests initiated by the L<ngx_auth_request> module are
189+ sharing the same variable containers with their parents, so we can maliciously
190+ construct such a horrible example :
190191
191192 :nginx
192193 map $uri $tag {
@@ -208,36 +209,33 @@ have guts for the following dreadful case:
208209 }
209210 }
210211
211- Our old friend L<ngx_map/map> defines mapping rules from builtin variable
212- L<ngx_core/$uri> to user variable C<$tag>. The rules are: C<$tag> is 1
213- if
214- L<ngx_core/$uri> is C</main>, 2 if it is C</sub>, 0 for all the other cases.
215- Then subrequest is initiated to <location /sub> by using module L<ngx_auth_requst>
216- and its command L<ngx_auth_request/auth_request>. After the subrequest
217- is
218- handled, variable C<$tag> is printed. Guess what happens on the output
219- if
220- we request to C<location /main> ?
212+ Here we use our old friend, the L<ngx_map/map> directive, to map the value of
213+ the built-in variable L<ngx_core/$uri> to our user variable C<$tag>. When
214+ L<ngx_core/$uri> takes the value C</main>, the value C<1> is assigned to
215+ C<$tags>; when L<ngx_core/$uri> takes the value C</sub>, the value C<2> is
216+ assigned instead to C<$tags>; under all the other conditions, C<0> is assigned.
217+ Next, in C</main>, we first initiate a subrequest to C</sub> by using the
218+ L<ngx_auth_request/auth_request> directive, and then output the value of
219+ C<$tag>. And within C</sub>, we directly output the value of C<$tag>. Guess
220+ what we will get when we access C</main>?
221221
222222 $ curl 'http://localhost:8080/main'
223223 main tag: 2
224224
225- eh ? did the mapping rules says C<$tag> is 1 when L<ngx_core/$uri> is C</main>
226- ?
227- why it is 2 as if </sub> is requested ?
228-
229- Hold on, this is because variable C<$tag> is first devalued in subrequest
230- to C</sub>,
231- it is mapped as C<2> under that context (L<ngx_core/$uri> becomes C<sub>,
232- so mapping rule
233- deducts the value accordingly as C<2>). Mapping result is cached, even
234- worse, the subrequest
235- initiated by L<ngx_auth_request/auth_request> shares the same copy of variables
236- in between
237- parent and subrequests. So Nginx returns the cached value C<2> when variable
238- C<$tag> is
239- devalued back in the parent request. What a big deal.
240-
241- Admittedly, think twice if we design to share variables in between parent
242- request and subrequests.
225+ Ouch! Didn't we map the value C</main> to C<1>? Why the actual output for
226+ C</main> is the value, C<2>, for C</sub>? What is going on here?
227+
228+ Actually it worked like this: our C<$tag> variable was first read in the
229+ subrequest to C</sub>, and the "get handler" registered by L<ngx_map/map>
230+ computed the value C<2> for C<$tag> in that context (because L<ngx_core/$uri>
231+ was C</sub> in the subrequest) and the value C<2> got cached in the value
232+ container of C<$tag> from then on. Because the parent request shared the same
233+ container as the subrequest created by L<ngx_auth_request/auth_request>, when
234+ the parent request read C<$tag> later (after the subrequest was finished), the
235+ cached value C<2> was directly returned! Such results can indeed be very
236+ surprising at first glance.
237+
238+ From this example, we can conclude again that it can hardly be a good idea to
239+ enable
240+ variable container sharing in subrequests.
243241
0 commit comments