11= Nginx Variables (05) =
22
3- In L<vartut/ (02)> we learnt that variable life cycle is bounded with
4- the request, but i own you a formal definition of "request". Without further
5- explaining, you might have assumed that "request" refers to the HTTP request
6- initiated by the client. But, there are actually two types of "request"
7- in Nginx
8- world. One is called "main request", the other "subrequest". Now let's
9- wire them in.
10-
11- The "main request" is the request initiated by HTTP client from outside
12- Nginx.
13- All requests we've been discussed are "main request", including the internal
14- "jump"
15- introduced in L<vartut/ 02), which uses command L<ngx_echo/echo_exec> and
16- L<ngx_rewrite/rewrite>
17-
18- Whereas the "subrequest" is a type of cascading request issued by Nginx
19- from within
20- its internals. "Subrequest" is encapsulated just like HTTP request, but
21- it has absolutely
22- nothing to do with HTTP protocol and networking. Subrequest is a useful
23- abstraction in
24- Nginx design, to ease the decomposition of main request into a few "internal
25- requests" with
26- finer granularity. Subrequest can be issued to multiple C<location> interfaces
27- sequentially
28- or in parallel, and handles the main request by collaborating their respective
29- outcomes.
30- Certainly, subrequest can be further decomposed into other subrequests,
31- subrequest can even
32- form a recursive pattern by cascade the request to itself. When a request
33- issues its subrequests,
34- it is called "parent request" in Nginx terminology. As a footnote, for
35- those who had wet themselves
36- with Apache, subrequest is defined equivalently and not totally a stranger.
37-
38- Let's check a subrequest example:
3+ == Variables in Subrequests ==
4+
5+ === A Detour to Subrequests ===
6+
7+ We have seen earlier that the lifetime of variable containers is bound to the
8+ request, but I own you a formal definition of "requests" there. You might have
9+ assumed that the "requests" in that context are just those HTTP requests
10+ initiated from the client side. In fact, there are two kinds of "requests" in
11+ the Nginx world. One is called "main requests", and the other is called
12+ "subrequests".
13+
14+ Main requests are those initiated externally by HTTP clients. All the examples
15+ that we have seen so far involve main requests only, including those doing
16+ "internal redirections" via the L<ngx_echo/echo_exec> or L<ngx_rewrite/rewrite>
17+ directive.
18+
19+ Whereas subrequests are a special kind of requests initiated from within the
20+ Nginx core. But please do not confuse subrequests with those HTTP requests
21+ created by the L<ngx_proxy> modules! Subrequests may look very much like an
22+ HTTP request in appearance, their implementation, however, has nothing to do
23+ with neither the HTTP protocol nor any kind of socket communication. A
24+ subrequest is an abstract invocation for decomposing the task of the main
25+ request into smaller "internal requests" that can be served independently by
26+ multiple different C<location> blocks, either in series or in parallel.
27+ "Subrequests" can also be recursive: any subrequest can initiate more
28+ sub-subrequests, targeting other C<location> blocks or even the current
29+ C<location> itself. According to Nginx's terminology, if request A initiates a
30+ subrequest B, then A is called the "parent request" of B. It is worth
31+ mentioning that the Apache web server also has the concept of subrequests for
32+ long, so readers coming from that world should be no stranger to this.
33+
34+ Let's check out an example using subrequests:
3935
4036 :nginx
4137 location /main {
@@ -51,37 +47,37 @@ Let's check a subrequest example:
5147 echo bar;
5248 }
5349
54- Now in C<location /main>, two subrequests are sent to C</foo> and C</bar>
55- via 3rd party module L<ngx_echo> and its command L<ngx_echo/echo_location>.
56- The subrequests are of type HTTP C<GET>. When they are sent by L<ngx_echo/echo_location>
57- the requests are executed sequentially, in the order of their writing,
58- which
59- means, C</bar> is sub-requested only when C</foo> has finished its part.
60- And
61- outcome from the subrequests will be concatenated as the final response
62- of C</main>
50+ Here in C<location /main>, we use the L<ngx_echo/echo_location> directive from
51+ the L<ngx_echo> module to initiate two C<GET>-typed subrequests targeting
52+ C</foo> and C</bar>, respectively. The subrequests initiated by
53+ L<ngx_echo/echo_location> are always running sequentially according to their
54+ literal order in the configuration file. Therefore, the second C</bar> request
55+ will not be fired until the first C</foo> request completes processing. The
56+ response body of these two subrequests get concatenated together according to
57+ their running order, to form the final response body of their parent request
58+ (for C</main>):
6359
6460 :bash
6561 $ curl 'http://localhost:8080/main'
6662 foo
6763 bar
6864
69- As we can tell, subrequest is issued within a virtual server, i.e. subrequest
70- handling is implemented as a few C API calls, without any networking nor
71- UNIX
72- socket, therefore subrequest execution is extremely fast.
73-
74- Back to the very initial subject about Nginx variable life cycle, we can
75- still
76- safely conclude that it is bounded with current request and every request
77- has
78- an isolated copy of variables. However, the request can be "main request"
79- as
80- well as "subrequest". Even if "parent request" and "subrequest" share the
81- same
82- variable name, they are virtually different variables. We can verify it
83- with
84- following test :
65+ It should be noted that the communication of C<location> blocks via subrequests
66+ is limited within the same C<server> block (i.e., the same virtual server
67+ configuration), so when the Nginx core processes a subrequest, it just calls a
68+ few C functions behind the scene, without doing any kind of network or UNIX
69+ domain socket communication. For this reason, subrequests are extremely
70+ efficient.
71+
72+ === Independent Variable Containers in Subrequests ===
73+
74+ Back to our earlier discussion for the lifetime of Nginx variable containers,
75+ now we can still state that the lifetime is bound to the current request, and
76+ every request does have its own copy of all the variable containers. It is just
77+ that the "request" here can be either a main request, or a subrequest.
78+ Variables with the same name between a parent request and a subrequest will
79+ generally not interfere with each other. Let's do a small experiment to confirm
80+ this :
8581
8682 :nginx
8783 location /main {
@@ -103,31 +99,30 @@ following test:
10399 echo "bar: $var";
104100 }
105101
106- In this test, the same variable C<$var> is declared in C<location /main>,
107- C<location /foo> and C<location /bar> but is initialized with different
108- values.
109- Further more, we print the value of variable C<$var> within C</main> after
110- two subrequests are handled. Then we issue a request to C</main>:
102+ In this sample, we assign different values to the variable C<$var> in three
103+ C<location> blocks, C</main>, C</foo>, and C</bar>, and output the value of
104+ C<$var> in all these locations. In particular, we intentionally output the
105+ value of C<$var> in C<location /main> I<after> calling the two subrequests, so
106+ if value changes of C<$var> in the subrequests can affect their parent request,
107+ we should see a new value output in location C</main>. The result of requesting
108+ C</main> is as follows:
111109
112110 :bash
113111 $ curl 'http://localhost:8080/main'
114112 foo: foo
115113 bar: bar
116114 main: main
117115
118- Apparently, when the subrequests are handled in C</foo> and C</bar>,
119- they both have no impact on the value of C<$var> and the main requesting
120- handling
121- in C</main>, again subrequests themselves have no impact on each other.
122- So we
123- have asserted that "main request" and every other subrequest each has its
124- own
125- copies of C<$var>.
116+ Apparently, the assignments to variable C<$var> in those two subrequests do not
117+ affect the main request C</main> at all. This successfully verifies that both
118+ the main request and its subrequests do own different copies of variable
119+ containers.
120+
121+ === Shared Variable Containers among Requests ===
126122
127- Unfortunately, there exists exceptions. Some Nginx module might issue
128- subrequest which references the same copy of variables of their parent
129- request. The
130- 3rd party module L<ngx_auth_request> is one of them:
123+ Unfortunately, subrequests initiated by certain Nginx modules do share variable
124+ containers with their parent requests, like those initiated by the 3rd-party
125+ module L<ngx_auth_request>. Below is such an example:
131126
132127 :nginx
133128 location /main {
@@ -141,43 +136,44 @@ request. The
141136 echo "sub: $var";
142137 }
143138
144- Variable C<$var> is declared and initialized with value C<main> in C<location
145- /main>
146- then a subrequest is issued via module L<ngx_auth_request> and its command
147- L<ngx_auth_request/auth_request>, finally we print variable C<$var> using
148- command L<ngx_echo/echo>.
149- Now we reset variable C<$var> as C<sub> in C<location /sub> and check what
150- our test says:
139+ Here in C<location /main>, we first assign the initial value C<main> to
140+ variable C<$var>, then fire a subrequest to C</sub> via the
141+ L<ngx_auth_request/auth_request> directive from the L<ngx_auth_request> module,
142+ and finally output the value of C<$var>. Note that in C<location /sub> we
143+ intentionally overwrite the value of C<$var> to C<sub>. When accessing
144+ C</main>, we get
151145
152146 :bash
153147 $ curl 'http://localhost:8080/main'
154148 main: sub
155149
156- It says, C<$var> has become C<sub> back in its main request to C<location
157- /main>, which means
158- the subrequest issued by L<ngx_auth_request> module shares exactly the
159- same copy of variables
160- with its parent request.
161-
162- Wait a minute, you might exclaim, why the print within subrequest to C<location
163- /sub> is missing?
164- good gocha and the answer is simple, command L<ngx_auth_request/auth_request>
165- ignores the responses
166- from its subrequest, instead all it cares is the status code from the subrequest
167- (subrequest is
168- ecapsulated as HTTP request). When status code is C<2XX>, the handling
169- of main request continues,
170- otherwise the handling gets aborted and error is returned. In our example,
171- subrequest only executes
172- a print by L<ngx_echo/echo>, which implicitly returns status code C<200>.
173-
174- Surely it's always easier to share variables in between parent request
175- and subrequest, like module
176- L<ngx_auth_request> does. Do expect the side effect and haunted bugs it
177- consequences when configuration
178- scales and becomes complicated. It's too hard to trace a variable when
179- its value get overwritten in
180- some subrequests. To keep our sanity, modules like L<ngx_echo>, L<ngx_lua>
181- and L<ngx_srcache> and many
182- other 3rd party module chooses not to share variables in between requests.
150+ Obviously, the value change of C<$var> in the subrequest to C</sub> does affect
151+ the main request to C</main>. Thus the variable container of C<$var> is indeed
152+ shared between the main request and the subrequest created by the
153+ L<ngx_auth_request> module.
154+
155+ For the previous example, some readers might ask: "why doesn't the response
156+ body of the subrequest appear in the final output?" The answer is simple: it is
157+ just because the L<ngx_auth_request/auth_request> directive discards the
158+ response
159+ body of the subrequest it manages, and only checks the response status code of
160+ the subrequest. When the status code looks good, like C<200>,
161+ L<ngx_auth_request/auth_request> will just allow Nginx continue processing the
162+ main request; otherwise it will immediately abort the main request by
163+ returning a C<403> error page, for example. In our example, the subrequest to
164+ C</sub>
165+ just return a C<200> response implicitly created by the L<ngx_echo/echo>
166+ directive in C<location /sub>.
167+
168+ Even though sharing variable containers among the main request and all its
169+ subrequests could make bidirectional data exchange easier, it could also lead
170+ to unexpected subtle issues that are hard to debug in real-world
171+ configurations. Because users often forget that a variable with the same name
172+ is actually used in some deeply embedded subrequest and just use it for
173+ something else in the main request, this variable could get unexpectedly
174+ modified during processing. Such bad side effects make many 3rd-party modules
175+ like L<ngx_echo>, L<ngx_lua> and
176+ L<ngx_srcache> choose to disable the variable sharing behavior for subrequests
177+ by
178+ default.
183179
0 commit comments