├── .gitignore ├── Makefile ├── README.markdown ├── ebooks.mk ├── en ├── 00-Foreword01.tut ├── 00-Foreword02.tut ├── 01-NginxVariables01.tut ├── 01-NginxVariables02.tut ├── 01-NginxVariables03.tut ├── 01-NginxVariables04.tut ├── 01-NginxVariables05.tut ├── 01-NginxVariables06.tut ├── 01-NginxVariables07.tut ├── 01-NginxVariables08.tut ├── 02-NginxDirectiveExecOrder01.tut ├── 02-NginxDirectiveExecOrder02.tut ├── 02-NginxDirectiveExecOrder03.tut ├── 02-NginxDirectiveExecOrder04.tut ├── 02-NginxDirectiveExecOrder05.tut ├── 02-NginxDirectiveExecOrder06.tut ├── 02-NginxDirectiveExecOrder07.tut ├── 02-NginxDirectiveExecOrder08.tut ├── 02-NginxDirectiveExecOrder09.tut └── 02-NginxDirectiveExecOrder10.tut ├── image ├── value-container.jpg └── value-container.ora ├── tutorial-simple.css ├── tutorial.css ├── utils ├── fix-html.pl ├── fmt.pl ├── gen-html-index-cn.pl ├── gen-html-index-en.pl ├── prefix.pl ├── tut2wiki-cn.pl ├── tut2wiki-en.pl ├── wc.pl ├── wiki2html-cn.pl └── wiki2html-en.pl └── zh-cn ├── 00-Foreword01.tut ├── 00-Foreword02.tut ├── 01-NginxVariables01.tut ├── 01-NginxVariables02.tut ├── 01-NginxVariables03.tut ├── 01-NginxVariables04.tut ├── 01-NginxVariables05.tut ├── 01-NginxVariables06.tut ├── 01-NginxVariables07.tut ├── 01-NginxVariables08.tut ├── 02-NginxDirectiveExecOrder01.tut ├── 02-NginxDirectiveExecOrder02.tut ├── 02-NginxDirectiveExecOrder03.tut ├── 02-NginxDirectiveExecOrder04.tut ├── 02-NginxDirectiveExecOrder05.tut ├── 02-NginxDirectiveExecOrder06.tut ├── 02-NginxDirectiveExecOrder07.tut ├── 02-NginxDirectiveExecOrder08.tut ├── 02-NginxDirectiveExecOrder09.tut ├── 02-NginxDirectiveExecOrder10.tut └── 02-NginxDirectiveExecOrder11.tut /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | a.pod 5 | a.wiki 6 | html/ 7 | wiki/ 8 | *.html 9 | *.mobi 10 | *.epub 11 | *.pdf 12 | *.png 13 | go 14 | draft/ 15 | upload 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ver=$(shell date +'%Y.%m.%d') 2 | 3 | .PHONY: all zhcn en clean html 4 | 5 | all: zhcn en 6 | 7 | zhcn: 8 | $(MAKE) -f ebooks.mk lang=$@ lang2=cn src=zh-cn title="agentzh的Nginx教程($(ver)版)" 9 | 10 | en: 11 | $(MAKE) -f ebooks.mk 12 | 13 | html: 14 | $(MAKE) -f ebooks.mk lang=zhcn lang2=cn src=zh-cn title="agentzh的Nginx教程($(ver)版)" html 15 | $(MAKE) -f ebooks.mk html 16 | 17 | clean: 18 | rm -rf html/ wiki/ index*.html *.mobi *.epub *.pdf agentzh-nginx-tutorials-*.html 19 | 20 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | This repository holds the source scripts for my Nginx tutorial series 2 | published here: 3 | 4 | http://openresty.org/download/agentzh-nginx-tutorials-en.html 5 | 6 | Versions in other languages are expected to be maintained by the community. 7 | 8 | Pre-generated e-books can be downloaded directly from here: 9 | 10 | http://openresty.org/#eBooks 11 | 12 | How to generate HTML and other ebook format files on your side: 13 | 14 | 1. install perl 5.8.1+ into your system (usually it is already 15 | installed on \*NIX systems. 16 | 2. install the Perl CPAN module List::MoreUtils with the 17 | command: 18 | 19 | sudo cpan List::MoreUtils 20 | 21 | 3. Install Calibre from here: http://calibre-ebook.com/ 22 | 4. Build the ebook files \*.mobi and \*.epub 23 | make 24 | Note that Gnu make is also required. On most \*BSD systems, 25 | it's usually required to run "gmake" instead of "make" here. 26 | 27 | -------------------------------------------------------------------------------- /ebooks.mk: -------------------------------------------------------------------------------- 1 | lang= en 2 | lang2= en 3 | src= en 4 | ver= $(shell date +'%Y.%m.%d') 5 | title= agentzh's Nginx Tutorials (ver $(ver)) 6 | name= agentzh-nginx-tutorials-$(lang) 7 | tutfiles= $(sort $(wildcard $(src)/*.tut)) 8 | wikifiles= $(patsubst %.tut,wiki/%.wiki,$(tutfiles)) 9 | htmlfiles= $(patsubst %.tut,html/%.html,$(tutfiles)) 10 | 11 | .PHONY: all mobi epub pdf html 12 | 13 | .PRECIOUS: $(wikifiles) $(htmlfiles) 14 | 15 | all: mobi epub pdf html 16 | 17 | mobi: $(name).mobi 18 | 19 | epub: $(name).epub 20 | 21 | pdf: $(name).pdf 22 | 23 | html: $(name).html 24 | 25 | %.pdf: $(name).html 26 | cp tutorial-simple.css tutorial.css 27 | ebook-convert $< $@ \ 28 | --margin-bottom 5 \ 29 | --margin-top 5 \ 30 | --margin-left 5 \ 31 | --margin-right 5 \ 32 | --no-chapters-in-toc \ 33 | --book-producer 'agentzh' \ 34 | --pdf-default-font-size 12 \ 35 | --pdf-mono-font-size 12 \ 36 | --paper-size letter \ 37 | --title "$(title)" --publisher 'agentzh' \ 38 | --language $(lang2) --authors 'agentzh' 39 | git co tutorial.css 40 | 41 | %.mobi: $(name).html 42 | cp tutorial-simple.css tutorial.css 43 | ebook-convert $< $@ \ 44 | --output-profile kindle_dx --no-inline-toc \ 45 | --title "$(title)" --publisher 'agentzh' \ 46 | --language $(lang2) --authors 'agentzh' 47 | git co tutorial.css 48 | 49 | %.epub: $(name).html 50 | cp tutorial-simple.css tutorial.css 51 | ebook-convert $< $@ \ 52 | --no-default-epub-cover \ 53 | --output-profile kindle_dx \ 54 | --title "$(title)" --publisher 'agentzh' \ 55 | --language $(lang2) --authors 'agentzh' 56 | git co tutorial.css 57 | 58 | $(name).html: $(htmlfiles) ./utils/gen-html-index-$(lang2).pl 59 | ./utils/gen-html-index-$(lang2).pl -v $(ver) -o $@ $(htmlfiles) 60 | 61 | html/%.html: wiki/%.wiki ./utils/wiki2html-$(lang2).pl 62 | mkdir -p $(dir $@) 63 | ./utils/wiki2html-$(lang2).pl -o $@ $< 64 | 65 | wiki/%.wiki: %.tut ./utils/tut2wiki-$(lang2).pl 66 | mkdir -p $(dir $@) 67 | ./utils/tut2wiki-$(lang2).pl -o $@ $< 68 | 69 | #test: ; echo $(htmlfiles) 70 | -------------------------------------------------------------------------------- /en/00-Foreword01.tut: -------------------------------------------------------------------------------- 1 | = Foreword = 2 | 3 | I've been doing a lot of work in the Nginx world over the last few years and 4 | I've also been thinking about writing a series of tutorial-like articles to 5 | explain to more people what I've done and what I've learned in this area. Now I 6 | have finally decided to post serial articles to the Sina Blog 7 | L in Chinese. Every article will roughly 8 | cover a single topic and will be in a rather casual style. But at some point in 9 | the future I may restructure the articles and their style in order to turn them 10 | into a "real" book. 11 | 12 | The articles are divided into series. For example, the first series is 13 | "Nginx Variables". Each series can be thought of as mapping to a chapter in the 14 | Nginx book that I may publish in the future. 15 | 16 | The articles are intended for Nginx users of all experience levels, including 17 | users with extensive Apache and Lighttpd experience who may have never used 18 | Nginx before. 19 | 20 | The examples in the articles are at least compatible with Nginx C<0.8.54>. Do 21 | not try the examples with older versions of Nginx. The latest stable version of 22 | Nginx as of this writing is C<1.7.9>. 23 | 24 | All of the Nginx modules referenced in the articles are production-ready. I will 25 | not be covering any Nginx core modules that are either experimental or buggy. 26 | Additionally, I will be making extensive use of 3rd-party Nginx modules in the 27 | examples. If it's inconvenient for you to download and install the individual 28 | modules one at a time then I highly recommend that you download and install the 29 | C software bundle that I maintain. 30 | 31 | L 32 | 33 | All of the modules referenced in the articles, including the core Nginx modules 34 | that are new (but stable), are included in the OpenResty bundle. 35 | 36 | A principle that I will be trying to adhere to is to use small concise examples 37 | to explain and validate the concepts and behaviors being described. My hope is 38 | that it will help the reader to develop the good habit of not accepting others' 39 | viewpoints or statements at face value without testing them first. This approach 40 | may have something to do with my QA background. In fact, I keep tweaking and 41 | correcting the articles based on the results of running the examples while 42 | writing. 43 | 44 | The examples in the articles fall into one of two categories, good and 45 | problematic. The purpose of the problematic examples is to highlight potential 46 | pitfalls and other areas where Nginx or its modules behave in ways that readers 47 | may not expect. Problematic examples are easy to identify because each line of 48 | text in the example will be prefixed with a question mark, i.e., "C". Here is 49 | an example: 50 | 51 | :nginx 52 | ? server { 53 | ? listen 8080; 54 | ? 55 | ? location /bad { 56 | ? echo $foo; 57 | ? } 58 | ? } 59 | 60 | Do not reproduce these articles without explicit permissions from us. Copyright 61 | reserved. 62 | 63 | I encourage readers to send feedback (C), especially 64 | constructive criticism. 65 | 66 | The source for all the articles is on GitHub: 67 | 68 | L 69 | 70 | The source files are under the F directory. I am using a little markup 71 | language that is a mixture of C and C to write these articles. They 72 | are the F<.tut> files. You are welcome to create forks and/or provide patches. 73 | 74 | The e-books files that are suitable for cellphones, Kindle, iPad/iPhone, 75 | Sony Readers, and other devices can be downloaded from here: 76 | 77 | L 78 | 79 | Special thanks go to Kai Wu (kai10k) who kindly translates these articles to 80 | English. 81 | 82 | agentzh at home in the Fuzhou city 83 | 84 | October 30, 2011 85 | -------------------------------------------------------------------------------- /en/00-Foreword02.tut: -------------------------------------------------------------------------------- 1 | = Writing Plan for the Tutorials = 2 | 3 | Here lists the tutorial series that have already been published or to 4 | be published. 5 | 6 | * Getting Started with Nginx 7 | * How Nginx Matches URIs 8 | * L 9 | * L 10 | * Nginx's if is Evil 11 | * Nginx Subrequests 12 | * Nginx Static File Services 13 | * Nginx Log Services 14 | * Application Gateways based on Nginx 15 | * Reverse-Proxies based on Nginx 16 | * Nginx and Memcached 17 | * Nginx and Redis 18 | * Nginx and MySQL 19 | * Nginx and PostgreSQL 20 | * Application caching Based on Nginx 21 | * Security and Access Control in Nginx 22 | * Web Services Based on Nginx 23 | * AJAX Applications Driven by Nginx 24 | * Performance Testing for Nginx and its Applications 25 | * Strength of the Nginx Community 26 | 27 | The series names can roughly correspond to the chapter names in my final 28 | Nginx book, but they are unlikely to stay exactly the same. The actual series 29 | names may change and the relative order of the series may change as well. 30 | 31 | The list above will be constantly updated to always reflect the latest plan. 32 | 33 | -------------------------------------------------------------------------------- /en/01-NginxVariables05.tut: -------------------------------------------------------------------------------- 1 | = Nginx Variables (05) = 2 | 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 owe 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 or L 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 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 blocks, either in series or in parallel. 27 | "Subrequests" can also be recursive: any subrequest can initiate more 28 | sub-subrequests, targeting other C blocks or even the current 29 | C 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: 35 | 36 | :nginx 37 | location /main { 38 | echo_location /foo; 39 | echo_location /bar; 40 | } 41 | 42 | location /foo { 43 | echo foo; 44 | } 45 | 46 | location /bar { 47 | echo bar; 48 | } 49 | 50 | Here in C, we use the L directive from 51 | the L module to initiate two C-typed subrequests targeting 52 | C and C, respectively. The subrequests initiated by 53 | L are always running sequentially according to their 54 | literal order in the configuration file. Therefore, the second C request 55 | will not be fired until the first C 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): 59 | 60 | :bash 61 | $ curl 'http://localhost:8080/main' 62 | foo 63 | bar 64 | 65 | It should be noted that the communication of C blocks via subrequests 66 | is limited within the same C 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: 81 | 82 | :nginx 83 | location /main { 84 | set $var main; 85 | 86 | echo_location /foo; 87 | echo_location /bar; 88 | 89 | echo "main: $var"; 90 | } 91 | 92 | location /foo { 93 | set $var foo; 94 | echo "foo: $var"; 95 | } 96 | 97 | location /bar { 98 | set $var bar; 99 | echo "bar: $var"; 100 | } 101 | 102 | In this sample, we assign different values to the variable C<$var> in three 103 | C blocks, C, C, and C, 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 I 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. The result of requesting 108 | C is as follows: 109 | 110 | :bash 111 | $ curl 'http://localhost:8080/main' 112 | foo: foo 113 | bar: bar 114 | main: main 115 | 116 | Apparently, the assignments to variable C<$var> in those two subrequests do not 117 | affect the main request C 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 === 122 | 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. Below is such an example: 126 | 127 | :nginx 128 | location /main { 129 | set $var main; 130 | auth_request /sub; 131 | echo "main: $var"; 132 | } 133 | 134 | location /sub { 135 | set $var sub; 136 | echo "sub: $var"; 137 | } 138 | 139 | Here in C, we first assign the initial value C
to 140 | variable C<$var>, then fire a subrequest to C via the 141 | L directive from the L module, 142 | and finally output the value of C<$var>. Note that in C we 143 | intentionally overwrite the value of C<$var> to C. When accessing 144 | C
, we get 145 | 146 | :bash 147 | $ curl 'http://localhost:8080/main' 148 | main: sub 149 | 150 | Obviously, the value change of C<$var> in the subrequest to C does affect 151 | the main request to C. Thus the variable container of C<$var> is indeed 152 | shared between the main request and the subrequest created by the 153 | L 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 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 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 165 | just return a C<200> response implicitly created by the L 166 | directive in C. 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, L and 176 | L choose to disable the variable sharing behavior for subrequests 177 | by 178 | default. 179 | 180 | -------------------------------------------------------------------------------- /en/01-NginxVariables08.tut: -------------------------------------------------------------------------------- 1 | = Nginx Variables (08) = 2 | 3 | In L we mentioned that another category of builtin variables 4 | L<$cookie_XXX> are like L<$arg_XXX>. Similarly when there exist no cookie 5 | named C, its corresponding Nginx variable L<$cookie_XXX> has non-value 6 | "not found". 7 | 8 | :nginx 9 | location /test { 10 | content_by_lua ' 11 | if ngx.var.cookie_user == nil then 12 | ngx.say("cookie user: missing") 13 | else 14 | ngx.say("cookie user: [", ngx.var.cookie_user, "]") 15 | end 16 | '; 17 | } 18 | 19 | The C utility offers the C<--cookie name=value> option, which designates 20 | C as a cookie of its request (by adding the C header). 21 | Let's test a few cases containing cookies. 22 | 23 | $ curl --cookie user=agentzh 'http://localhost:8080/test' 24 | cookie user: [agentzh] 25 | 26 | $ curl --cookie user= 'http://localhost:8080/test' 27 | cookie user: [] 28 | 29 | $ curl 'http://localhost:8080/test' 30 | cookie user: missing 31 | 32 | As expected, when cookie C does not exist, Lua variable C 34 | is C. So we have successfully distinguished the case with empty string 35 | and the 36 | case with non-value. 37 | 38 | A nice add-on with module L is when lua references an undeclared 39 | variable 40 | of Nginx, the variable is C and Nginx will not aborts it loading as 41 | before. 42 | 43 | :nginx 44 | location /test { 45 | content_by_lua ' 46 | ngx.say("$blah = ", ngx.var.blah) 47 | '; 48 | } 49 | 50 | User variable C<$blah> is never declared in the Nginx configuration F, but 52 | it is referenced as C in Lua code. Nginx can be started still, 53 | because 54 | when Nginx loads its configuration, Lua code is only compiled but not executed, 55 | So Nginx 56 | has no idea a variable C<$blah> is referenced. When lua command is executed 57 | in run time 58 | by command L, the lua variable is evaluated as 59 | C. Module 60 | L and its command L will convert Lua C into 61 | string C<"nil"> 62 | before it is printed, so the output will be: 63 | 64 | :bash 65 | curl 'http://localhost:8080/test' 66 | $blah = nil 67 | 68 | This is indeed what we want. 69 | 70 | We should have noticed also, when command L includes 71 | C<$blah> 72 | in its parameter, it is never evaluated as "variable interpolation" does 73 | (otherwise 74 | Nginx will be complaining variable C<$blah> is not declared). This is because 75 | command 76 | L does not really support "variable interpolation" 77 | . As we have 78 | said earlier in L, Nginx command does not necessarily support 79 | "variable 80 | interpolation" and it is entirely up to the module implementation. 81 | 82 | It's actually difficult to return an "invalid" non-value. As we learnt 83 | in L, 84 | variables which are declared but not initialized by L 85 | has non-value "invalid". 86 | However, as soon as the variable is devalued, the "get handler" is executed 87 | and an empty 88 | string is computed and cached, so eventually empty string is returned, 89 | not the "invalid" non-value. 90 | Following lua code can prove this: 91 | 92 | :nginx 93 | location /foo { 94 | content_by_lua ' 95 | if ngx.var.foo == nil then 96 | ngx.say("$foo is nil") 97 | else 98 | ngx.say("$foo = [", ngx.var.foo, "]") 99 | end 100 | '; 101 | } 102 | 103 | location /bar { 104 | set $foo 32; 105 | echo "foo = [$foo]"; 106 | } 107 | 108 | By requesting to C we have: 109 | 110 | :bash 111 | $ curl 'http://localhost:8080/foo' 112 | $foo = [] 113 | 114 | As we can tell, when Lua references uninitialized Nginx variable 115 | C<$foo>, it obtains empty string. 116 | 117 | Last not the least, we should have pointed out, although Nginx variable 118 | can have only strings as valid value. The 3rd party module L 119 | can support array like operations for Nginx variable.Here is an example: 120 | 121 | :nginx 122 | location /test { 123 | array_split "," $arg_names to=$array; 124 | array_map "[$array_it]" $array; 125 | array_join " " $array to=$res; 126 | 127 | echo $res; 128 | } 129 | 130 | Module L provides commands C, C 131 | and C. The semantics is pretty close to the builtin functions 132 | C, C and C in Perl (other languages support similar 133 | functionalities too). Now let's check what happens when C 134 | is 135 | requested: 136 | 137 | :bash 138 | $ curl 'http://localhost:8080/test?names=Tom,Jim,Bob' 139 | [Tom] [Jim] [Bob] 140 | 141 | Clearly module L make it easier to handle inputs with variable 142 | length, such as the URL parameter C, which composes of multiple comma 143 | delimited 144 | names. Still we must emphasize, module L is a much better choice 145 | to execute 146 | this kind of complicated tasks, usually it is more flexible and maintainable. 147 | 148 | Till now the tutorial covers the Nginx variable. In the process we have 149 | been discussing 150 | many builtin and 3rd party Nginx modules, these modules help us better 151 | understand features 152 | and internals of Nginx variable by composing various mini constructs. Later 153 | on the tutorial 154 | will be covering more details of those modules. 155 | 156 | With these examples, we should understand that Nginx variable plays a key 157 | role in the 158 | Nginx mini language: variables are the ways and means Nginx communicate 159 | internally, they 160 | contain all the needed information (including the request information) 161 | and they are the 162 | cornerstone elements which bridge every other Nginx modules. Nginx variables 163 | are everywhere 164 | in the coming tutorials, understand them is absolutely necessary. 165 | 166 | In the coming tutorial "L", we will be 167 | discussing in detail 168 | the Nginx execution ordering and the phases every request traverses. It' 169 | s indispensable to 170 | understand them since for the Nginx mini language, the ordering of writing 171 | can be dramatically 172 | different from the ordering of executing in the timeline. It usually confuses 173 | many Nginx users. 174 | 175 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder02.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (02) = 2 | 3 | We've just learnt, all L commands within C 4 | are executed in C phase. In fact, almost all commands implemented 5 | by module C are executed in C phase under the specific 6 | context. Commad L introduced in 7 | L 8 | is one of them. However, we shall point out that when these commands are 9 | found in C directive, they will be executed in an earlier phase 10 | we've not 11 | addressed: the C phase. 12 | 13 | Command L, introduced in L 15 | is also executed in C phase. Actually, commands implemented by 16 | module 17 | L can mix with commands implemented by module L 18 | and the execution ordering is ensured. Let's check an example: 19 | 20 | :nginx 21 | location /test { 22 | set $a "hello%20world"; 23 | set_unescape_uri $b $a; 24 | set $c "$b!"; 25 | 26 | echo $c; 27 | } 28 | 29 | By sending a request accordingly we have: 30 | 31 | :bash 32 | $ curl 'http://localhost:8080/test' 33 | hello world! 34 | 35 | Apparently, the L command and its 36 | neighboring L commands are all executed in the order 37 | of their writing. 38 | 39 | To further demonstrate our assertion, we check again Nginx "debug log" 40 | (in case it's unclear for you how to check "debug log", please reference 41 | steps found in L). 42 | 43 | :bash 44 | grep -E 'http script (value|copy|set)' logs/error.log 45 | 46 | The debug logs are filtered as: 47 | 48 | :text 49 | [debug] 11167#0: *1 http script value: "hello%20world" 50 | [debug] 11167#0: *1 http script set $a 51 | [debug] 11167#0: *1 http script value (post filter): "hello world" 52 | [debug] 11167#0: *1 http script set $b 53 | [debug] 11167#0: *1 http script copy: "!" 54 | [debug] 11167#0: *1 http script set $c 55 | 56 | The leading two lines: 57 | 58 | :text 59 | [debug] 11167#0: *1 http script value: "hello%20world" 60 | [debug] 11167#0: *1 http script set $a 61 | 62 | They correspond to the command 63 | 64 | :nginx 65 | set $a "hello%20world"; 66 | 67 | The following two lines: 68 | 69 | :text 70 | [debug] 11167#0: *1 http script value (post filter): "hello world" 71 | [debug] 11167#0: *1 http script set $b 72 | 73 | They are generated by command 74 | 75 | :nginx 76 | set_unescape_uri $b $a; 77 | 78 | There are minor differences in the first line, if we compare to the logs 79 | generated by command L: the C<"(post filter)"> addition. 80 | In the end of the line, URL decoding has successfully executed as we wish. 81 | C<"hello%20world"> is decoded as C<"hello world">. 82 | 83 | The last two lines of debug log: 84 | 85 | :text 86 | [debug] 11167#0: *1 http script copy: "!" 87 | [debug] 11167#0: *1 http script set $c 88 | 89 | They are generated by the last L command 90 | 91 | :nginx 92 | set $c "$b!"; 93 | 94 | As you might have noticed, since "variable interpolation" is evaluated 95 | when variable C<$c> is declared and initialized, the debug log starts 96 | with C. In the end of the log it is the string constant 97 | C<"!"> to be concatenated. 98 | 99 | With the log information, it's fairly easy to tell the command execution 100 | ordering: 101 | 102 | :nginx 103 | set $a "hello%20world"; 104 | set_unescape_uri $b $a; 105 | set $c "$b!"; 106 | 107 | It is a perfect match to the statements ordering. 108 | 109 | Just like the commands implemented in module L, command 110 | L implemented in 3rd party module L, can mix 111 | with 112 | commands of module L as well. As introduced in 113 | L, command L supports 114 | computation with given Lua code, and assigns the computed result to a Nginx 115 | variable. As command L does, command L 116 | declares Nginx variable before initialization if the variable does not 117 | exist. 118 | 119 | Let's check a mixed example which comprises command L 120 | and L: 121 | 122 | :nginx 123 | location /test { 124 | set $a 32; 125 | set $b 56; 126 | set_by_lua $c "return ngx.var.a + ngx.var.b"; 127 | set $equation "$a + $b = $c"; 128 | 129 | echo $equation; 130 | } 131 | 132 | Variable C<$a> and C<$b> are initialized with numerical value C<32> 133 | and C<56> respectively, then command L is used 134 | together with given Lua code to compute the sum of C<$a> and C<$b>. 135 | Variable C<$c> is initialized with the computed value. Finally, 136 | variables C<$a>, C<$b> and C<$c> are concatenated by "variable interpolation" 137 | and assigns the result to variable C<$equation>, which is printed by 138 | command L. 139 | 140 | We shall pay attention to a few points in the example: Firstly 141 | Nginx variable C<$VARIABLE> is referenced as L 142 | in Lua code. Secondly, since Nginx variables are strings, the 143 | value of variable C and C are actually strings 144 | C<"32"> and C<"56">, however they are automatically converted to numerical 145 | values by Lua in the addition operation. Thirdly Lua code returns 146 | to Nginx variable C<$c> the computed sum value by statement C. 147 | Finally when Lua code returns, it actually converts the numerical value 148 | back to string. (because string is the only valid value for Nginx variable) 149 | 150 | The actual output meets our expectation: 151 | 152 | :bash 153 | $ curl 'http://localhost:8080/test' 154 | 32 + 56 = 88 155 | 156 | This in fact asserts that command L can mix with 157 | commands implemented by module L, such as L. 158 | 159 | Many other 3rd party modules support the mix with module L 160 | as well. The examples include module L, discussed in 161 | L and module L, which 162 | encrypts sessions. The latter will be studied in detail shortly. 163 | 164 | Since builtin module L is virtually indispensable, it's 165 | a great advantage for the 3rd party module has the caliber of being 166 | mixed with. Truth is, all of those 3rd party modules have adopted 167 | a special technique, which allows the "injection" of their execution 168 | into commands of module C (with the help of a 3rd party module 169 | L developed by Marcus Clyne). For the rest regular 3rd 170 | party modules, which also register their execution in phase C, 171 | their commands are executed separately from module L in runtime. 172 | In fact, it's hardly accurate to tell the commands execution ordering 173 | in between different modules (strictly speaking they are usually executed 174 | in the order of loading, but exception does exist). For example both 175 | modules, C and C register their commands to be executed in phase 176 | C, then it is either the case in which commands of C are executed 177 | followed by C or the other complete way around. Unless it is explicitly 178 | documented, we cannot rely on the uncertain ordering in our configurations. 179 | 180 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder03.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (03) = 2 | 3 | As discussed earlier, unless special techniques are utilized 4 | as module L does, a module can not mix its 5 | commands with L, and expects the correct execution 6 | order. Even if the commands are registered in the C 7 | phase as well. We can demonstrate with some examples. 8 | 9 | 3rd party module L provides a few commands, 10 | which deal with the current request header and response header. 11 | One of them is L. The 12 | command can modify a given request header in C phase 13 | (or add the specific header if it's not available in current 14 | request). As described in its documentation, the command always 15 | executes in the end of C phase: 16 | 17 | :text 18 | phase: rewrite tail 19 | 20 | Being terse though, C means the end of phase C. 21 | 22 | Since it executes in the end of phase C, the implication 23 | is its execution is always after the commands implemented in module 24 | C. Even if it is written at the very beginning: 25 | 26 | :nginx 27 | ? location /test { 28 | ? set $value dog; 29 | ? more_set_input_headers "X-Species: $value"; 30 | ? set $value cat; 31 | ? 32 | ? echo "X-Species: $http_x_species"; 33 | ? } 34 | 35 | As briefly introduced in L, Builtin 36 | variable L<$http_XXX> has the header C for the current 37 | request. We must be careful though, variable <$http_XXX> matches 38 | to the normalized request header, i.e. it lower cases capital 39 | letters and turns minus C<-> into underscore C<_> for the request 40 | header names. Therefore variable C<$http_x_species> can successfully 41 | catches the request header C, which is declared by command 42 | L. 43 | 44 | Because of the statement ordering, we might have mistakenly concluded 45 | header C has the value C when C is requested. 46 | But the actual result is different: 47 | 48 | :bash 49 | $ curl 'http://localhost:8080/test' 50 | X-Species: cat 51 | 52 | Clearly, statement C is executed earlier than 53 | L, although it is 54 | written afterwards. 55 | 56 | This example tells us that commands of different modules 57 | are executed independently from each other, even if they are 58 | all registered in the same processing phase. (unless it is implemented 59 | as module L, whose commands are specifically tuned 60 | with module L). In other words, every processing 61 | phase is further divided into sub-phases by Nginx modules. 62 | 63 | Similar to L, command 64 | L provided by 3rd party module 65 | L execute in the end of C phase as well. 66 | We can verify this: 67 | 68 | :nginx 69 | ? location /test { 70 | ? set $a 1; 71 | ? rewrite_by_lua "ngx.var.a = ngx.var.a + 1"; 72 | ? set $a 56; 73 | ? 74 | ? echo $a; 75 | ? } 76 | 77 | By using Lua code specified by command L 78 | Nginx variable C<$a> is incremented by 1.We might have expected 79 | the result be C<56> if we are looking at the writing sequence.The 80 | actual result is C<57> because command 81 | is always executed after all the L statements. 82 | 83 | :bash 84 | $ curl 'http://localhost:8080/test' 85 | 57 86 | 87 | Admittedly command L has different behavior 88 | than command L, which is discussed in 89 | L. 90 | 91 | Out of sheer curiosity, we shall ask immediately that what would 92 | be execution ordering in between L 93 | and L, since they both ride on C tail? 94 | The answer is : undefined. We must avoid a configuration which relies 95 | on their execution orders. 96 | 97 | Nginx phase C is a rather early processing phase. Usually 98 | commands registered in this phase execute various rewrite tasks on 99 | the request (for example rewrite the URL or the URL parameters), 100 | the commands might also declare and initialize Nginx variables which 101 | are needed in the subsequent handling. Certainly, one cannot 102 | forbid others to complicate themselves by checking the request body, 103 | or visit a database etc. After all, command like L 104 | offers the caliber to stuff in any potentially mind twisted Lua code. 105 | 106 | After phase C, Nginx has another phase called C. 107 | The commands provided by 3rd party module L, which is 108 | discussed in L, execute in phase C. 109 | Commands registered in C phase mostly carry out ACL functionalities, 110 | such as guarding user clearance, checking user origins, examining source 111 | IP validity etc. 112 | 113 | For example command L and L provided 114 | by builtin module L can control which IP addresses have the 115 | privileges to visit, or which IP addresses are rejected: 116 | 117 | :nginx 118 | location /hello { 119 | allow 127.0.0.1; 120 | deny all; 121 | 122 | echo "hello world"; 123 | } 124 | 125 | Location C allows visit from localhost (IP address C<127.0.0.1>) 126 | and reject requests from all other IP addresses (returns http error C<403>) 127 | The rules defined by L commands are asserted in the writing 128 | sequence. Once one rule is matched, the assertion stops and all the rest 129 | L or L commands are ignored. If no rule 130 | is matched, handling continues in the following statements. If the matched 131 | rule is L, handing is aborted and error C<403> is returned 132 | immediately. In our example, request issued from localhost matches to 133 | the rule C and handing continues to the other statements, 134 | however request issued from every other IP addresses will match rule C 136 | handling is therefore aborted and error C<403> is returned. 137 | 138 | We can give it a test, by sending request from localhost: 139 | 140 | :bash 141 | $ curl 'http://localhost:8080/hello' 142 | hello world 143 | 144 | If request is sent from another machine (suppose Nginx runs on IP 145 | C<192.168.1.101> ) we have: 146 | 147 | :bash 148 | $ curl 'http://192.168.1.101:8080/hello' 149 | 150 | 403 Forbidden 151 | 152 |

403 Forbidden

153 |
nginx
154 | 155 | 156 | 157 | By the way, module L supports the "CIDR notation" to designate 158 | a sub-network. For example C<169.200.179.4/24> represents the sub-network 159 | which has the routing prefix C<169.200.179.0> (or subnet mask C<255.255. 160 | 255.0>) 161 | 162 | Because commands of module L execute in C phase, 163 | and phase C is behind C phase. So for those commands we 164 | have 165 | been discussing, regardless of the writing order they always execute in 166 | C phase, which is earlier than L or L. 167 | Keep this in mind, we shall try our best to keep the writing and execution 168 | order 169 | consistent. 170 | 171 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder05.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (05) = 2 | 3 | C is by all means the most significant phase in Nginx's 4 | request handling, because commands running in the phase have the 5 | responsibility to generate "content" and output HTTP response. 6 | Because of its importance, Nginx has a rich set of commands running 7 | in it. The commands include L, L, 8 | L, L, 9 | L, which were discussed in 10 | L, L, 11 | L and L 12 | respectively. 13 | 14 | C is a phase which runs later than C and C. 15 | Therefore its commands always execute in the end when they are used 16 | together with commands of C and C. 17 | 18 | :nginx 19 | location /test { 20 | # rewrite phase 21 | set $age 1; 22 | rewrite_by_lua "ngx.var.age = ngx.var.age + 1"; 23 | 24 | # access phase 25 | deny 10.32.168.49; 26 | access_by_lua "ngx.var.age = ngx.var.age * 3"; 27 | 28 | # content phase 29 | echo "age = $age"; 30 | } 31 | 32 | This is a perfect example, in which commands are executed 33 | in an exact sequence as they are written. The testing result 34 | matches to our expectations too. 35 | 36 | :bash 37 | $ curl 'http://localhost:8080/test' 38 | age = 6 39 | 40 | In fact, the commands' writing order can be completely shuffled 41 | and it won't have any impact to their execution sequence. 42 | Command L, which is implemented by module 43 | L, executes in C phase. Command 44 | L from module L executes in 45 | the end of C phase. Command L from 46 | module L executes in C phase. Command 47 | L from module L executes in 48 | the end of C phase. Finally, our favorite command 49 | L, implemented by module L, executes 50 | in C phase. 51 | 52 | The example also demonstrates the collaborating in between commands 53 | running on each different Nginx phase. In the process, Nginx variable 54 | is the data carrier interconnecting commands and modules. The execution 55 | order of these commands is largely decided by the phase each applies 56 | to. 57 | 58 | As matter of fact, multiple commands from different modules 59 | could coexist in phase C and C. As the example shows, 60 | command L and command L 61 | both belong to phase C. Command L 62 | and command L both belong to phase C. 63 | However it is not the same story for phase C. 64 | 65 | Most modules, when they implement commands for phase C, they 66 | are actually inserting "content handler" for the current C 67 | directive, however there can be one and only one "content handler" 68 | for a C. So only one module could beat the rest when multiple 69 | modules are contending the role. Consider following problematic example: 70 | 71 | :nginx 72 | ? location /test { 73 | ? echo hello; 74 | ? content_by_lua 'ngx.say("world")'; 75 | ? } 76 | 77 | Command L from module L and command 78 | L from module L both execute in 79 | phase C. But only one of them could successfully become 80 | "content handler": 81 | 82 | :bash 83 | $ curl 'http://localhost:8080/test' 84 | world 85 | 86 | Our test indicates, that the winner is L 87 | although it is written afterwards, and command L 88 | never really has a chance to run. We cannot be assured which module 89 | wins in the circumstance. For example, module L wins and 90 | the output becomes C if we swap the L 91 | and L statements. So we shall avoid to use multiple 92 | commands for phase C, if the commands are implemented by 93 | different modules. 94 | 95 | The example can be modified by replacing command L 96 | with command L and we will get what we need: 97 | 98 | :nginx 99 | location /test { 100 | echo hello; 101 | echo world; 102 | } 103 | 104 | Again test proves: 105 | 106 | :bash 107 | $ curl 'http://localhost:8080/test' 108 | hello 109 | world 110 | 111 | We can use multiple L commands, there is no problem 112 | with this because they all belong to module L. Module 113 | L regulates the execution ordering of them. Be careful 114 | though, not every module supports the commands being executed 115 | multiple times within one C. Command L 116 | for an instance, can be used only once, so following example 117 | is incorrect: 118 | 119 | :nginx 120 | ? location /test { 121 | ? content_by_lua 'ngx.say("hello")'; 122 | ? content_by_lua 'ngx.say("world")'; 123 | ? } 124 | 125 | Nginx dumps error for the configuration: 126 | 127 | :text 128 | [emerg] "content_by_lua" directive is duplicate ... 129 | 130 | The correct way of doing it is: 131 | 132 | :nginx 133 | location /test { 134 | content_by_lua 'ngx.say("hello") ngx.say("world")'; 135 | } 136 | 137 | Instead of using twice the L command in 138 | C, the approach is to call function L twice 139 | in the Lua code, which is executed by command L 140 | 141 | Similarly, command L from module L 142 | cannot coexist with command L within one C 143 | because they both execute in C phase. Many Nginx newbies 144 | make following mistake: 145 | 146 | :nginx 147 | ? location /test { 148 | ? echo "before..."; 149 | ? proxy_pass http://127.0.0.1:8080/foo; 150 | ? echo "after..."; 151 | ? } 152 | ? 153 | ? location /foo { 154 | ? echo "contents to be proxied"; 155 | ? } 156 | 157 | The example tries to output strings C<"before..."> and 158 | C<"after..."> with command L before and 159 | after module L returns its content. However 160 | only one module could execute in C. The test 161 | indicates module L wins and command 162 | L from module L never runs 163 | 164 | :bash 165 | $ curl 'http://localhost:8080/test' 166 | contents to be proxied 167 | 168 | To implement what the example had wanted to, we shall 169 | use two other commands provided by module L, 170 | L and L: 171 | 172 | :nginx 173 | location /test { 174 | echo_before_body "before..."; 175 | proxy_pass http://127.0.0.1:8080/foo; 176 | echo_after_body "after..."; 177 | } 178 | 179 | location /foo { 180 | echo "contents to be proxied"; 181 | } 182 | 183 | Test tells we make it: 184 | 185 | $ curl 'http://localhost:8080/test' 186 | before... 187 | contents to be proxied 188 | after... 189 | 190 | The reason commands L and 191 | L could coexist with other modules 192 | in C phase, is they are not "content handler" but 193 | "output filter" of Nginx. Back in L when 194 | we examine the "debug log" generated by command L 195 | , we've learnt Nginx calls its "output filter" whenever 196 | Nginx outputs data. So that module L takes the 197 | advantage of it to modify content generated by module L 198 | (by adding surrounding content). We shall point out though, 199 | "output filter" is not one of those 11 phases mentioned in 200 | L (many phases could trigger "output filter" when 201 | they output data). Still it's perfectly all right to document 202 | commands L and L 203 | as following: 204 | 205 | phase: output filter 206 | 207 | It means the command executes in "output filter". 208 | 209 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder06.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (06) = 2 | 3 | We've learnt in L that when a command executes 4 | in C phase for a specific C, it usually 5 | means its Nginx module registers a "content handler" for 6 | the C. However, what happens if no module registers 7 | its command as "content handler" for phase C ? Who 8 | will be taking the glory of generate content and output responses ? 9 | The answer is the static resource module, which maps the request 10 | URI to the file system. Static resource module only comes into play 11 | when there is none "content handler", otherwise it hands off the 12 | duty to "content handler". 13 | 14 | Typically Nginx has three static resource modules for the C 15 | phase (unless one or more of those modules are disabled explicitly, 16 | or some other conflicting modules are enabled when Nginx is built) 17 | The three modules, in the order of their execution order, are 18 | L module, L module and L module. 19 | Let's discuss them one by one. 20 | 21 | Module L and L only apply to those 22 | request URI, which ends with C. For the other request URI 23 | which does not end with C, both modules ignore them and let 24 | the following C phase module handle. Module L 25 | however, has an exact opposite strategy. It ignores the request 26 | URI which ends with C and handles the rest. 27 | 28 | Module L mainly looks for a specific home page file, 29 | such as F or F in the file system. For 30 | example: 31 | 32 | :nginx 33 | location / { 34 | root /var/www/; 35 | index index.htm index.html; 36 | } 37 | 38 | When address C is requested, Nginx looks for file 39 | F and F (in this order) in a path 40 | in the file system. The path is specified by command L. 41 | If file F exists, Nginx jumps internally to 42 | location C; if it does not exist and file F 43 | exists, Nginx jumps internally to location C. If 44 | file F does not exist either, and handling 45 | is transferred to the other module which executes it commands 46 | in phase C. 47 | 48 | We have learnt in L, commands 49 | L and L can trigger 50 | "internal redirects" as well. The jump modifies the request URI, 51 | and looks for the corresponding C directive for 52 | subsequent handling. In the process, phases C, 53 | C and C are reiterated for the C. 54 | The "internal redirect" is different from the "external redirect" 55 | defined by HTTP response code 302 and 301, client browser 56 | won't update its URI addresses. Therefore as soon as internal 57 | jump occurs when module L finds the files specified 58 | by command L, the net effect is like client 59 | would have been requesting the file's URI at the very beginning. 60 | 61 | We can check following example to witness the "internal redirect" 62 | triggered by module L, when it finds the needed file. 63 | 64 | :nginx 65 | location / { 66 | root /var/www/; 67 | index index.html; 68 | } 69 | 70 | location /index.html { 71 | set $a 32; 72 | echo "a = $a"; 73 | } 74 | 75 | We need to create an empty file F under the path 76 | F, and make sure the file is readable for the Nginx 77 | worker process. Then we could send request to C: 78 | 79 | :bash 80 | $ curl 'http://localhost:8080/' 81 | a = 32 82 | 83 | What happened ? Why the output is not the content of 84 | file F (which shall be empty) ? Firstly Nginx uses 85 | directive C to handle original C request, then 86 | module L executes in C phase, and it finds 87 | file F under path F. At this moment, it 88 | triggers an "internal redirect" to location C. 89 | 90 | So far so good. But here comes the surprises ! When Nginx 91 | looks for C directive which matches to C, 92 | C has a higher priority than C. 93 | This is because Nginx uses "longest matched substring" semantics 94 | to match C directives to request URI's prefix. When 95 | directive is chosen, phases C, 96 | C and C are reiterated, and eventually it 97 | outputs C
. 98 | 99 | What if we remove file F in the example, and 100 | request to C again ? The answer is error C<403 Forbidden>. Why? 101 | When module L cannot find the file specified by command 102 | L (F in here), it transfers the 103 | handling to the following module which executes in C. 104 | But none of those following modules can fulfill the request, Nginx 105 | bails out and dumps us error. Meanwhile it logs the error in Nginx 106 | error log: 107 | 108 | :text 109 | [error] 28789#0: *1 directory index of "/var/www/" is forbidden 110 | 111 | The meaning of C is to generate "indexes". Usually 112 | this implies to generate a web page, which lists every file and sub 113 | directories under path C. If we use module L 114 | right after L, it can generate such a page just like 115 | what we need. Now let's modify the example a little bit: 116 | 117 | :nginx 118 | location / { 119 | root /var/www/; 120 | index index.html; 121 | autoindex on; 122 | } 123 | 124 | When C is requested again meanwhile file F 125 | is kept missing. A nice html page is generated: 126 | 127 | :bash 128 | $ curl 'http://localhost:8080/' 129 | 130 | Index of / 131 | 132 |

Index of /


../
133 |     cgi-bin/  08-Mar-2010 19:36   -
134 |     error/      08-Mar-2010 19:36   -
135 |     htdocs/    05-Apr-2010 03:55   -
136 |     icons/      08-Mar-2010 19:36   -
137 |     

138 | 139 | 140 | The page shows there are a few subdirectories under my F. 141 | They are F, F, F and F. The output 142 | might be different if you have tried by yourself. 143 | 144 | Again, if file F does exist, module L 145 | will trigger "internal redirect", and module L will not have 146 | a chance to execute, you may test it yourself too. 147 | 148 | The "goal keeper" module executed in phase C is L. 149 | which is also used intensively. The module serves the static files, including 150 | the static resources of a web site, such as static F<.html> files, static 151 | F<.css> files, static F<.js> files and static image files etc. Although 152 | C could trigger an "internal redirect" to the specified home page, 153 | but the actual output task (takes the file content as response, and marks 154 | the corresponding response headers) is carried out by module L. 155 | 156 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder07.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (07) = 2 | 3 | Let's check an example in which module L serves 4 | disk files, with following configuration snippet: 5 | 6 | :nginx 7 | location / { 8 | root /var/www/; 9 | } 10 | 11 | Meanwhile two files are created under F. One 12 | file is named F and its content contains one line of 13 | text C. Another file is named F 14 | and its content contains one line of text C. Again 15 | be aware of the files' privileges and make sure they are 16 | readable by Nginx worker process. 17 | 18 | Now we send requests to the files' corresponding URI: 19 | 20 | :bash 21 | $ curl 'http://localhost:8080/index.html' 22 | this is my home 23 | 24 | $ curl 'http://localhost:8080/hello.html' 25 | hello world 26 | 27 | As we can see, the created file contents are sent as outputs. 28 | 29 | We can examine what is happening here: C does not 30 | have any command to execute in phase C, therefore no 31 | module has registered a "content handler" in the C. 32 | The handling thus falls to the three static resource modules 33 | which are the last resorts of phase C. The former two 34 | modules L and L notices that the request 35 | URI does not end with C so they hand off immediately to module 36 | L, which runs in the end. According to the "document 37 | root" specified by command L, module L 38 | maps the request URIs C and C to disk 39 | files F and F respectively. 40 | As both files can be found, their content are outputted as response, 41 | meanwhile response header C, C and 42 | C are accordingly indicated. 43 | 44 | To verify module L has executed, we could enable the 45 | "debug log" introduced in L. Again we send request 46 | to C and Nginx error log will contain following 47 | debug information: 48 | 49 | :text 50 | [debug] 3033#0: *1 http static fd: 8 51 | 52 | This line is generated by module L. Its meaning is " 53 | outputting static resource whose file handle is C<8>". Of course 54 | the numerical file handle changes every time, and the line is 55 | only a typical output in my setup. To be reminded, builtin module 56 | L could generate the same debug info as well, 57 | by default it is not enabled though, which will be discussed later. 58 | 59 | Command L only declares a "document root", it does not 60 | enables the L module. The module is as matter of fact, always 61 | enabled already, but it might not have the chance to execute. This is 62 | entirely up to the other modules, which execute earlier in C phase. 63 | Module L execute only when all of them have "gave up". To 64 | prove this, check following blank C definition: 65 | 66 | :nginx 67 | location / { 68 | } 69 | 70 | Because there is no L command, Nginx computes 71 | a default "document root" when the location is requested. The default 72 | shall be the F subdirectory under "configure prefix". For example 73 | suppose our "configure prefix" is F, the default "document 74 | root" is F. 75 | 76 | So who decides "configure prefix" ? Actually it the Nginx root 77 | directory when it is installed (or the value of C<--prefix> option of 78 | script F<./configure> when Nginx is built). If Nginx is installed into 79 | C, "configure prefix" is C and 80 | default "document root" is therefore C. Certainly 81 | a command line option C<--prefix> can be given when Nginx is started, 82 | to change the "configure prefix" (so that we can easily test multiple 83 | setups). Suppose Nginx is started as following: 84 | 85 | :bash 86 | nginx -p /home/agentzh/test/ 87 | 88 | For this server, its "configure prefix" becomes F 89 | and its "document root" becomes F. The "configure 90 | prefix" not only determines "document root", it actually determines 91 | the way many relational path resolutes to absolute path in Nginx configuration. 92 | We will encounter many examples which reference "configure prefix". 93 | 94 | In fact there is a simple way of telling current "document root", which 95 | is 96 | to request a non-existed file, Such as: 97 | 98 | :nginx 99 | $ curl 'http://localhost:8080/blah-blah.txt' 100 | 101 | 404 Not Found 102 | 103 |

404 Not Found

104 |
nginx
105 | 106 | 107 | 108 | Naturally, the C<404> error page is returned. Again when we check 109 | Nginx error log, we shall have following error message: 110 | 111 | :text 112 | [error] 9364#0: *1 open() "/home/agentzh/test/html/blah-blah.txt" failed (2: No such file or directory) 113 | 114 | The error message is printed by module L, since it cannot 115 | find a file F in its corresponding path. And because 116 | the error message contains the absolute path, which L 117 | attempts to open with, it's quite obvious that current "document root" 118 | is F. 119 | 120 | Many newbies might take it for granted that error C<404> is caused when 121 | the needed C does not exist. The former example tells us, 122 | C<404> error could be returned even if the needed C is configured 123 | and matched. This is because error C<404> means the non-existence of an 124 | abstract "resource", not the specific C. 125 | 126 | Another frequent mistake is missing the command for phase C, 127 | when they actually don't expect the default static modules to come into 128 | play,for example: 129 | 130 | :nginx 131 | location /auth { 132 | access_by_lua ' 133 | -- a lot of Lua code omitted here... 134 | '; 135 | } 136 | 137 | Apparently, only commands for phase C are given for F, 138 | which is L. And it has no commands for phase C. 139 | So when C is requested, the Lua code specified in C phase 140 | will execute, then the static resource will be served in phase C 141 | by module L. Since it actually looks for the file F 142 | on the disk 143 | normally it dumps a C<404> error unless we are luckily and file F 144 | is created on the corresponding path. So the thumb of rule, when error 145 | C<404> is encountered under no static resource circumstances, we shall 146 | first check if the C has properly configured its commands for 147 | phase C, the commands can be L, 148 | L and L etc. In fact, Nginx error 149 | log 150 | F could only give very confusing message for the case. As the 151 | ones below, which is found for the above example: 152 | 153 | :text 154 | [error] 9364#0: *1 open() "/home/agentzh/test/html/auth" failed (2: No such file or directory) 155 | 156 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder08.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (08) = 2 | 3 | So far we have addressed in detail C, 4 | C and C, which are also the most 5 | frequently encountered phases in Nginx request processing. 6 | We have learnt many Nginx modules and their commands that 7 | execute in those phases, and it's clear to us that the 8 | commands' execution order is directly decided by the phase 9 | they are running in. Understanding the phase is our keynote 10 | for correct configuration which orchestrates various Nginx 11 | modules. Therefore let's cover the rest phases we've not met. 12 | 13 | As mentioned in L, altogether there can be 11 14 | phases when Nginx handles a request. In their execution order 15 | the phases are C, C, C, 16 | C, C, C, C, 17 | C, C, C, and finally C. 18 | 19 | Phase C is the very first, commands registered in 20 | this phase execute right after Nginx has processed the request 21 | headers. Similar to phase C we've learnt earlier, 22 | C supports hooks by Nginx modules. Built-in module 23 | L is an example, it hooks its handler in C 24 | phase, and forcefully rewrite the request's original address 25 | as the value of a specific request header. The following case 26 | illustrates L module and its commands 27 | L, L. 28 | 29 | :nginx 30 | server { 31 | listen 8080; 32 | 33 | set_real_ip_from 127.0.0.1; 34 | real_ip_header X-My-IP; 35 | 36 | location /test { 37 | set $addr $remote_addr; 38 | echo "from: $addr"; 39 | } 40 | } 41 | 42 | The configuration tells Nginx to forcefully rewrite 43 | the original address of every request coming from C<127.0.0.1> 44 | to be the value of the request header C. Meanwhile 45 | it uses the built-in variable L to output 46 | the request's original address, so that we know if the 47 | rewrite is successful. 48 | 49 | First we send a request to C from localhost: 50 | 51 | :bash 52 | $ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test 53 | from: 1.2.3.4 54 | 55 | The test utilizes C<-H> option provided by curl, the option 56 | incorporates an extra HTTP header C in the 57 | request. As we can tell, variable L 58 | has become C<1.2.3.4> in C phase, the value comes 59 | from the request header C. So when does Nginx rewrite 60 | the request's original address ? yes it's in the C 61 | phase. Since phase C is far behind phase C, 62 | when command L reads variable 63 | L, its value has already been rewritten 64 | in C phase. 65 | 66 | If however, the request sent from localhost to C does not 67 | have a C header or the header value is an invalid IP address, 68 | Nginx will not modify the original address. For example: 69 | 70 | :bash 71 | $ curl localhost:8080/test 72 | from: 127.0.0.1 73 | 74 | $ curl -H 'X-My-IP: abc' localhost:8080/test 75 | from: 127.0.0.1 76 | 77 | If a request is sent from another machine to C, it original 78 | address won't be overwritten by Nginx either, even if it has a perfect 79 | C header. It is because our previous case marks explicitly 80 | with command L, that the rewriting only 81 | occurs for the requests coming from C<127.0.0.1>. This filtering 82 | mechanism protect Nginx from malicious requests sent by untrusted sources. 83 | As you might have expected, command L can 84 | designate a IP subnet (by using CIDR notation introduced earlier in 85 | L). Besides, command L can 86 | be used multiple times so that we can setup multiple trusted sources, 87 | below is an example: 88 | 89 | :nginx 90 | set_real_ip_from 10.32.10.5; 91 | set_real_ip_from 127.0.0.0/24; 92 | 93 | You might be asking, what's the benefit module L brings to 94 | us? 95 | Why would we rewrite a request's original address ? The answer is: when 96 | the request has come through one or more HTTP proxies, the module becomes 97 | very handy. When a request is forwarded by a proxy, its original address 98 | will become the proxy server's IP address, consequently Nginx and the services 99 | running on it will no longer have the actual source. However, we could 100 | let proxy server record the original address in a specific header (such 101 | as 102 | C) and recover it in Nginx, so that its subsequent processing 103 | (and 104 | the services running on Nginx) will take the request as if it comes right 105 | from its original address and the proxies in between are transparent. For 106 | this exact purpose, module L needs hook handlers in the first 107 | phase, the C phase, so the rewriting occurs as early as possible. 108 | 109 | Behind C is the C phase. We briefly mentioned 110 | in L, when module L and its commands are configured 111 | in C directive, they basically execute in C phase. 112 | We have an example below: 113 | 114 | :nginx 115 | server { 116 | listen 8080; 117 | 118 | location /test { 119 | set $b "$a, world"; 120 | echo $b; 121 | } 122 | 123 | set $a hello; 124 | } 125 | 126 | Attention the C statement is put in C 127 | directive, so it runs in C phase, which runs 128 | earlier than C phase. Therefore statement 129 | C in C directive is executed 130 | afterwards and it obtains the correct $a value: 131 | 132 | :bash 133 | $ curl localhost:8080/test 134 | hello, world 135 | 136 | Since phase C executes later than 137 | C phase, command L in C 138 | directive always runs later than module L, 139 | which rewrites the request's original address, example: 140 | 141 | :nginx 142 | server { 143 | listen 8080; 144 | 145 | set $addr $remote_addr; 146 | 147 | set_real_ip_from 127.0.0.1; 148 | real_ip_header X-Real-IP; 149 | 150 | location /test { 151 | echo "from: $addr"; 152 | } 153 | } 154 | 155 | Send request to C we have: 156 | 157 | :bash 158 | $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test 159 | from: 1.2.3.4 160 | 161 | Again, command L is written in front of 162 | commands of L, its actual execution is only 163 | afterwards. So when command L assigns 164 | variable C<$addr> in C phase, the variable 165 | L has been overwritten. 166 | -------------------------------------------------------------------------------- /en/02-NginxDirectiveExecOrder09.tut: -------------------------------------------------------------------------------- 1 | = Nginx directive execution order (09) = 2 | 3 | Right after C is the phase C. 4 | This phase does not allow Nginx modules to register their 5 | handlers, instead it is a phase when Nginx core matches 6 | the current request to the C directives. It means 7 | a request is not catered by any C directive until 8 | it reaches C. Apparently, for phases like 9 | C and C, the effective commands 10 | are those which get specified only in C directives 11 | and their outer directives, because the two phases are 12 | executed earlier than C. This explains that 13 | commands of module L are executed in phase 14 | C only if they are written within C 15 | directive. Similarly, the former examples configure the 16 | commands of module L in C directive 17 | to make sure the handlers registered in C phase 18 | could function correctly. 19 | 20 | As soon as Nginx matches a C directive in the 21 | C phase, it prints a debug log in the error 22 | log file. Let's check following example: 23 | 24 | :nginx 25 | location /hello { 26 | echo "hello world"; 27 | } 28 | 29 | If Nginx enables the "debug log", a debug log can be 30 | captured in file F whenever interface C 31 | is requested. 32 | 33 | :bash 34 | $ grep 'using config' logs/error.log 35 | [debug] 84579#0: *1 using configuration "/hello" 36 | 37 | For the purpose of convenience, the log's time stamp has been 38 | omitted. 39 | 40 | After phase C, it is our old buddy C. 41 | Since Nginx already matches the request to a specific 42 | C directive, starting from this phase, 43 | commands written within C directives are 44 | becoming effective. As illustrated earlier, commands of 45 | module L are executed in C phase 46 | when they are written in C directives. Likewise, 47 | commands of module L and module L 48 | (L and L) are 49 | also executed in phase C. 50 | 51 | After C, it is the C phase. Just 52 | like C, this phase does not allow Nginx modules 53 | to register their handlers either, instead it carries out 54 | the needed "internal redirects" by Nginx core (if this has been 55 | requested in C phase). We have addressed the "internal 56 | jump" concept in L, and demonstrated how to issue 57 | the "internal redirect" with command L or 58 | command L. However, let's focus on 59 | command L for the moment since command 60 | L is executed in C phase and 61 | becomes irrelevant to C, the former draws greater 62 | interest because it executes in C phase. Back to 63 | our example in L: 64 | 65 | :nginx 66 | server { 67 | listen 8080; 68 | 69 | location /foo { 70 | set $a hello; 71 | rewrite ^ /bar; 72 | } 73 | 74 | location /bar { 75 | echo "a = [$a]"; 76 | } 77 | } 78 | 79 | The command L found in directive 80 | C, rewrites the URI of current request 81 | as C unconditionally, meanwhile, it issues an 82 | "internal redirect" and execution continues from C. 83 | What ultimately intrigues us, is the magical bits and pieces 84 | of "internal redirect" mechanism, "internal redirect" effectively 85 | rewinds our processing of current request back to the 86 | C phase, so that the C directives 87 | can be matched again to the request URI, which usually 88 | has been rewritten. Just like our example, whose URI is 89 | rewritten as C by command L, 90 | the C directive is matched and execution 91 | repeats the C phase thereafter. 92 | 93 | It might not be obvious, that the actual act of rewinding to 94 | C does not occur in C phase, instead 95 | it occurs in the following C phase. Command 96 | L in the former example, simply requests 97 | Nginx to issue an "internal redirect" in its C 98 | phase. This design is usually questioned by Nginx beginners 99 | and they tend to come up with an idea to execute the "internal 100 | jump" directly by command L. The answer 101 | however, is fairly simple. The design allows URI be rewritten 102 | multiple times in the C directive,which is matched at 103 | the very beginning. Such as: 104 | 105 | :nginx 106 | location /foo { 107 | rewrite ^ /bar; 108 | rewrite ^ /baz; 109 | 110 | echo foo; 111 | } 112 | 113 | location /bar { 114 | echo bar; 115 | } 116 | 117 | location /baz { 118 | echo baz; 119 | } 120 | 121 | The request URI has been rewritten twice in C 122 | directive: firstly it becomes C, secondly it becomes 123 | C. As the net effect of both L 124 | statements, "internal redirect" occurs only once in C 125 | phase. If it would have executed the "internal redirect" at 126 | the first URI rewrite, the second would have no chance to be 127 | executed since processing would have left current C 128 | directive. To prove this we send a request to C: 129 | 130 | :bash 131 | $ curl localhost:8080/foo 132 | baz 133 | 134 | It can be asserted from the output, the actual jump is from 135 | C to C. We could further prove this by enabling 136 | Nginx "debug log" and interrogate the debug log 137 | generated in C phase for the matched: 138 | 139 | :bash 140 | $ grep 'using config' logs/error.log 141 | [debug] 89449#0: *1 using configuration "/foo" 142 | [debug] 89449#0: *1 using configuration "/baz" 143 | 144 | Clearly, for the specific request, Nginx only matches 145 | two C directives: C and C, and "internal 146 | jump" occurs only once. 147 | 148 | Quite obviously, if command C is used 149 | to rewrite the request URI in C directive, there won't 150 | be any "internal redirects", this is because the URI rewrite 151 | is happening in C phase, which gets executed 152 | earlier than C phase that matches in between 153 | the C directives. We can check the example below: 154 | 155 | :nginx 156 | server { 157 | listen 8080; 158 | 159 | rewrite ^/foo /bar; 160 | 161 | location /foo { 162 | echo foo; 163 | } 164 | 165 | location /bar { 166 | echo bar; 167 | } 168 | } 169 | 170 | In the example, every request whose URI starts with C 171 | gets its URI rewritten as C. The rewriting occurs 172 | in C phase, and the request has never been 173 | matched to any C directive. Only afterwards Nginx 174 | executes the matches in C phase. So if we send 175 | a request to C, C never gets matched 176 | because when the match occurs in C phase, the 177 | request URI has been rewritten as C. So C 178 | is the one and the only one matched directive. Actual output 179 | illustrates this: 180 | 181 | $ curl localhost:8080/foo 182 | bar 183 | 184 | Again let's check Nginx "debug log": 185 | 186 | :bash 187 | $ grep 'using config' logs/error.log 188 | [debug] 92693#0: *1 using configuration "/bar" 189 | 190 | As we can tell, Nginx altogether finishes once the C 191 | match, and there is no "internal redirect". 192 | -------------------------------------------------------------------------------- /image/value-container.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openresty/nginx-tutorials/9f7242b60f9f95bd840ec54388f83a17e3407ee3/image/value-container.jpg -------------------------------------------------------------------------------- /image/value-container.ora: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openresty/nginx-tutorials/9f7242b60f9f95bd840ec54388f83a17e3407ee3/image/value-container.ora -------------------------------------------------------------------------------- /tutorial-simple.css: -------------------------------------------------------------------------------- 1 | div.thumb img.thumbimage { 2 | background-color: #FFFFFF; 3 | } 4 | 5 | html .thumbimage { 6 | border: 1px solid #CCCCCC; 7 | } 8 | 9 | img { 10 | vertical-align: middle; 11 | } 12 | 13 | div.thumbinner { 14 | background-color: #F9F9F9; 15 | border: 1px solid #CCCCCC; 16 | font-size: 94%; 17 | overflow: hidden; 18 | padding: 3px !important; 19 | text-align: center; 20 | min-width: 100px; 21 | } 22 | 23 | .thumbcaption { 24 | text-align: left; 25 | border: medium none; 26 | font-size: 94%; 27 | line-height: 1.4em; 28 | padding: 3px !important; 29 | } 30 | 31 | div.tright { 32 | margin: 0.5em 1.3em 1.3em 1.4em; 33 | } 34 | 35 | div.thumb { 36 | background-color: transparent; 37 | width: auto; 38 | } 39 | 40 | div.tright, div.floatright, table.floatright { 41 | clear: right; 42 | float: right; 43 | } 44 | 45 | .magnify { 46 | float: right; 47 | background: none repeat scroll 0 0 transparent !important; 48 | border: medium none !important; 49 | direction: ltr; 50 | } 51 | -------------------------------------------------------------------------------- /tutorial.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 1em; 3 | } 4 | 5 | h1, h2, h3, h4, h5, h6 { 6 | border-bottom: 1px solid #aaa; 7 | padding-top: .5em; 8 | padding-bottom: .17em; 9 | margin: 0; 10 | } 11 | 12 | ul { 13 | line-height: 1.4em; 14 | } 15 | li { 16 | margin-bottom: .1em; 17 | } 18 | 19 | h1.page-title { 20 | margin-top: 0; 21 | margin-bottom: .1em; 22 | line-height: 1.2em; 23 | padding-bottom: 0; 24 | } 25 | 26 | article h1 { 27 | margin-bottom: .6em; 28 | font-size: 150%; 29 | } 30 | 31 | code { 32 | font-family: monospace,Courier; 33 | color: black; 34 | font-size: .9em; 35 | background-color: #eee; 36 | padding: 0 2px; 37 | } 38 | 39 | .content { 40 | padding: 1.5em; 41 | } 42 | 43 | code.block { 44 | display: block; 45 | line-height: 1.3em; 46 | padding: 1em; 47 | border: 1px dashed #2f6fab; 48 | margin: 1em 0px; 49 | background-color: #f9f9f9; 50 | } 51 | 52 | @media screen { 53 | @font-face{font-family:'Octicons Regular';src:url("https://a248.e.akamai.net/assets.github.com/assets/octicons/octicons-regular-webfont-a20448aa13cf370506c8a675c4fc0efba750115c.eot");src:url("https://a248.e.akamai.net/assets.github.com/assets/octicons/octicons-regular-webfont-a20448aa13cf370506c8a675c4fc0efba750115c.eot#iefix") format("embedded-opentype"),url("https://a248.e.akamai.net/assets.github.com/assets/octicons/octicons-regular-webfont-46d3d8ac303cad13c0b2006f8e8149a472d9d4e5.woff") format("woff"),url("https://a248.e.akamai.net/assets.github.com/assets/octicons/octicons-regular-webfont-93f33ad81ec64b5f0980ad57a53cf82e8445cab9.ttf") format("truetype"),url("https://a248.e.akamai.net/assets.github.com/assets/octicons/octicons-regular-webfont-f963ccb8f0402b76e11107147fe5b2e275319280.svg#newFontRegular") format("svg");font-weight:normal;font-style:normal} 54 | 55 | .toc { 56 | width: 200px; 57 | border: 1px solid #aaa; 58 | background-color: #f9f9f9; 59 | padding: 5px; 60 | font-size: 95%; 61 | position: absolute; left: 10px; top: 100px; 62 | font-size: .8em; 63 | } 64 | .toc ul { 65 | padding-left: 20px; 66 | } 67 | .content { 68 | margin-left: 210px; 69 | } 70 | 71 | .content .con-title .anchor { 72 | opacity: 0; 73 | font-family: 'Octicons Regular'; 74 | color: #4787ed; text-decoration: none; font-size: .8em; 75 | } 76 | .content .con-title:hover .anchor { 77 | opacity: 1; 78 | } 79 | } 80 | 81 | div.thumb img.thumbimage { 82 | background-color: #FFFFFF; 83 | } 84 | 85 | html .thumbimage { 86 | border: 1px solid #CCCCCC; 87 | } 88 | 89 | img { 90 | vertical-align: middle; 91 | } 92 | 93 | div.thumbinner { 94 | background-color: #F9F9F9; 95 | border: 1px solid #CCCCCC; 96 | font-size: 94%; 97 | overflow: hidden; 98 | padding: 3px !important; 99 | text-align: center; 100 | min-width: 100px; 101 | } 102 | 103 | .thumbcaption { 104 | text-align: left; 105 | border: medium none; 106 | font-size: 94%; 107 | line-height: 1.4em; 108 | padding: 3px !important; 109 | } 110 | 111 | div.tright { 112 | margin: 0.5em 1.3em 1.3em 1.4em; 113 | } 114 | 115 | div.thumb { 116 | background-color: transparent; 117 | width: auto; 118 | } 119 | 120 | div.tright, div.floatright, table.floatright { 121 | clear: right; 122 | float: right; 123 | } 124 | 125 | .magnify { 126 | float: right; 127 | background: none repeat scroll 0 0 transparent !important; 128 | border: medium none !important; 129 | direction: ltr; 130 | } 131 | 132 | .backtop-box { 133 | position: fixed; left: 180px; bottom: 20px; 134 | } 135 | .backtop-box b { 136 | display: block; cursor: pointer; 137 | width: 40px; height: 40px; 138 | opacity: 0; 139 | background: #fff;; 140 | text-align: center; 141 | border: 1px solid #aaa; 142 | border-radius: 18px 2px 2px 2px; 143 | -moz-border-radius: 18px 2px 2px 2px; 144 | -webkit-border-radius: 18px 2px 2px 2px; 145 | -o-border-radius: 18px 2px 2px 2px; 146 | -ms-border-radius: 18px 2px 2px 2px; 147 | line-height: 40px; 148 | color: #666; 149 | } 150 | .backtop-box b:hover { 151 | background: #eee; 152 | } 153 | .backtop-box-show b { 154 | opacity: 1; 155 | } 156 | 157 | -------------------------------------------------------------------------------- /utils/fix-html.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use encoding 'utf8'; 6 | 7 | local $/; 8 | my $src = <>; 9 | 10 | if ($src !~ m/^\s*.*?<)(/pre>)}{$1.fmt($2).$3}ges; 14 | $src =~ s{(.*?<)(/code>)}{$1.fmt($2).$3}ges; 15 | $src =~ s!

!


    !gs; 16 | $src =~ s!

    !
      !gs; 17 | $src =~ s!
    !
!gs; 18 | $src =~ s!^\s*

!

    !g; 19 | $src =~ s!^\s*(\p{Han})!

    $1!s; 20 | $src =~ s!href="/!href="http://wiki.nginx.org/!g; 21 | $src =~ s!unescape!\&\#117;nescape!gs; 22 | print $src; 23 | 24 | sub fmt { 25 | my $a = shift; 26 | $a =~ s{>([^><]+)<}{ 27 | my $n = $1; 28 | $n =~ s!\n+!
!gs; 29 | $n =~ s! !\ !gs; 30 | $n =~ s!\%!\%!gs; 31 | '>'. $n . '<' 32 | }gse; 33 | $a; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /utils/fmt.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use encoding 'utf8'; 6 | 7 | my $infile = shift or 8 | die "No input file specified.\n"; 9 | 10 | open my $in, "<:encoding(UTF-8)", $infile 11 | or 12 | die "cannot open $infile for reading: $!\n"; 13 | 14 | my $changes = 0; 15 | 16 | my $res; 17 | while (<$in>) { 18 | if (/^\s|^\s*$/) { # verbatim 19 | next; 20 | } 21 | 22 | my $orig = $_; 23 | 24 | if (/\p{Han}/) { 25 | s/.{39}.*?(?:[ \t”“,:。!?]|\p{Han})/$&\n/gso; 26 | } else { 27 | s/.{70}.*?[ \t,'""!?:;.”“,:。!?]/$&\n/gso; 28 | } 29 | 30 | s/\s*\n\s*/\n/gms; 31 | $changes++ if $orig ne $_; 32 | 33 | } continue { 34 | $res .= $_; 35 | } 36 | 37 | close $in; 38 | 39 | warn "$changes changes\n"; 40 | 41 | if ($changes && $res) { 42 | open my $out, ">:encoding(UTF-8)", $infile or 43 | die "Cannot open $infile for writing: $!\n"; 44 | print $out $res; 45 | close $out; 46 | warn "$infile wrote.\n"; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /utils/gen-html-index-cn.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use Getopt::Std; 9 | 10 | my %opts; 11 | getopts('o:v:', \%opts) or usage(); 12 | 13 | my $outfile = $opts{o}; 14 | my $ver = $opts{v} or usage(); 15 | 16 | my @nums = qw( 17 | 零 一 二 三 四 五 六 七 八 九 18 | 十 十一 十二 十三 十四 十五 十六 十七 十八 十九 19 | 二十 20 | ); 21 | 22 | my $res = <<_EOC_; 23 | 24 | 25 | agentzh 的 Nginx 教程(版本 $ver) 26 | 27 | 42 | 43 |

agentzh 的 Nginx 教程(版本 $ver)

44 |

目录

45 | _EOC_ 46 | 47 | $res .= "
    \n"; 48 | for my $infile (@ARGV) { 49 | (my $base = $infile) =~ s{.*/|\.html$}{}g; 50 | 51 | if ($infile =~ /Foreword(\d+)/) { 52 | my $n = $1; 53 | if ($n eq '01') { 54 | $res .= <<_EOC_; 55 |
  • 缘起
  • 56 | _EOC_ 57 | } elsif ($n eq '02') { 58 | $res .= <<_EOC_; 59 |
  • Nginx 教程的连载计划
  • 60 | _EOC_ 61 | } else { 62 | die "unknown infile: $infile"; 63 | } 64 | 65 | } elsif ($infile =~ /NginxVariables(\d+)/) { 66 | my $num = +$1; 67 | my $n = $nums[$num]; 68 | #$infile =~ s{.*/}{}g; 69 | $res .= <<_EOC_; 70 |
  • Nginx 变量漫谈($n)
  • 71 | _EOC_ 72 | 73 | } elsif ($infile =~ /DirectiveExecOrder(\d+)/) { 74 | my $num = +$1; 75 | my $n = $nums[$num]; 76 | #$infile =~ s{.*/}{}g; 77 | $res .= <<_EOC_; 78 |
  • Nginx 配置指令的执行顺序($n)
  • 79 | _EOC_ 80 | 81 | } else { 82 | die "Unknown file $infile"; 83 | } 84 | } 85 | 86 | $res .= "
\n"; 87 | 88 | for my $infile (@ARGV) { 89 | open my $in, "<:encoding(utf-8)", $infile 90 | or die "Cannot open $infile for reading: $!\n"; 91 | $res .= do { local $/; <$in> }; 92 | close $in; 93 | } 94 | 95 | $res .= ""; 96 | 97 | if ($outfile) { 98 | open my $out, ">:encoding(utf-8)", $outfile 99 | or die "Cannot open $outfile for writing: $!\n"; 100 | 101 | print $out $res; 102 | close $out; 103 | 104 | } else { 105 | print $res; 106 | } 107 | 108 | sub usage { 109 | die "Usage: $0 -v [-o ] \n"; 110 | } 111 | 112 | -------------------------------------------------------------------------------- /utils/gen-html-index-en.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use Getopt::Std; 9 | 10 | my %opts; 11 | getopts('o:v:', \%opts) or usage(); 12 | 13 | my $outfile = $opts{o}; 14 | my $ver = $opts{v} or usage(); 15 | 16 | my @nums = qw( 17 | 00 01 02 03 04 05 06 07 08 09 18 | 10 11 12 13 14 15 16 17 18 19 19 | 20 20 | ); 21 | 22 | my $res = <<_EOC_; 23 | 24 | 25 | agentzh's Nginx Tutorials (version $ver) 26 | 27 | 28 | 43 | 44 |

agentzh's Nginx Tutorials (version $ver)

45 |
46 |

Table of Contents

47 | _EOC_ 48 | 49 | $res .= "
    \n"; 50 | for my $infile (@ARGV) { 51 | (my $base = $infile) =~ s{.*/|\.html$}{}g; 52 | my $id = lc($base); 53 | 54 | if ($infile =~ /Foreword(\d+)/) { 55 | my $n = $1; 56 | if ($n eq '01') { 57 | $res .= <<_EOC_; 58 |
  • Foreword
  • 59 | _EOC_ 60 | } elsif ($n eq '02') { 61 | $res .= <<_EOC_; 62 |
  • Writing Plan for the Tutorials
  • 63 | _EOC_ 64 | } else { 65 | die "unknown infile: $infile"; 66 | } 67 | 68 | } elsif ($infile =~ /NginxVariables(\d+)/) { 69 | my $num = +$1; 70 | my $n = $nums[$num]; 71 | #$infile =~ s{.*/}{}g; 72 | $res .= <<_EOC_; 73 |
  • Nginx Variables ($n)
  • 74 | _EOC_ 75 | 76 | } elsif ($infile =~ /DirectiveExecOrder(\d+)/) { 77 | my $num = +$1; 78 | my $n = $nums[$num]; 79 | #$infile =~ s{.*/}{}g; 80 | $res .= <<_EOC_; 81 |
  • Nginx Directive Execution Order ($n)
  • 82 | _EOC_ 83 | 84 | } else { 85 | die "Unknown file $infile"; 86 | } 87 | } 88 | 89 | $res .= "
\n"; 90 | $res .= "
\n"; 91 | 92 | $res .= "
\n"; 93 | 94 | for my $infile (@ARGV) { 95 | open my $in, "<:encoding(utf-8)", $infile 96 | or die "Cannot open $infile for reading: $!\n"; 97 | $res .= "\n"; 102 | close $in; 103 | } 104 | 105 | $res .= "
\n"; 106 | 107 | $res .= <<_EOC_; 108 | 140 | _EOC_ 141 | 142 | $res .= ""; 143 | 144 | if ($outfile) { 145 | open my $out, ">:encoding(UTF-8)", $outfile 146 | or die "Cannot open $outfile for writing: $!\n"; 147 | 148 | print $out $res; 149 | close $out; 150 | 151 | } else { 152 | print $res; 153 | } 154 | 155 | sub usage { 156 | die "Usage: $0 -v [-o ] \n"; 157 | } 158 | 159 | -------------------------------------------------------------------------------- /utils/prefix.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use List::MoreUtils qw(first_index); 6 | 7 | sub shell ($); 8 | 9 | my @chapters = qw( 10 | Foreword 11 | NginxVariables 12 | NginxDirectiveExecOrder 13 | ); 14 | 15 | my $suffix = ".tut"; 16 | 17 | chdir "zh-cn/" or die "cannot cd to zh-cn/\n"; 18 | 19 | my @files = glob "*$suffix"; 20 | for my $file (@files) { 21 | if ($file =~ /^ (?:(\d+)-)? (\w+?) (?: (\d+))? \Q$suffix\E $/x) { 22 | my ($old_serial, $chapter, $order) = ($1, $2, $3); 23 | 24 | my $serial = scalar first_index { $_ eq $chapter } @chapters; 25 | if ($serial < 0) { 26 | die "Bad chapter name: $chapter\n"; 27 | } 28 | 29 | $serial = sprintf("%02d", $serial); 30 | warn "serial: $serial\n"; 31 | 32 | if (defined $old_serial && $serial eq $old_serial) { 33 | warn "$file already ok\n"; 34 | next; 35 | } 36 | 37 | my $new_file; 38 | if (defined $order) { 39 | $new_file = "$serial-$chapter$order$suffix"; 40 | 41 | } else { 42 | $new_file = "$serial-$chapter$suffix"; 43 | } 44 | 45 | shell "git mv $file $new_file"; 46 | 47 | } else { 48 | die "unknown file $file\n"; 49 | } 50 | } 51 | 52 | chdir ".."; 53 | 54 | sub shell ($) { 55 | my $cmd = shift; 56 | print "$cmd\n"; 57 | system($cmd) == 0 58 | or die "Cannot run command: $cmd. Abort."; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /utils/tut2wiki-cn.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use Getopt::Std; 9 | my %opts; 10 | getopts('o:', \%opts) or usage(); 11 | 12 | my $outfile = $opts{o}; 13 | 14 | my %links = ( 15 | order => { 16 | '一' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xm7t.html', 17 | '二' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xnof.html', 18 | '三' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xpff.html', 19 | '四' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xyo1.html', 20 | '五' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y05x.html', 21 | '六' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y1f1.html', 22 | '七' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y8p1.html', 23 | '八' => 'http://blog.sina.com.cn/s/blog_6d579ff401010gp3.html', 24 | }, 25 | var => { 26 | '一' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wi7p.html', 27 | '二' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wk2j.html', 28 | '三' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wm63.html', 29 | '四' => 'http://blog.sina.com.cn/s/blog_6d579ff40100woyb.html', 30 | '五' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wqn7.html', 31 | '六' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wsip.html', 32 | '七' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wu5t.html', 33 | '八' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wvxn.html', 34 | }, 35 | ); 36 | 37 | my $infile = shift or 38 | usage(); 39 | 40 | open my $in, "<:encoding(utf-8)", $infile 41 | or die "cannot open $infile for reading: $!\n"; 42 | 43 | my $prev; 44 | my $src = ''; 45 | while (<$in>) { 46 | if (/^\s+/ || /^\s*$/) { 47 | $src .= $_; 48 | next; 49 | } 50 | 51 | if (/^[*:]\s+/) { 52 | $src .= $_; 53 | next; 54 | } 55 | 56 | if ($prev && $prev =~ /^\s+|^\s*$/) { 57 | $src .= $_; 58 | next; 59 | } 60 | 61 | chop $src; 62 | 63 | if ($src =~ /(?:\p{Han}|[”“)(,;:?。!…])$/s 64 | && /^(?:\p{Han}|[“”)(,;:?。!…])/) 65 | { 66 | $src .= $_; 67 | 68 | } else { 69 | $src .= " $_"; 70 | } 71 | 72 | } continue { 73 | $prev = $_; 74 | } 75 | 76 | close $in; 77 | 78 | utf8::encode($src); 79 | 80 | open $in, "<:encoding(utf-8)", \$src or die $!; 81 | 82 | my $wiki = ''; 83 | undef $prev; 84 | my $orig; 85 | my $in_geshi; 86 | while (<$in>) { 87 | $orig = $_; 88 | if (/^\s+|^\s*$/) { 89 | if (/^\s+(.+)/) { 90 | my $first = $1; 91 | if (!$in_geshi && $prev && $prev =~ /^$|^\S/) { 92 | if ($first =~ /^:(\w+)$/) { 93 | $in_geshi = 1; 94 | $wiki .= qq{\n}; 95 | $_ = ''; 96 | next; 97 | } 98 | 99 | $in_geshi = 1; 100 | $wiki .= "\n"; 101 | #s/^ {1,4}//; 102 | next; 103 | } 104 | 105 | #s/^ {1,4}//; 106 | } 107 | 108 | next; 109 | } 110 | 111 | if ($in_geshi) { 112 | chomp $wiki; 113 | $wiki .= "\n\n"; 114 | undef $in_geshi; 115 | } 116 | 117 | s{\bL<(\w+)tut/([^>()]*?(([^>]+?)))>}{ 118 | my $tag = $1; 119 | my $n = $2; 120 | my $key = $3; 121 | my $link = $&; 122 | my $map = $links{$tag} or die "Tag $tag not found in links table"; 123 | my $url = $map->{$key}; 124 | #warn "URL: $url"; 125 | if (!defined $url) { 126 | die "Bad link $link\n"; 127 | } 128 | "[$url $n]" 129 | }ge; 130 | 131 | s{\bL<(ngx_devel_kit)>}{[https://github.com/simpl/ngx_devel_kit $1]}g; 132 | 133 | s{\bL}{ 134 | my $n = $1; 135 | if ($n eq 'static') { 136 | "ngx_static" 137 | 138 | } elsif ($n eq 'http_core') { 139 | "[http://nginx.org/en/docs/http/ngx_http_core_module.html ngx_$n]" 140 | 141 | } elsif ($n eq 'realip') { 142 | "[[HttpRealIpModule|ngx_realip]]" 143 | 144 | } elsif ($n eq 'auth_request') { 145 | "[http://mdounin.ru/hg/ngx_http_auth_request_module/ ngx_$n]" 146 | 147 | } else { 148 | my @n; 149 | if ($n eq 'srcache') { 150 | @n = 'SRCache'; 151 | } else { 152 | @n = map \{ ucfirst \} split /_/, $n; 153 | } 154 | "[[Http" . join("", @n) . "Module|ngx_$n]]" 155 | } 156 | }ge; 157 | 158 | s{\bL}{[[CoreModule#error_log|error_log]]}g; 159 | 160 | s{\bL<(Nginx 变量漫谈(?:系列)?)>}{[http://blog.sina.com.cn/s/articlelist_1834459124_1_1.html $1]}g; 161 | 162 | s{\bL<(Nginx 配置指令的执行顺序(?:系列)?)>}{[http://blog.sina.com.cn/s/articlelist_1834459124_2_1.html $1]}g; 163 | 164 | s{\bL}{ 165 | my $n = $1; 166 | my $d = $2; 167 | 168 | if ($n eq 'ngx_http_core') { 169 | $n = 'ngx_core'; 170 | } 171 | 172 | if ($n eq 'auth_request') { 173 | "$d" 174 | 175 | } elsif ($n eq 'realip') { 176 | "[[HttpRealIpModule#$d|$d]]" 177 | 178 | } else { 179 | my @n = map \{ ucfirst \} split /_/, $n; 180 | "[[Http" . join("", @n) . "Module#$d|$d]]" 181 | } 182 | }ge; 183 | 184 | s{\bL<(\$arg_XXX)>}{[[HttpCoreModule\#\$arg_PARAMETER|$1]]}g; 185 | s{\bL<(\$cookie_XXX)>}{[[HttpCoreModule\#\$cookie_COOKIE|$1]]}g; 186 | s{\bL<(\$http_XXX)>}{[[HttpCoreModule\#\$http_HEADER|$1]]}g; 187 | s{\bL<(\$sent_http_XXX)>}{[[HttpCoreModule\#\$sent_http_HEADER|$1]]}g; 188 | 189 | s{\bL<([^\|>]+)\|([^\|>]+)>}{[$2 $1]}g; 190 | 191 | s{\bL<(http[^\|\>\s]+)>}{[$1 $1]}g; 192 | 193 | s{\b[FC]<(.*?)>}{$1}g; 194 | 195 | } continue { 196 | $prev = $orig; 197 | $wiki .= $_; 198 | } 199 | 200 | close $in; 201 | 202 | if ($in_geshi) { 203 | chomp $wiki; 204 | $wiki .= "\n\n"; 205 | undef $in_geshi; 206 | } 207 | 208 | if ($wiki =~ /\bL<.*?>/) { 209 | die "Found unresolved link $&\n"; 210 | } 211 | 212 | $wiki =~ s/^\s+|\s+$//sg; 213 | 214 | if ($outfile) { 215 | open my $out, ">:encoding(utf-8)", $outfile 216 | or die "Cannot open $outfile for writing: $!\n"; 217 | 218 | print $out $wiki; 219 | 220 | close $out; 221 | 222 | } else { 223 | print $wiki; 224 | } 225 | 226 | #warn "wrote $outfile.\n"; 227 | 228 | sub usage { 229 | die "Usage: $0 [-o ] \n"; 230 | } 231 | -------------------------------------------------------------------------------- /utils/tut2wiki-en.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use Getopt::Std; 9 | my %opts; 10 | getopts('o:', \%opts) or usage(); 11 | 12 | my $outfile = $opts{o}; 13 | 14 | my %links = ( 15 | order => { 16 | '01' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xm7t.html', 17 | '02' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xnof.html', 18 | '03' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xpff.html', 19 | '04' => 'http://blog.sina.com.cn/s/blog_6d579ff40100xyo1.html', 20 | '05' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y05x.html', 21 | '06' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y1f1.html', 22 | '07' => 'http://blog.sina.com.cn/s/blog_6d579ff40100y8p1.html', 23 | '08' => 'http://blog.sina.com.cn/s/blog_6d579ff401010gp3.html', 24 | }, 25 | var => { 26 | '01' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wi7p.html', 27 | '02' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wk2j.html', 28 | '03' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wm63.html', 29 | '04' => 'http://blog.sina.com.cn/s/blog_6d579ff40100woyb.html', 30 | '05' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wqn7.html', 31 | '06' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wsip.html', 32 | '07' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wu5t.html', 33 | '08' => 'http://blog.sina.com.cn/s/blog_6d579ff40100wvxn.html', 34 | }, 35 | ); 36 | 37 | my $infile = shift or 38 | usage(); 39 | 40 | open my $in, "<:encoding(UTF-8)", $infile 41 | or die "cannot open $infile for reading: $!\n"; 42 | 43 | my $prev; 44 | my $src = ''; 45 | while (<$in>) { 46 | if (/^\s+/ || /^\s*$/) { 47 | $src .= $_; 48 | next; 49 | } 50 | 51 | if (/^[*:]\s+/) { 52 | $src .= $_; 53 | next; 54 | } 55 | 56 | if ($prev && $prev =~ /^\s+|^\s*$/) { 57 | $src .= $_; 58 | next; 59 | } 60 | 61 | chop $src; 62 | 63 | if ($src =~ /(?:\p{Han}|[”“)(,;:?。!…])$/s 64 | && /^(?:\p{Han}|[“”)(,;:?。!…])/) 65 | { 66 | $src .= $_; 67 | 68 | } else { 69 | $src .= " $_"; 70 | } 71 | 72 | } continue { 73 | $prev = $_; 74 | } 75 | 76 | close $in; 77 | 78 | open $in, "<:encoding(UTF-8)", \$src; 79 | 80 | my $wiki = ''; 81 | undef $prev; 82 | my $orig; 83 | my $in_geshi; 84 | while (<$in>) { 85 | $orig = $_; 86 | if (/^\s+|^\s*$/) { 87 | if (/^\s+(.+)/) { 88 | my $first = $1; 89 | if (!$in_geshi && $prev && $prev =~ /^$|^\S/) { 90 | if ($first =~ /^:(\w+)$/) { 91 | $in_geshi = 1; 92 | $wiki .= qq{\n}; 93 | $_ = ''; 94 | next; 95 | } 96 | 97 | $in_geshi = 1; 98 | $wiki .= "\n"; 99 | #s/^ {1,4}//; 100 | next; 101 | } 102 | 103 | #s/^ {1,4}//; 104 | } 105 | 106 | next; 107 | } 108 | 109 | if ($in_geshi) { 110 | chomp $wiki; 111 | $wiki .= "\n\n"; 112 | undef $in_geshi; 113 | } 114 | 115 | s{\bL<(\w+)tut/([^>()]*?\(([^>]+?)\))>}{ 116 | my $tag = $1; 117 | my $n = $2; 118 | my $key = $3; 119 | my $link = $&; 120 | my $map = $links{$tag} or die "Tag $tag not found in links table"; 121 | my $url = $map->{$key}; 122 | #warn "URL: $url"; 123 | if (!defined $url) { 124 | die "Bad link $link\n"; 125 | } 126 | "[$url $n]" 127 | }ge; 128 | 129 | s{\bL<(ngx_devel_kit)>}{[https://github.com/simpl/ngx_devel_kit $1]}g; 130 | 131 | s{\bL}{ 132 | my $n = $1; 133 | if ($n eq 'static') { 134 | "ngx_static" 135 | 136 | } elsif ($n eq 'http_core') { 137 | "[http://nginx.org/en/docs/http/ngx_http_core_module.html ngx_$n]" 138 | 139 | } elsif ($n eq 'realip') { 140 | "[[HttpRealIpModule|ngx_realip]]" 141 | 142 | } elsif ($n eq 'auth_request') { 143 | "[http://mdounin.ru/hg/ngx_http_auth_request_module/ ngx_$n]" 144 | 145 | } else { 146 | my @n; 147 | if ($n eq 'srcache') { 148 | @n = 'SRCache'; 149 | } else { 150 | @n = map \{ ucfirst \} split /_/, $n; 151 | } 152 | "[[Http" . join("", @n) . "Module|ngx_$n]]" 153 | } 154 | }ge; 155 | 156 | s{\bL}{[[CoreModule#error_log|error_log]]}g; 157 | 158 | s{\bL<(Nginx Variables(?: Series)?)>}{[http://blog.sina.com.cn/s/articlelist_1834459124_1_1.html $1]}g; 159 | 160 | s{\bL<(Nginx Directive Execution Order(?: Series)?)>}{[http://blog.sina.com.cn/s/articlelist_1834459124_2_1.html $1]}g; 161 | 162 | s{\bL}{ 163 | my $n = $1; 164 | my $d = $2; 165 | 166 | if ($n eq 'ngx_http_core') { 167 | $n = 'ngx_core'; 168 | } 169 | 170 | if ($n eq 'auth_request') { 171 | "$d" 172 | 173 | } elsif ($n eq 'realip') { 174 | "[[HttpRealIpModule#$d|$d]]" 175 | 176 | } else { 177 | my @n = map \{ ucfirst \} split /_/, $n; 178 | "[[Http" . join("", @n) . "Module#$d|$d]]" 179 | } 180 | }ge; 181 | 182 | s{\bL<(\$arg_XXX)>}{[[HttpCoreModule\#\$arg_PARAMETER|$1]]}g; 183 | s{\bL<(\$cookie_XXX)>}{[[HttpCoreModule\#\$cookie_COOKIE|$1]]}g; 184 | s{\bL<(\$http_XXX)>}{[[HttpCoreModule\#\$http_HEADER|$1]]}g; 185 | s{\bL<(\$sent_http_XXX)>}{[[HttpCoreModule\#\$sent_http_HEADER|$1]]}g; 186 | 187 | s{\bL<([^\|>]+)\|([^\|>]+)>}{[$2 $1]}g; 188 | 189 | s{\bL<(http[^\|\>\s]+)>}{[$1 $1]}g; 190 | 191 | s{\b[FC]<(.*?)>}{$1}g; 192 | s{\bI<(.*?)>}{$1}g; 193 | 194 | } continue { 195 | $prev = $orig; 196 | $wiki .= $_; 197 | } 198 | 199 | close $in; 200 | 201 | if ($in_geshi) { 202 | chomp $wiki; 203 | $wiki .= "\n\n"; 204 | undef $in_geshi; 205 | } 206 | 207 | if ($wiki =~ /\bL<.*?>/) { 208 | die "Found unresolved link $&\n"; 209 | } 210 | 211 | $wiki =~ s/^\s+|\s+$//sg; 212 | 213 | if ($outfile) { 214 | open my $out, ">:encoding(UTF-8)", $outfile 215 | or die "Cannot open $outfile for writing: $!\n"; 216 | 217 | print $out $wiki; 218 | 219 | close $out; 220 | 221 | } else { 222 | print $wiki; 223 | } 224 | 225 | #warn "wrote $outfile.\n"; 226 | 227 | sub usage { 228 | die "Usage: $0 [-o ] \n"; 229 | } 230 | 231 | -------------------------------------------------------------------------------- /utils/wc.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use encoding 'utf8'; 6 | 7 | if (!@ARGV) { 8 | die "No input file specified.\n"; 9 | } 10 | 11 | my $hanc = 0; 12 | my $wc = 0; 13 | my $c = 0; 14 | my $lc = 0; 15 | 16 | for my $infile (@ARGV) { 17 | open my $in, "<:encoding(UTF-8)", $infile 18 | or 19 | die "cannot open $infile for reading: $!\n"; 20 | 21 | while (<$in>) { 22 | $lc++; 23 | $hanc++ while /\p{Han}/g; 24 | while (1) { 25 | if (/\G\s+/gc) { 26 | next; 27 | } elsif (/\G(?:\p{Han}|[”“,;:?。!…]|[,;.])/gc) { 28 | $wc++; 29 | next; 30 | 31 | } elsif (/\G[A-Za-z0-9_]+/gc) { 32 | $wc++; 33 | next; 34 | } elsif (/\G./gc) { 35 | next; 36 | } 37 | 38 | last; 39 | } 40 | 41 | $c += length($_); 42 | } 43 | 44 | close $in; 45 | } 46 | 47 | my $rough_wc = int($wc * 1.2); 48 | 49 | print "$hanc han chars found.\n"; 50 | print "$wc words found.\n"; 51 | print "$rough_wc rough words found.\n"; 52 | print "$lc lines found.\n"; 53 | print "$c chars found.\n"; 54 | 55 | -------------------------------------------------------------------------------- /utils/wiki2html-cn.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use List::MoreUtils qw(first_index); 9 | use Getopt::Std; 10 | 11 | my @chapters = qw( 12 | Foreword 13 | NginxVariables 14 | NginxDirectiveExecOrder 15 | ); 16 | 17 | my %text2chapter = ( 18 | '缘起' => 'Forword', 19 | 'Nginx 变量漫谈' => 'NginxVariables', 20 | 'Nginx 配置指令的执行顺序' => 'NginxDirectiveExecOrder', 21 | ); 22 | 23 | my @nums = qw( 24 | 零 一 二 三 四 五 六 七 八 九 25 | 十 十一 十二 十三 十四 十五 十六 十七 十八 十九 26 | 二十 27 | ); 28 | 29 | my %opts; 30 | getopts('o:', \%opts) or usage(); 31 | 32 | my $outfile = $opts{o}; 33 | 34 | my $infile = shift or usage(); 35 | 36 | (my $base = $infile) =~ s{.*/|\.wiki$}{}g; 37 | 38 | my ($cur_chapter, $cur_serial, $cur_order); 39 | if ($infile =~ /\b(\d+)-(\w+?)(?:\d+)?\.wiki$/) { 40 | $cur_serial = $1; 41 | $cur_chapter = $2; 42 | $cur_order = $3; 43 | 44 | } else { 45 | die "Bad input file $infile\n"; 46 | } 47 | 48 | open my $in, "<:encoding(utf-8)", $infile 49 | or die "Cannot open $infile for reading: $!\n"; 50 | 51 | my $ctx = {}; 52 | my $html = ''; 53 | while (<$in>) { 54 | #warn "line $.\n"; 55 | if (/^\s+$/) { 56 | if ($ctx->{code}) { 57 | #warn "inserting br in code"; 58 | $html .= "
\n"; 59 | } 60 | next; 61 | } 62 | 63 | if (/^\s+/) { 64 | $html .= fmt_code($_, $ctx); 65 | 66 | } elsif (/^[*:]\s+(.*)/) { 67 | my $txt = $1; 68 | if (!$ctx->{list}) { 69 | $ctx->{list} = 1; 70 | 71 | $html .= "
    \n"; 72 | } 73 | 74 | my $item = fmt_para($txt, $ctx); 75 | if ($item =~ m{^

    (.*)

    $}) { 76 | $item = $1; 77 | } 78 | 79 | $html .= "
  • $item
  • \n"; 80 | 81 | } elsif (/^\S/) { 82 | if ($ctx->{list}) { 83 | undef $ctx->{list}; 84 | $html .= "
\n"; 85 | } 86 | 87 | 88 | $html .= fmt_para($_, $ctx); 89 | 90 | } else { 91 | die "unexpected line $_"; 92 | } 93 | } 94 | 95 | close $in; 96 | 97 | #$html .= " \n\n"; 98 | 99 | if ($outfile) { 100 | open my $out, ">:encoding(utf-8)", $outfile 101 | or die "Cannot open $outfile for writing: $!\n"; 102 | 103 | print $out $html; 104 | close $out; 105 | 106 | } else { 107 | print $html; 108 | } 109 | 110 | sub fmt_para { 111 | my ($s, $ctx) = @_; 112 | if ($s =~ /^= (.*?) =$/) { 113 | my $title = $1; 114 | return <<"_EOC_"; 115 |

$title

116 | _EOC_ 117 | } 118 | 119 | if (/^{code} = 1; 121 | return ""; 122 | } 123 | 124 | if (m{^}) { 125 | $ctx->{code} = 0; 126 | return ""; 127 | } 128 | 129 | my $res; 130 | 131 | while (1) { 132 | #my $pos = pos $s; 133 | #warn "pos: $pos" if defined $pos; 134 | if ($s =~ /\G (\s*) \[ (http[^\]\s]+) \s+ ([^\]]+) \] /gcx) { 135 | my ($indent, $url, $label) = ($1, $2, $3); 136 | 137 | if (defined $text2chapter{$label} 138 | || ($label =~ /(.+)系列$/ && $text2chapter{$1})) 139 | { 140 | my $key; 141 | 142 | if (defined $text2chapter{$label}) { 143 | $key = $label; 144 | 145 | } else { 146 | $key = $1; 147 | } 148 | 149 | my $chapter = $text2chapter{$key}; 150 | if (!$chapter) { 151 | die "Chapter $key not found"; 152 | } 153 | my $serial = first_index { $_ eq $chapter } @chapters; 154 | if (!$serial) { 155 | die "chapter $chapter not found"; 156 | } 157 | 158 | $serial = sprintf("%02d", $serial); 159 | my $base = "$serial-${chapter}01"; 160 | $res .= qq{$indent
$label}; 161 | 162 | } elsif ($label =~ m/(.*)(([一二三四五六七八九十]+))/) { 163 | my $text = $1; 164 | my $cn_num = $2; 165 | my $order = sprintf "%02d", first_index { $_ eq $cn_num } @nums; 166 | 167 | my ($chapter, $serial); 168 | if (!$text) { 169 | $chapter = $cur_chapter; 170 | $serial = $cur_serial; 171 | 172 | } else { 173 | $chapter = $text2chapter{$text}; 174 | if (!$chapter) { 175 | die "chapter $text not found"; 176 | } 177 | 178 | $serial = first_index { $_ eq $chapter } @chapters; 179 | if (!$serial) { 180 | die "chapter $chapter not found"; 181 | } 182 | 183 | $serial = sprintf("%02d", $serial); 184 | } 185 | 186 | $res .= qq{$indent$label}; 187 | 188 | } else { 189 | #warn "matched abs link $&\n"; 190 | $label = fmt_html($label); 191 | $res .= qq{$indent$label}; 192 | } 193 | 194 | } elsif ($s =~ /\G \s* \[\[ ([^\|\]]+) \| ([^\]]+) \]\]/gcx) { 195 | my ($url, $label) = ($1, $2); 196 | #warn "matched rel link $&\n"; 197 | $url =~ s/\$/.24/g; 198 | $res .= qq{ $label}; 199 | 200 | } elsif ($s =~ /\G [^\[]+ /gcx) { 201 | #warn "matched text $&\n"; 202 | $res .= $&; 203 | 204 | } elsif ($s =~ /\G ./gcx) { 205 | #warn "unknown link $&\n"; 206 | 207 | } else { 208 | #warn "breaking"; 209 | last; 210 | } 211 | } 212 | 213 | return "

$res

\n"; 214 | } 215 | 216 | sub fmt_html { 217 | my $s = shift; 218 | $s =~ s/\&/\&/g; 219 | $s =~ s/"/\"/g; 220 | $s =~ s//\>/g; 222 | $s =~ s/ /\ /g; 223 | $s; 224 | } 225 | 226 | sub fmt_code { 227 | my $s = shift; 228 | $s = fmt_html($s); 229 | $s =~ s{\n}{
\n}sg; 230 | $s; 231 | } 232 | 233 | sub usage { 234 | die "Usage: $0 [-o ] \n"; 235 | } 236 | -------------------------------------------------------------------------------- /utils/wiki2html-en.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use v5.10.1; 4 | use utf8; 5 | use strict; 6 | use warnings; 7 | 8 | use List::MoreUtils qw(first_index); 9 | use Getopt::Std; 10 | 11 | my @chapters = qw( 12 | Foreword 13 | NginxVariables 14 | NginxDirectiveExecOrder 15 | ); 16 | 17 | my %text2chapter = ( 18 | 'Foreword' => 'Forword', 19 | 'Nginx Variables' => 'NginxVariables', 20 | 'Nginx Directive Execution Order' => 'NginxDirectiveExecOrder', 21 | ); 22 | 23 | my @nums = qw( 24 | 00 01 02 03 04 05 06 07 08 09 25 | 10 11 12 13 14 15 16 17 18 19 26 | 20 27 | ); 28 | 29 | my %opts; 30 | getopts('o:', \%opts) or usage(); 31 | 32 | my $outfile = $opts{o}; 33 | 34 | my $infile = shift or usage(); 35 | 36 | (my $base = $infile) =~ s{.*/|\.wiki$}{}g; 37 | 38 | my ($cur_chapter, $cur_serial, $cur_order); 39 | if ($infile =~ /\b(\d+)-(\w+?)(?:\d+)?\.wiki$/) { 40 | $cur_serial = $1; 41 | $cur_chapter = $2; 42 | $cur_order = $3; 43 | 44 | } else { 45 | die "Bad input file $infile\n"; 46 | } 47 | 48 | open my $in, "<:encoding(UTF-8)", $infile 49 | or die "Cannot open $infile for reading: $!\n"; 50 | 51 | my $ctx = {}; 52 | my $html = ''; 53 | while (<$in>) { 54 | #warn "line $.\n"; 55 | if (/^\s+$/) { 56 | if ($ctx->{code}) { 57 | #warn "inserting br in code"; 58 | $html .= "
\n"; 59 | } 60 | next; 61 | } 62 | 63 | if (/^\s+/) { 64 | $html .= fmt_code($_, $ctx); 65 | 66 | } elsif (/^[*:]\s+(.*)/) { 67 | my $txt = $1; 68 | if (!$ctx->{list}) { 69 | $ctx->{list} = 1; 70 | 71 | $html .= "
    \n"; 72 | } 73 | 74 | my $item = fmt_para($txt, $ctx); 75 | if ($item =~ m{^

    (.*)

    $}) { 76 | $item = $1; 77 | } 78 | 79 | $html .= "
  • $item
  • \n"; 80 | 81 | } elsif (/^\S/) { 82 | if ($ctx->{list}) { 83 | undef $ctx->{list}; 84 | $html .= "
\n"; 85 | } 86 | 87 | 88 | $html .= fmt_para($_, $ctx); 89 | 90 | } else { 91 | die "unexpected line $_"; 92 | } 93 | } 94 | 95 | close $in; 96 | 97 | #$html .= " \n\n"; 98 | 99 | if ($outfile) { 100 | open my $out, ">:encoding(UTF-8)", $outfile 101 | or die "Cannot open $outfile for writing: $!\n"; 102 | 103 | print $out $html; 104 | close $out; 105 | 106 | } else { 107 | print $html; 108 | } 109 | 110 | sub fmt_para { 111 | my ($s, $ctx) = @_; 112 | if ($s =~ /^= (.*?) =$/) { 113 | my $title = $1; 114 | my $id = quote_anchor($base); 115 | return <<"_EOC_"; 116 |

$title

117 | _EOC_ 118 | } 119 | 120 | $s =~ s{\[\[File:(.*?)\|thumb\|alt=(.*?)\]\]} 121 | {
122 |
123 | 124 |
125 |
$2
126 |
127 |
128 |
}g; 129 | 130 | if ($s =~ /^== (.*?) ==$/) { 131 | my $title = $1; 132 | my $id = quote_anchor("$base-$title"); 133 | return <<"_EOC_"; 134 |

$title

135 | _EOC_ 136 | } 137 | 138 | if ($s =~ /^=== (.*?) ===$/) { 139 | my $title = $1; 140 | my $id = quote_anchor("$base-$title"); 141 | return <<"_EOC_"; 142 |

$title

143 | _EOC_ 144 | } 145 | 146 | if (/^{code} = 1; 148 | return ""; 149 | } 150 | 151 | if (m{^}) { 152 | $ctx->{code} = 0; 153 | return ""; 154 | } 155 | 156 | my $res; 157 | 158 | while (1) { 159 | #my $pos = pos $s; 160 | #warn "pos: $pos" if defined $pos; 161 | if ($s =~ /\G (\s*) \[ (http[^\]\s]+) \s+ ([^\]]+) \] /gcx) { 162 | my ($indent, $url, $label) = ($1, $2, $3); 163 | 164 | if (defined $text2chapter{$label} 165 | || ($label =~ /(.+)系列$/ && $text2chapter{$1})) 166 | { 167 | my $key; 168 | 169 | if (defined $text2chapter{$label}) { 170 | $key = $label; 171 | 172 | } else { 173 | $key = $1; 174 | } 175 | 176 | my $chapter = $text2chapter{$key}; 177 | if (!$chapter) { 178 | die "Chapter $key not found"; 179 | } 180 | my $serial = first_index { $_ eq $chapter } @chapters; 181 | if (!$serial) { 182 | die "chapter $chapter not found"; 183 | } 184 | 185 | $serial = sprintf("%02d", $serial); 186 | my $base = lc("$serial-${chapter}01"); 187 | $res .= qq{$indent$label}; 188 | 189 | } elsif ($label =~ m/(.*)\((\d{2})\)/) { 190 | my $text = $1; 191 | my $cn_num = $2; 192 | my $order = sprintf "%02d", first_index { $_ eq $cn_num } @nums; 193 | 194 | $text =~ s/^\s+|\s+$//g; 195 | 196 | my ($chapter, $serial); 197 | if (!$text) { 198 | $chapter = $cur_chapter; 199 | $serial = $cur_serial; 200 | 201 | } else { 202 | $chapter = $text2chapter{$text}; 203 | if (!$chapter) { 204 | die "chapter $text not found"; 205 | } 206 | 207 | $serial = first_index { $_ eq $chapter } @chapters; 208 | if (!$serial) { 209 | die "chapter $chapter not found"; 210 | } 211 | 212 | $serial = sprintf("%02d", $serial); 213 | } 214 | 215 | $res .= qq{$indent$label}; 216 | 217 | } else { 218 | #warn "matched abs link $&\n"; 219 | $label = fmt_html($label); 220 | $res .= qq{$indent$label}; 221 | } 222 | 223 | } elsif ($s =~ /\G \s* \[\[ ([^\|\]]+) \| ([^\]]+) \]\]/gcx) { 224 | my ($url, $label) = ($1, $2); 225 | #warn "matched rel link $&\n"; 226 | $url =~ s/\$/.24/g; 227 | $res .= qq{ $label}; 228 | 229 | } elsif ($s =~ /\G [^\[]+ /gcx) { 230 | #warn "matched text $&\n"; 231 | $res .= $&; 232 | 233 | } elsif ($s =~ /\G ./gcx) { 234 | #warn "unknown link $&\n"; 235 | 236 | } else { 237 | #warn "breaking"; 238 | last; 239 | } 240 | } 241 | 242 | return "

$res

\n"; 243 | } 244 | 245 | sub fmt_html { 246 | my $s = shift; 247 | $s =~ s/\&/\&/g; 248 | $s =~ s/"/\"/g; 249 | $s =~ s//\>/g; 251 | $s =~ s/ /\ /g; 252 | $s; 253 | } 254 | 255 | sub fmt_code { 256 | my $s = shift; 257 | # new template do not need the space indent 258 | $s =~ s/^ {4}//g; 259 | $s = fmt_html($s); 260 | $s =~ s{\n}{
\n}sg; 261 | $s; 262 | } 263 | 264 | sub usage { 265 | die "Usage: $0 [-o ] \n"; 266 | } 267 | 268 | sub quote_anchor { 269 | my $id = shift; 270 | for ($id) { 271 | s/\$/-dollar-/g; 272 | s/\&/-and-/g; 273 | s/[^-\w.]/-/g; 274 | s/--+/-/g; 275 | s/^-+|-+$//g; 276 | $_ = lc; 277 | } 278 | 279 | $id =~ s/^01-nginxvariables\d+-/nginx-variables-/; 280 | 281 | return $id; 282 | } 283 | -------------------------------------------------------------------------------- /zh-cn/00-Foreword01.tut: -------------------------------------------------------------------------------- 1 | = 缘起 = 2 | 3 | 其实这两年为 Nginx 世界做了这么多的事情,一直想通过一系列教程性的文章把我 4 | 的那些工作成果和所学所知都介绍给更多的朋友。现在终于下决心在新浪博客 L 5 | 上面用中文 6 | 写点东西,每一篇东西都会有一个小主题,但次序和组织上就不那么讲究了,毕竟并不是一 7 | 本完整的图书,或许未来我会将之整理出书也不一定。 8 | 9 | 我现在编写的教程是按所谓的“系列”来划分的,比如首先连载的“Nginx 变量漫谈 10 | ”系列。每一个系列基本上都可以粗略对应到未来出的 Nginx 书中的一“章”(当 11 | 然内部还会重新组织内容并划分出“节”来)。我面向的读者是各个水平层次的 Nginx 12 | 用户,同时也包括未使用过 Nginx 的 Apache、Lighttpd 等服务 13 | 器的老用户。 14 | 15 | 我只保证这些教程中的例子至少兼容到 Nginx C<0.8.54>,别用更老的版 16 | 本来找我的错处,我一概不管,毕竟眼下最新的稳定版已经是 C<1.0.10> 了。 17 | 18 | 凡在教程里面提到的模块,都是经过生产环境检验过的。即便是标准模块,如果没有达到生 19 | 产标准,或者有重要的 bug,我也不会提及。 20 | 21 | 我在教程中会大量使用非标准的第三方模块,如果你怕麻烦,不愿自己一个一个从网上下载 22 | 和安装那些个模块,我推荐你下载和安装我维护的 C 23 | 这个软件包: 24 | 25 | L 26 | 27 | 教程里提及的模块,包括足够新的 Nginx 稳定版核心,都包含在了这个软件包中。 28 | 29 | 我在这些教程中遵循的一个原则是,尽量通过短小精悍的实例来佐证我陈述的原理和观点。 30 | 我希望帮助读者养成不随便听信别人现成的观点和陈述,而通过自己运行实例来验证的好习 31 | 惯。这种风格或许也和我在 QA 方面的背景有关。事实上我在写作过程中也经常按我设 32 | 计的小例子的实际运行结果,不断地对我的理解以及教程的内容进行修正。 33 | 34 | 对于有问题的代码示例,我们会有意在排版上让它们和其他合法示例所有区别,即在问题示 35 | 例的每一行代码前添加问号字符,即(C),一个例子是: 36 | 37 | :nginx 38 | ? server { 39 | ? listen 8080; 40 | ? 41 | ? location /bad { 42 | ? echo $foo; 43 | ? } 44 | ? } 45 | 46 | 未经我的同意,请不要随便转载或者以其他方式使用这些教程。因为其中的每一句话,除了 47 | 特别引用的“名句”,都是我自己的,我保留所有的权利。我不希望读者转载的另一大原因 48 | 在于:转载后的拷贝版本是死的,我就不能再同步更新了。而我经常会按照读者的反馈,对 49 | 已发表的老文章进行了大面积的修订。 50 | 51 | 我欢迎读者多提宝贵意见,特别是建设性的批评意见。类似“太烂了!”这样无聊中伤的意 52 | 见我看还是算了。 53 | 54 | 所有这些文章的源都已经放在 GitHub 网站上进行版本控制了: 55 | 56 | L 57 | 58 | 源文件都在此项目的 F 目录下。我使用了一种自己设计的 C 59 | 和 C 标记语言的混合物来撰写这些文章,就是那些 F<.tut> 文件 60 | 。欢迎建立分支和提供补丁。 61 | 62 | 本教程适用于普通手机、Kindle、iPad/iPhone、Sony 等电子阅读器的 C<.html>、C<.mobi>、C<.epub> 63 | 以及 C<.pdf> 等格式的电子书文件可以从下面这个位置下载: 64 | 65 | L 66 | 67 | 章亦春 (agentzh) 于福州家中 68 | 69 | 2011 年 11 月 30 日 70 | 71 | -------------------------------------------------------------------------------- /zh-cn/00-Foreword02.tut: -------------------------------------------------------------------------------- 1 | = Nginx 教程的连载计划 = 2 | 3 | 下面以教程系列为单位,列举出了已经发表和计划发表的连载教程: 4 | 5 | * Nginx 新手起步 6 | * Nginx 是如何匹配 URI 的 7 | * L 8 | * L 9 | * Nginx 的 if 是邪恶的 10 | * Nginx 子请求 11 | * Nginx 静态文件服务 12 | * Nginx 的日志服务 13 | * 基于 Nginx 的应用网关 14 | * 基于 Nginx 的反向代理 15 | * Nginx 与 Memcached 16 | * Nginx 与 Redis 17 | * Nginx 与 MySQL 18 | * Nginx 与 PostgreSQL 19 | * 基于 Nginx 的应用缓存 20 | * Nginx 中的安全与访问控制 21 | * 基于 Nginx 的 Web Service 22 | * Nginx 驱动的 Web 2.0 应用 23 | * 测试 Nginx 及其应用的性能 24 | * 借助 Nginx 社区的力量 25 | 26 | 这些系列的名字和最终我的 Nginx 书中的“章”名可以粗略地对应上,但不会等同 27 | 。同时未发表的系列的名字也可能发生变化,同时实际发表的顺序也会和这里列出的顺序不 28 | 太一样。 29 | 30 | 上面的列表会随时更新,以保证总是反映了最新的计划和发表情况。 31 | 32 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables01.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(一) = 2 | 3 | Nginx 的配置文件使用的就是一门微型的编程语言,许多真实世界里的 Nginx 4 | 配置文件其实就是一个一个的小程序。当然,是不是“图灵完全的”暂且不论,至少据我观 5 | 察,它在设计上受 Perl 和 Bourne Shell 这两种语言的影响很大。 6 | 在这一点上,相比 Apache 和 Lighttpd 等其他 Web 服务器的配 7 | 置记法,不能不说算是 Nginx 的一大特色了。既然是编程语言,一般也就少不了“ 8 | 变量”这种东西(当然,Haskell 这样奇怪的函数式语言除外了)。 9 | 10 | 熟悉 Perl、Bourne Shell、C/C++ 等命令式编程语言的朋友肯定 11 | 知道,变量说白了就是存放“值”的容器。而所谓“值”,在许多编程语言里,既可以是 12 | C<3.14> 这样的数值,也可以是 C 这样的字符串 13 | ,甚至可以是像数组、哈希表这样的复杂数据结构。然而,在 Nginx 配置中,变量 14 | 只能存放一种类型的值,因为也只存在一种类型的值,那就是字符串。 15 | 16 | 比如我们的 F 文件中有下面这一行配置: 17 | 18 | :nginx 19 | set $a "hello world"; 20 | 21 | 我们使用了标准 L 模块的 L 22 | 配置指令对变量 C<$a> 进行了赋值操作。特别地,我们把字符串 C 赋给了它。 24 | 25 | 我们看到,Nginx 变量名前面有一个 C<$> 符号,这是记法上的要求。所有的 26 | Nginx 变量在 Nginx 配置文件中引用时都须带上 C<$> 前缀。这种 27 | 表示方法和 Perl、PHP 这些语言是相似的。 28 | 29 | 虽然 C<$> 这样的变量前缀修饰会让正统的 C 和 C 程 30 | 序员不舒服, 31 | 但这种表示方法的好处也是显而易见的,那就是可以直接把变量嵌入到字符串常量中以构造 32 | 出新的字符串: 33 | 34 | :nginx 35 | set $a hello; 36 | set $b "$a, $a"; 37 | 38 | 这里我们通过已有的 Nginx 变量 C<$a> 的值,来构造变量 C<$b> 39 | 的值,于是这两条指令顺序执行完之后,C<$a> 的值是 C,而 C<$b> 40 | 的值则是 C. 这种技术在 Perl 世界里被称为“ 41 | 变 42 | 量插值”(variable interpolation),它让专门的字符串拼接运 43 | 算符变得 44 | 不再那么必要。 45 | 我们在这里也不妨采用此术语。 46 | 47 | 我们来看一个比较完整的配置示例: 48 | 49 | :nginx 50 | server { 51 | listen 8080; 52 | 53 | location /test { 54 | set $foo hello; 55 | echo "foo: $foo"; 56 | } 57 | } 58 | 59 | 这个例子省略了 F 配置文件中最外围的 C 60 | 配置块以及 C 配置块。使用 C 这个 HTTP 客 61 | 户端在命令行上请求这个 C 接口,我们可 62 | 以得到 63 | 64 | :bash 65 | $ curl 'http://localhost:8080/test' 66 | foo: hello 67 | 68 | 这里我们使用第三方 L 模块的 L 69 | 配置指令将 C<$foo> 变量的值作为当前请求的响应体输出。 70 | 71 | 我们看到,L 配置指令的参数也支持“变量 72 | 插值”。不过,需要说明的是,并非所有的配置指令都支持“变量插值”。事实上,指令参 73 | 数是否允许“变量插值”,取决于该指令的实现模块。 74 | 75 | 如果我们想通过 L 76 | 指令直接输出含有“美元符”(C<$>)的字符串,那么有没有办法把特殊的 77 | C<$> 字符给转义 78 | 掉呢?答案是否定的(至少到目前最新的 Nginx 稳定版 C<1.0.10>)。 79 | 不过幸运的是,我们可以绕过这个限制,比如通过 80 | 不支持“变量插值”的模块配置指令专门构造出取值为 C<$> 的 Nginx 变量 81 | ,然后再在 L 82 | 中使用这个变量。看下面这个例子: 83 | 84 | :nginx 85 | geo $dollar { 86 | default "$"; 87 | } 88 | 89 | server { 90 | listen 8080; 91 | 92 | location /test { 93 | echo "This is a dollar sign: $dollar"; 94 | } 95 | } 96 | 97 | 测试结果如下: 98 | 99 | :bash 100 | $ curl 'http://localhost:8080/test' 101 | This is a dollar sign: $ 102 | 103 | 这里用到了标准模块 L 提供的配置指令 L 104 | 来为变量 C<$dollar> 赋予字符串 C<"$">,这样我们在下面需要使用 105 | 美元符的地方,就直接引用我们的 C<$dollar> 变量就可以了。其实 L 106 | 模块最常规的用法是根据客户端的 IP 地址对指定的 Nginx 变量进行赋值, 107 | 这里只是借用它以便“无条件地”对我们的 C<$dollar> 变量赋予“美元符” 108 | 这个值。 109 | 110 | 在“变量插值”的上下文中,还有一种特殊情况,即当引用的变量名之后紧跟着变量名的构 111 | 成字符时(比如后跟字母、数字 112 | 以及下划线),我们就需要使用特别的记法来消除歧义,例如: 113 | 114 | :nginx 115 | server { 116 | listen 8080; 117 | 118 | location /test { 119 | set $first "hello "; 120 | echo "${first}world"; 121 | } 122 | } 123 | 124 | 这里,我们在 L 配置指令的参数值中引用变量 C<$first> 125 | 的时候,后面紧跟着 C 这个单词,所以如果直接写作 C<"$firstworld"> 126 | 则 Nginx “变量插值”计算引擎会将之识别为引用了变量 C<$firstworld>. 127 | 为了解决这个难题,Nginx 的字符串记法支持使用花括号在 C<$> 之后把变量 128 | 名围起来,比如这里的 C<${first}>. 上面这个例子的输出是: 129 | 130 | :bash 131 | $ curl 'http://localhost:8080/test' 132 | hello world 133 | 134 | L 指令(以及前面提到的 L 135 | 指令)不仅有赋值的功能,它还有创建 Nginx 变量的副作用,即当作 136 | 为赋值对象的变量尚不存在时,它会自动创建该变量。比如在上面这个例子中,如 137 | 果 C<$a> 这个变量尚未创建,则 C 指令会自动创建 C<$a> 138 | 这个用户变量。如果我们不创建就直接使用它的值,则会报错。例如 139 | 140 | :nginx 141 | ? server { 142 | ? listen 8080; 143 | ? 144 | ? location /bad { 145 | ? echo $foo; 146 | ? } 147 | ? } 148 | 149 | 此时 Nginx 服务器会拒绝加载配置: 150 | 151 | [emerg] unknown "foo" variable 152 | 153 | 是的,我们甚至都无法启动服务! 154 | 155 | 有趣的是,Nginx 变量的创建和赋值操作发生在全然不同的时间阶段。Nginx 156 | 变量的创建只能发生在 Nginx 配置加载的时候,或者说 Nginx 启动的时候 157 | ;而赋值操作则只会发生在请求实际处理的时候。这意味着不创建而直接使用变量会导致启 158 | 动失败,同时也意味着我们无法 159 | 在请求处理时动态地创建新的 Nginx 变量。 160 | 161 | Nginx 变量一旦创建,其变量名的可见范围就是整个 Nginx 配置,甚至可以 162 | 跨越不同虚拟 163 | 主机的 C 配置块。我们来看一个例子: 164 | 165 | :nginx 166 | server { 167 | listen 8080; 168 | 169 | location /foo { 170 | echo "foo = [$foo]"; 171 | } 172 | 173 | location /bar { 174 | set $foo 32; 175 | echo "foo = [$foo]"; 176 | } 177 | } 178 | 179 | 这里我们在 C 中用 C 指令创建了变量 180 | C<$foo>,于是在整个配置文件中这个变量都是可见的,因此我们可以在 C 中直接引用这个变量而不用担心 Nginx 会报错。 182 | 183 | 下面是在命令行上用 C 工具访问这两个接口的结果: 184 | 185 | :bash 186 | $ curl 'http://localhost:8080/foo' 187 | foo = [] 188 | 189 | $ curl 'http://localhost:8080/bar' 190 | foo = [32] 191 | 192 | $ curl 'http://localhost:8080/foo' 193 | foo = [] 194 | 195 | 从这个例子我们可以看到,C 指令因为是在 C 196 | 中使用的,所以赋值操作只会在访问 C 的请求中执行。而请求 C 197 | 接口时,我们总是得到空的 C<$foo> 值,因为用户变量未赋值就输出的话,得到 198 | 的便是空字符串。 199 | 200 | 从这个例子我们可以窥见的另一个重要特性是,Nginx 变量名的可见范围虽然是整个 201 | 配置 202 | ,但每个请求都有所有变量的独立副本,或者说都有各变量用来存放值的容器的独立副本, 203 | 彼此互不干扰。比如前面我们请求了 C 204 | 接口后,C<$foo> 变量被赋予了值 C<32>,但它丝毫不会影响后续对 C 205 | 接口的请求所对应的 C<$foo> 值(它仍然是空的!),因为各个请求都有自己独 206 | 立的 C<$foo> 207 | 变量的副本。 208 | 209 | 对于 Nginx 新手来说,最常见的错误之一,就是将 Nginx 变量理解成某种 210 | 在请求 211 | 之间全局共享的东西,或者说“全局变量”。而事实上,Nginx 变量的生命期是不 212 | 可能跨越请求边界的。 213 | 214 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables02.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(二) = 2 | 3 | 关于 Nginx 变量的另一个常见误区是认为变量容器的生命期,是与 4 | C 配置块绑定的。其实不然。我们来看一个涉及“内部跳转”的例 5 | 子: 6 | 7 | :nginx 8 | server { 9 | listen 8080; 10 | 11 | location /foo { 12 | set $a hello; 13 | echo_exec /bar; 14 | } 15 | 16 | location /bar { 17 | echo "a = [$a]"; 18 | } 19 | } 20 | 21 | 这里我们在 C 中,使用第三方模块 L 22 | 提供的 L 配置指令,发起到 C 的“内部跳转”。所谓“内部跳转”,就是在处理请求的过程中,于服务器内 24 | 部,从一个 C 跳转到另一个 C 的过程 25 | 。这不同于利用 HTTP 状态码 C<301> 和 C<302> 所进行的“ 26 | 外部跳转”, 27 | 因为后者是由 HTTP 客户端配合进行跳转的,而且在客户端,用户可以通过浏览器地 28 | 址栏 29 | 这样的界面,看到请求的 URL 地址发生了变化。内部跳转和 C(或 30 | C)中的 C 命令很像,都是“有去无回”。另一个相近的例 31 | 子是 32 | C 语言中的 C 语句。 33 | 34 | 既然是内部跳转,当前正在处理的请求就还是原来那个,只是当前的 C 35 | 发生了变化,所以还是原来的那一套 Nginx 变量的容器副本。对应到上例,如果我 36 | 们请求的是 C 这个接口,那么整个工作流程是这样的:先在 C 中通过 L 指令将 C<$a> 变 38 | 量的值赋为字符串 C,然后通过 L 39 | 指令发起内部跳转,又进入到 C 中,再输出 C<$a> 40 | 变量的值。因为 C<$a> 还是原来的 C<$a>,所以我们可以期望得到 C 41 | 这行输出。测试证实了这一点: 42 | 43 | :bash 44 | $ curl localhost:8080/foo 45 | a = [hello] 46 | 47 | 但如果我们从客户端直接访问 C 接口,就会得到空的 C<$a> 变 48 | 量的值,因为它依赖于 C 来对 C<$a> 进行初 49 | 始化。 50 | 51 | 从上面这个例子我们看到,一个请求在其处理过程中,即使经历多个不同的 C 52 | 配置块,它使用的还是同一套 Nginx 变量的副本。这里,我们也首次涉及 53 | 到了“内部跳转”这个概念。值得一提的是,标准 L 模 54 | 块的 L 配置指令其实也可以发起“内部 55 | 跳转”,例如上面那个例子用 L 配 56 | 置指令可以改写成下面这样的形式: 57 | 58 | :nginx 59 | server { 60 | listen 8080; 61 | 62 | location /foo { 63 | set $a hello; 64 | rewrite ^ /bar; 65 | } 66 | 67 | location /bar { 68 | echo "a = [$a]"; 69 | } 70 | } 71 | 72 | 其效果和使用 L 是完全相同的。后面我们 73 | 还会专门介绍这个 L 指令的更多用法, 74 | 比如发起 C<301> 和 C<302> 这样的“外部跳转”。 75 | 76 | 从上面这个例子我们看到,Nginx 变量值容器的生命期是与当前正在处理的请求绑定 77 | 的,而与 C 无关。 78 | 79 | 前面我们接触到的都是通过 L 指令隐式创建的 80 | Nginx 变量。这些变量我们一般称为“用户自定义变量”,或者更简单一些,“用户 81 | 变量”。既然有“用户自定义变量”,自然也就有由 Nginx 核心和各个 Nginx 82 | 模块提供的“预定义变量”,或者说“内建变量”(builtin variables)。 83 | 84 | Nginx 内建变量最常见的用途就是获取关于请求或响应的各种信息。例如由 L 85 | 模块提供的内建变量 L,可以用来获取当前请求的 URI(经 86 | 过解码,并 87 | 且不含请求参数),而 L 则用来获 88 | 取请求最原始的 URI 89 | (未经解码,并且包含请求参数)。请看下面这个例子: 90 | 91 | :nginx 92 | location /test { 93 | echo "uri = $uri"; 94 | echo "request_uri = $request_uri"; 95 | } 96 | 97 | 这里为了简单起见,连 C 配置块也省略了,和前面 98 | 所有示例一样,我们监听的依然是 C<8080> 端口。在这个例子里,我们把 L 99 | 和 L 的值输出到响应体中去。下面 100 | 我们用不同的请求来 101 | 测试一下这个 C 接口: 102 | 103 | :bash 104 | $ curl 'http://localhost:8080/test' 105 | uri = /test 106 | request_uri = /test 107 | 108 | $ curl 'http://localhost:8080/test?a=3&b=4' 109 | uri = /test 110 | request_uri = /test?a=3&b=4 111 | 112 | $ curl 'http://localhost:8080/test/hello%20world?a=3&b=4' 113 | uri = /test/hello world 114 | request_uri = /test/hello%20world?a=3&b=4 115 | 116 | 另一个特别常用的内建变量其实并不是单独一个变量,而是有无限多变种的一群变量,即名 117 | 字以 C 118 | 开头的所有变量,我们估且称之为 L<$arg_XXX> 变量群。一个例子是 C<$arg_name>, 119 | 这个变量的值是当前请求名为 120 | C 的 URI 参数的值,而且还是未解码的原始形式的值。我们来看一个 121 | 比较完整的示例: 122 | 123 | :nginx 124 | location /test { 125 | echo "name: $arg_name"; 126 | echo "class: $arg_class"; 127 | } 128 | 129 | 然后在命令行上使用各种参数组合去请求这个 C 接口: 130 | 131 | :bash 132 | $ curl 'http://localhost:8080/test' 133 | name: 134 | class: 135 | 136 | $ curl 'http://localhost:8080/test?name=Tom&class=3' 137 | name: Tom 138 | class: 3 139 | 140 | $ curl 'http://localhost:8080/test?name=hello%20world&class=9' 141 | name: hello%20world 142 | class: 9 143 | 144 | 其实 C<$arg_name> 不仅可以匹配 C 参数,也可以匹配 145 | C 参数,抑或是 C,等等: 146 | 147 | $ curl 'http://localhost:8080/test?NAME=Marry' 148 | name: Marry 149 | class: 150 | 151 | $ curl 'http://localhost:8080/test?Name=Jimmy' 152 | name: Jimmy 153 | class: 154 | 155 | Nginx 会在匹配参数名之前,自动把原始请求中的参数名调整为全部小写的形式。 156 | 157 | 如果你想对 URI 参数值中的 C<%XX> 这样的编码序列进行解码,可以使用第 158 | 三方 L 159 | 模块提供的 L 配 160 | 置指令: 161 | 162 | :nginx 163 | location /test { 164 | set_unescape_uri $name $arg_name; 165 | set_unescape_uri $class $arg_class; 166 | 167 | echo "name: $name"; 168 | echo "class: $class"; 169 | } 170 | 171 | 现在我们再看一下效果: 172 | 173 | :bash 174 | $ curl 'http://localhost:8080/test?name=hello%20world&class=9' 175 | name: hello world 176 | class: 9 177 | 178 | 空格果然被解码出来了! 179 | 180 | 从这个例子我们同时可以看到,这个 L 181 | 指令也 182 | 像 L 指令那样,拥有自动创建 Nginx 变 183 | 量的功能。后面我们还会专 184 | 门介绍到 L 模块。 185 | 186 | 像 L<$arg_XXX> 这种类型的变量拥有无穷无尽种可能的名字,所以它们 187 | 并不对应任何存放值的容器。而且这种变量在 Nginx 核心中是经过特别处理的, 188 | 第三方 Nginx 模块是不能提供这样充满魔法的内建变量的。 189 | 190 | 类似 L<$arg_XXX> 的内建变量还有不少,比如用来取 cookie 值的 191 | L<$cookie_XXX> 变量群,用来取请求头的 L<$http_XXX> 192 | 变量群,以及用来取响应头的 L<$sent_http_XXX> 变量群。这里就不 193 | 一一介绍了,感兴趣的读者可以参考 L 模块的官方文 194 | 档。 195 | 196 | 需要指出的是,许多内建变量都是只读的,比如我们刚才介绍的 L 197 | 和 L. 198 | 对只读变量进行赋值是应当绝对避免的,因为会有意想不到的后果,比如: 199 | 200 | :nginx 201 | ? location /bad { 202 | ? set $uri /blah; 203 | ? echo $uri; 204 | ? } 205 | 206 | 这个有问题的配置会让 Nginx 在启动的时候报出一条令人匪夷所思的错误 207 | : 208 | 209 | [emerg] the duplicate "uri" variable in ... 210 | 211 | 如果你尝试改写另外一些只读的内建变量,比如 L<$arg_XXX> 变量,在 212 | 某些 Nginx 的版本中甚至可能导致进程崩溃。 213 | 214 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables03.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(三) = 2 | 3 | 也有一些内建变量是支持改写的,其中一个例子是 L. 4 | 这个变量在读取时返回当前请求的 URL 参数串(即请求 URL 中问号后面的部分 5 | ,如果有的话 6 | ),而在赋值时可以直接修改参数串。我们来看一个例子: 7 | 8 | :nginx 9 | location /test { 10 | set $orig_args $args; 11 | set $args "a=3&b=4"; 12 | 13 | echo "original args: $orig_args"; 14 | echo "args: $args"; 15 | } 16 | 17 | 这里我们把原始的 URL 参数串先保存在 C<$orig_args> 变量中,然 18 | 后通过改写 L 变量来修改当前的 URL 19 | 参数串,最后我们用 L 20 | 指令分别输出 C<$orig_args> 和 L 21 | 变量的值。接下来我们这样来测试这个 C 22 | 接口: 23 | 24 | :bash 25 | $ curl 'http://localhost:8080/test' 26 | original args: 27 | args: a=3&b=4 28 | 29 | $ curl 'http://localhost:8080/test?a=0&b=1&c=2' 30 | original args: a=0&b=1&c=2 31 | args: a=3&b=4 32 | 33 | 在第一次测试中,我们没有设置任何 URL 参数串,所以输出 C<$orig_args> 34 | 变量的值时便得到空。而在第一次和第二次测试中,无论我们是否提供 URL 参数串, 35 | 参数串都 36 | 会在 C 中被强行改写成 C. 37 | 38 | 需要特别指出的是,这里的 L 变量和 39 | L<$arg_XXX> 一样 40 | ,也不再使用属于自己的存放值的容器。当我们读取 L 41 | 时,Nginx 42 | 会执行一小段代码,从 Nginx 核心中专门存放当前 URL 参数串的位置去读取 43 | 数据;而当我们改写 L 时,Nginx 44 | 会执行另一小段代码,对相 45 | 同位置进行改写。Nginx 的其他部分在需要当前 URL 参数串的时候,都 46 | 会从那个位置去读数据,所以我们对 L 47 | 的修改会影响到所有部分的功 48 | 能。我们来看一个例子: 49 | 50 | :nginx 51 | location /test { 52 | set $orig_a $arg_a; 53 | set $args "a=5"; 54 | echo "original a: $orig_a"; 55 | echo "a: $arg_a"; 56 | } 57 | 58 | 这里我们先把内建变量 C<$arg_a> 的值,即原始请求的 URL 参数 C 59 | 的值,保存在用户变量 C<$orig_a> 中,然后通过对内建变量 L 60 | 进行赋值,把当前请求的参数串改写为 C ,最后再用 L 61 | 指令分别输出 C<$orig_a> 和 C<$arg_a> 变量的值。因为对内建 62 | 变量 L 的修改会直接导致当前请求的 63 | URL 参数串发生变化,因此内建变量 L<$arg_XXX> 自然也会随之变 64 | 化。测试的结果证实了这一点: 65 | 66 | :bash 67 | $ curl 'http://localhost:8080/test?a=3' 68 | original a: 3 69 | a: 5 70 | 71 | 我们看到,因为原始请求的 URL 参数串是 C, 所以 C<$arg_a> 72 | 最初的值为 C<3>, 但随后通过改写 L 73 | 变量,将 URL 参数串又强行修改为 C, 所以最终 C<$arg_a> 74 | 的值又自动变为了 C<5>. 75 | 76 | 我们再来看一个通过修改 C<$args> 变量影响标准的 HTTP 代理模块 L 77 | 的例子: 78 | 79 | :nginx 80 | server { 81 | listen 8080; 82 | 83 | location /test { 84 | set $args "foo=1&bar=2"; 85 | proxy_pass http://127.0.0.1:8081/args; 86 | } 87 | } 88 | 89 | server { 90 | listen 8081; 91 | 92 | location /args { 93 | echo "args: $args"; 94 | } 95 | } 96 | 97 | 这里我们在 C 配置块中定义了两个虚拟主机。第一个虚拟主机监听 8080 98 | 端口,其 C 接口自己通过改写 L 99 | 变量,将当前请求的 URL 参数串无条件地修改为 C. 100 | 然后 C 接口再通过 L 模块的 L 101 | 指令配置了一个反向代理,指向本机的 8081 端口上的 HTTP 服务 C. 102 | 默认情况下,L 模块在转发 HTTP 请求到远方 HTTP 103 | 服务的时候,会自动把当前请求的 URL 参数串也转发到远方。 104 | 105 | 而本机的 8081 端口上的 HTTP 服务正是由我们定义的第二个虚拟主机来提供 106 | 的。我们在第二个虚拟主机的 C 中利用 L 107 | 指令输出当前请求的 URL 参数串,以检查 C 接口 108 | 通过 L 模块实际转发过来的 URL 请求参数串。 109 | 110 | 我们来实际访问一下第一个虚拟主机的 C 接口: 111 | 112 | :bash 113 | $ curl 'http://localhost:8080/test?blah=7' 114 | args: foo=1&bar=2 115 | 116 | 我们看到,虽然请求自己提供了 URL 参数串 C,但在 C 中,参数串被强行改写成了 C. 接着 118 | 经由 L 指令将我们被改写掉的参数串 119 | 转发给了第二个虚拟主 120 | 机上配置的 C 接口,然后再把 C 接口的 URL 121 | 参数串输出。事实 122 | 证明,我们对 L 变量的赋值操作,也成 123 | 功影响到了 L 模块的行为。 124 | 125 | 在读取变量时执行的这段特殊代码,在 Nginx 中被称为“取处理程序”(get 126 | handler);而 127 | 改写变量时执行的这段特殊代码,则被称为“存处理程序”(set handler)。 128 | 不同的 129 | Nginx 模块一般会为它们的变量准备不同的“存取处理程序”,从而让这些变量的行 130 | 为充满魔法。 131 | 132 | 其实这种技巧在计算世界并不鲜见。比如在面向对象编程中,类的设计者一般不 133 | 会把类的成员变量直接暴露给类的用户,而是另行提供两个方法(method),分别用 134 | 于该成员变量的读 135 | 操作和写操作,这两个方法常常被称为“存取器”(accessor)。下面是 C++ 136 | 语言中的一个例子: 137 | 138 | :cpp 139 | #include 140 | using namespace std; 141 | 142 | class Person { 143 | public: 144 | const string get_name() { 145 | return m_name; 146 | } 147 | 148 | void set_name(const string name) { 149 | m_name = name; 150 | } 151 | 152 | private: 153 | string m_name; 154 | }; 155 | 156 | 在这个名叫 C 的 C++ 类中,我们提供了 C 157 | 和 C 这两个公共方法,以作为私有成员变量 C 158 | 的“存取器”。 159 | 160 | 这样设计的 161 | 好处是显而易见的。类的设计者可以在“存取器”中执行任意代码,以实现所需的业务逻辑 162 | 以及“ 163 | 副作用”,比如自动更新与当前成员变量存在依赖关系的其他成员变量,抑或是直接修改某 164 | 个与当前对象相关联的数据库表中的对应字段。而对于后一种情况,也许“存取器”所 165 | 对应的 166 | 成员变量压根就不存在,或者即使存在,也顶多扮演着数据缓存的角色,以缓解被代理数 167 | 据库的访问压力。 168 | 169 | 与面向对象编程中的“存取器”概念相对应,Nginx 变量也是支持绑定“存取处理程 170 | 序”的。Nginx 模块在创建变量时,可以选择是否为变量分配存放值的容器,以 171 | 及是否自己提供与读写操作相对应的“存取处理程序”。 172 | 173 | 不是所有的 Nginx 变量都拥有存放值的容器。拥有值容器的变量在 Nginx 174 | 核心中被称为“被索引的”(indexed);反之,则被称为“未索引的”(non-indexed)。 175 | 176 | 我们前面在 L 中已经知道,像 L<$arg_XXX> 这样具有无数变种的变量群,是“未索 177 | 引的”。当读取这样的变量时,其实是它的“取处理程序”在起作用,即实时扫描当前请求 178 | 的 URL 参数串,提取出变量名所指定的 URL 参数的值。很多新手都会对 L<$arg_XXX> 179 | 的实现方式产生误解,以为 Nginx 会事先解析好当前请求的所有 URL 参数, 180 | 并且把相关的 L<$arg_XXX> 变量的值都事先设置好。然而事实并非如此,Nginx 181 | 根本不会事先就解析好 URL 参数串,而是在用户读取某个 L<$arg_XXX> 182 | 变量时,调用其“取处理程序”,即时去扫描 URL 参数串。类似地,内建变量 L<$cookie_XXX> 183 | 也是通过它的“取处理程序”,即时去扫描 C 请求头中的相关定义的 184 | 。 185 | 186 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables04.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(四) = 2 | 3 | 在设置了“取处理程序”的情况下,Nginx 变量也可以选择将其值容器用作缓存,这 4 | 样在多次读取变量的时候,就只需要调用“取处理程序”计算一次。我们下面就来看一个这 5 | 样的例子: 6 | 7 | :nginx 8 | map $args $foo { 9 | default 0; 10 | debug 1; 11 | } 12 | 13 | server { 14 | listen 8080; 15 | 16 | location /test { 17 | set $orig_foo $foo; 18 | set $args debug; 19 | 20 | echo "original foo: $orig_foo"; 21 | echo "foo: $foo"; 22 | } 23 | } 24 | 25 | 这里首次用到了标准 L 模块的 L 26 | 配置指令,我们有必要在此介绍一下。C 在英文中除了“地图”之外,也有“ 27 | 映射”的意思。比方说,中学数学里讲的“函数”就是一种“映射”。而 Nginx 的 28 | 这个 29 | L 指令就可以用于定义两个 Nginx 变量之间的映 30 | 射关系,或者说是函数关系。回到上面这个例子,我们用 L 31 | 指令定义了用户变量 C<$foo> 与 L 32 | 内建变量之间的映射关系。特 33 | 别地,用数学上的函数记法 C 来说,我们的 C<$args> 34 | 就是“自变量” C,而 C<$foo> 则是“因变量” C,即 C<$foo> 35 | 的值是由 L 的值来 36 | 决定的,或者按照书写顺序可以说,我们将 L 变量 37 | 的值映射到了 C<$foo> 变量上。 38 | 39 | 现在我们再来看 L 指令定义的映射规则: 40 | 41 | :nginx 42 | map $args $foo { 43 | default 0; 44 | debug 1; 45 | } 46 | 47 | 花括号中第一行的 C 是一个特殊的匹配条件,即当其他条件都不匹 48 | 配的 49 | 时候,这个条 50 | 件才匹配。当这个默认条件匹配时,就把“因变量” C<$foo> 映射到值 C<0>. 51 | 而花括号中第二行的意思是说,如果“自变量” C<$args> 精确匹配了 C 52 | 这个字符串,则把“因变量” C<$foo> 映射到值 C<1>. 将这两行合起来 53 | ,我们就得到如下完整的映射规则:当 L 54 | 的值等于 C 的时候,C<$foo> 变量的值就是 C<1>,否则 55 | C<$foo> 的值就为 C<0>. 56 | 57 | 明白了 L 指令的含义,再来看 C. 在那里,我们先把当前 C<$foo> 变量的值保存在另一个用户变 59 | 量 C<$orig_foo> 中,然后再强行把 L 60 | 的值改写为 C,最后我们再用 L 61 | 指令分别输出 C<$orig_foo> 和 C<$foo> 的值。 62 | 63 | 从逻辑上看,似乎当我们强行改写 L 的 64 | 值为 C 65 | 之后,根据先前的 L 映射规则,C<$foo> 66 | 变量此时的值应当自动调整为字符串 C<1>, 而不论 C<$foo> 原先的值是 67 | 怎 68 | 样的。然而测试结果并非如此: 69 | 70 | :bash 71 | $ curl 'http://localhost:8080/test' 72 | original foo: 0 73 | foo: 0 74 | 75 | 第一行输出指示 C<$orig_foo> 的值为 C<0>,这正是我们期望的: 76 | 上面这个请求并没有提供 URL 参数串,于是 L 77 | 最初的取值 78 | 就是空,再根据我们先前定义的映射规则,C<$foo> 79 | 变量在第一次被读取时的值就应当是 C<0>(即匹配默认的那个 C 80 | 条件)。 81 | 82 | 而第二行输出显示,在强行改写 L 83 | 变量的值为字符串 C 之后,C<$foo> 的条件仍然是 C<0> 84 | ,这显然不符合映射规则,因为当 L 为 85 | C 时,C<$foo> 的值应当是 C<1>. 这究竟是为什么呢? 86 | 87 | 其实原因很简单,那就是 C<$foo> 变量在第一次读取时,根据映射规则计算出的 88 | 值被缓存住了。 89 | 刚才我们说过,Nginx 模块可以为其创建的变量 90 | 选择使用值容器,作为其“取处理程序”计算结果的缓存。显然,L 91 | 模 92 | 块认为变量间的映射计算足够昂贵,需要自动将因变量的计算结果缓存下来,这样在当前 93 | 请求的处理过程中如果再次读取这个因变量,Nginx 就可以直接返回缓存住的结果, 94 | 而不 95 | 再调用该变量的“取处理程序”再行计算了。 96 | 97 | 为了进一步验证这一点,我们不妨在请求中直接指定 URL 参数串为 C: 98 | 99 | :bash 100 | $ curl 'http://localhost:8080/test?debug' 101 | original foo: 1 102 | foo: 1 103 | 104 | 我们看到,现在 C<$orig_foo> 的值就成了 C<1>,因为变量 C<$foo> 105 | 在第一次被读取时,自变量 L 的值就是 106 | C,于是按照映射规则,“取处理程序”计算返回的值便是 C<1>. 107 | 而后续再读取 C<$foo> 的值时,就总是得到被缓存住的 C<1> 这个结果, 108 | 而 109 | 不 110 | 论 L 后来变成什么样了。 111 | 112 | L 指令其实是一个比较特殊的例子,因为它可以为用户变量 113 | 注册“取处理程序”,而且用户可以自己定义这个“取处理程序”的计算规则。当然,此规 114 | 则在这里被限定为与另一个变量的映射关系。同时,也并非所有使用了“取处理程序”的变 115 | 量都会缓存结果,例如我们前面在 L 中已经看到 L<$arg_XXX> 116 | 并不会使用值容器进 117 | 行缓存。 118 | 119 | 类似 L 模块,标准的 L 等模块也一样使用 120 | 了变量值的缓存机制。 121 | 122 | 在上面的例子中,我们还应当注意到 L 指令是在 C 123 | 配置块之外,也就是在最外围的 C 配置块中定义的。很多读者可能会对此 124 | 感 125 | 到奇 126 | 怪,毕竟我们只是在 C 中用到了它。这倒不是因为 127 | 我们 128 | 不想把 C 语句直接挪到 C 配置块中,而是因为 129 | L 指令只能在 C 块中使用! 130 | 131 | 很多 Nginx 新手都会担心如此“全局”范围的 L 132 | 设置会让访问所有虚拟 133 | 主机的所有 C 接口的请求都执行一遍变量值的映射计算,然而事 134 | 实 135 | 并非如此。前面我们已经了解到 L 配置指令的工作原理是 136 | 为用户变量注册 “取处理程序”,并且实际的映射计算是在“取处理程序”中完成的,而 137 | “取 138 | 处理程序”只有在该用户变量被实际读取时才会执行(当然,因为缓存的存在,只在请 139 | 求生命期中的第一次读取中才被执行),所以对于那些根本没有用到相关变量的请求来说 140 | ,就根本不会执行任何的无用计算。 141 | 142 | 这种只在实际使用对象时才计算对象值的技术,在计算领域被称为“惰性求值”(lazy 143 | evaluation)。提供“惰性求值” 语义的编程语言并不多见,最经典的例子 144 | 便是 Haskell. 与之相对的便是“主动求值” (eager 145 | evaluation)。我们有幸在 Nginx 中也看到了“惰性求值”的例子 146 | ,但“主动求 147 | 值”语义其实在 Nginx 里面更为常见,例如下面这行再普通不过的 L 148 | 语句: 149 | 150 | :nginx 151 | set $b "$a,$a"; 152 | 153 | 这里会在执行 L 规定的赋值操作时,“主动”地 154 | 计算出变量 C<$b> 的值,而不会 155 | 将该求值计算延缓到变量 C<$b> 实际被读取的时候。 156 | 157 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables05.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(五) = 2 | 3 | 前面在 L 中我们已经了解到变量值容器的生命期是与请求绑 4 | 定的,但是我当时有意避开了“请求” 5 | 的正式定义。大家应当一直默认这里的“请求”都是指客户端发起的 HTTP 请求。 6 | 其实在 Nginx 世界里有两种类型的“请求”,一种叫做“主请求”(main request), 7 | 而另一种则叫做“子请求”(subrequest)。我们先来介绍一下它们。 8 | 9 | 所谓“主请求”,就是由 HTTP 客户端从 Nginx 外部发起的请求。我们前面 10 | 见到的所有例子都只涉及到“主请求”,包括 L 中那两个使 11 | 用 L 和 L 12 | 指令发起“内部跳转”的例子。 13 | 14 | 而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种 15 | 级联请求。“子请求”在外观上很像 HTTP 请求,但实现上却和 HTTP 协议乃 16 | 至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便 17 | 用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个 C 18 | 接口,然后由这些 C 接口通力协作,共同完成整个“主请求”。 19 | 当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求” 20 | ,甚至可以玩递归调用(即自己调用自己)。当一个请求发起一个“子请求”的时候,按照 21 | Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。 22 | 值得一提的是,Apache 服务器中其实 23 | 也有“子请求”的概念,所以来自 Apache 世界的读者对此应当不会感到陌生。 24 | 25 | 下面就来看一个使用了“子请求”的例子: 26 | 27 | :nginx 28 | location /main { 29 | echo_location /foo; 30 | echo_location /bar; 31 | } 32 | 33 | location /foo { 34 | echo foo; 35 | } 36 | 37 | location /bar { 38 | echo bar; 39 | } 40 | 41 | 这里在 C 中,通过第三方 L 42 | 模块的 L 指令分别发起到 C 43 | 和 C 这两个接口的 C 类型的“子请求”。由 L 44 | 发起的“子请求”,其执行是按照配置书写的顺序串行处理的,即只有当 C 45 | 请求处理完毕之后,才会接着处理 C 请求。这两个“子请求”的输出会按 46 | 执行顺序拼接起来,作为 C 接口的最终输出: 47 | 48 | :bash 49 | $ curl 'http://localhost:8080/main' 50 | foo 51 | bar 52 | 53 | 我们看到,“子请求”方式的通信是在同一个虚拟主机内部进行的,所以 Nginx 核 54 | 心在实现“子请求”的时候,就只调用了若干个 C 函数,完全不涉及任何网络或者 UNIX 55 | 套接字(socket)通信。我们由此可以看出“子请求”的执行效率是极高的。 56 | 57 | 回到先前对 Nginx 变量值容器的生命期的讨论,我们现在依旧可以说,它们的 58 | 生命期是与当前请求相关联的。每个请求都有所有变量值容器的独立副本,只不过当前 59 | 请求既可以是“主请求”,也可以是“子请求”。即便是父子请求之间,同名变量一般也不 60 | 会 61 | 相互干扰。让我们来通过一个小实验证明一下这个说法: 62 | 63 | :nginx 64 | location /main { 65 | set $var main; 66 | 67 | echo_location /foo; 68 | echo_location /bar; 69 | 70 | echo "main: $var"; 71 | } 72 | 73 | location /foo { 74 | set $var foo; 75 | echo "foo: $var"; 76 | } 77 | 78 | location /bar { 79 | set $var bar; 80 | echo "bar: $var"; 81 | } 82 | 83 | 在这个例子中,我们分别在 C,C 和 C 84 | 这三个 C 配置块中为同一名字的变量,C<$var>,分别设 85 | 置 86 | 了不同的值并予以输出。特别地,我们在 C 接口中,故意在调用过 87 | C 和 C 这两个“子请求”之后,再输出它自己的 C<$var> 88 | 变量的值。请求 C 接口的结果是这样的: 89 | 90 | :bash 91 | $ curl 'http://localhost:8080/main' 92 | foo: foo 93 | bar: bar 94 | main: main 95 | 96 | 显然,C 和 C 这两个“子请求”在处理过程中对变量 C<$var> 97 | 各自所做的修改都丝毫没有影响到“主请求” C. 于是这成功印证了 98 | “主请求”以及各个“子请求”都拥有不同的变量 C<$var> 的值容器副本 99 | 。 100 | 101 | 不幸的是,一些 Nginx 模块发起的“子请求”却会自动共享其“父请求”的变 102 | 量值容器,比如第三方模块 L. 下面是一个 103 | 例子: 104 | 105 | :nginx 106 | location /main { 107 | set $var main; 108 | auth_request /sub; 109 | echo "main: $var"; 110 | } 111 | 112 | location /sub { 113 | set $var sub; 114 | echo "sub: $var"; 115 | } 116 | 117 | 这里我们在 C 接口中先为 C<$var> 变量赋初值 C
, 118 | 然后使用 L 模块提供的配置指令 L, 119 | 发起一个到 C 接口的“子请求”,最后利用 L 120 | 指令输出变量 C<$var> 的值。而我们在 C 接口中则故意把 C<$var> 121 | 变量的值改写成 C. 访问 C
接口的结果如下: 122 | 123 | :bash 124 | $ curl 'http://localhost:8080/main' 125 | main: sub 126 | 127 | 我们看到,C 接口对 C<$var> 变量值的修改影响到了主请求 C. 128 | 所以 L 模块发起的“子请求”确实是与其“父 129 | 请求”共享一套 Nginx 变量的值容器。 130 | 131 | 对于上面这个例子,相信有读者会问:“为什么‘子请求’ C 的输出没 132 | 有出现在最终的输出里呢?”答案很简单,那就是因为 L 133 | 指令会自动忽略“子请求”的响应体,而只检查“子请求”的响应状态码。当状态码是 C<2XX> 134 | 的时候,L 指令会 135 | 忽略“子请求”而让 Nginx 继续处理当前的请求,否则它就会立即中断当前(主)请 136 | 求的执行,返回相应的出错页。在我们的例子中,C “子请求”只是使用 137 | L 指令作了一些输出,所以隐式地返回了指示正常的 138 | C<200> 状态码。 139 | 140 | 如 L 模块这样父子请求共享一套 Nginx 141 | 变量的行为,虽然可以让父子请求之间的数据双向传递变得极为容易,但是对于足 142 | 够复杂的配置,却也经常导致不少难于调试的诡异 bug. 因为用户时常不知道“父 143 | 请求”的某个 Nginx 变量的值,其实已经在它的某个“子请求”中被意外修改了。 144 | 诸如此类的因共享而导致的不好的“副作用”,让包括 L,L, 145 | 以及 L 在内的许多第三方模块都选择了禁用父子请求间的 146 | 变量共享。 147 | 148 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables06.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(六) = 2 | 3 | Nginx 内建变量用在“子请求”的上下文中时,其行为也会变得有些微妙。 4 | 5 | 前面在 L 中我们已经知道,许多内建变量都不是简单的“存 6 | 放值的容器”,它们一般会通过注册“ 7 | 存取处理程序”来表现得与众不同,而它们即使有存放值的容器,也只是用于缓存“存取处 8 | 理程序”的计算结果。我们之前讨论过的 L 变量正 9 | 是通过它的“取处理程序”来返回当前请求的 URL 参数串。因为当前请求也可以是“ 10 | 子请求”,所以在“子请求”中读取 L,其“取 11 | 处理程序”会很自然地返回当前“子请求”的参数串。我们来看这样的一个例子: 12 | 13 | :nginx 14 | location /main { 15 | echo "main args: $args"; 16 | echo_location /sub "a=1&b=2"; 17 | } 18 | 19 | location /sub { 20 | echo "sub args: $args"; 21 | } 22 | 23 | 这里在 C 接口中,先用 L 指 24 | 令输出当前请求的 L 变量的值,接着再用 L 25 | 指令发起子请求 C. 这里值得注意的是,我们在 L 26 | 语句中除了通过第一个参数指定“子请求”的 URI 之外,还提供了第二个参数,用以 27 | 指定该“子请求”的 URL 参数串(即 C)。最后我们定义了 28 | C 接口,在里面输出了一下 L 的值 29 | 。请求 C 接口的结果如下: 30 | 31 | :bash 32 | $ curl 'http://localhost:8080/main?c=3' 33 | main args: c=3 34 | sub args: a=1&b=2 35 | 36 | 显然,当 L 用在“主请求” C 37 | 中时 38 | ,输出的就是“主请求”的 URL 参数串,C;而当用在“子请求” C 39 | 中时,输出 40 | 的则是“子请求”的参数串,C。这种行为正符合我们的直觉。 41 | 42 | 与 L 类似,内建变量 L 43 | 用在“子请求”中时,其“取处理程序”也会正确返回当前“子请求”解析过的 URI: 44 | 45 | :nginx 46 | location /main { 47 | echo "main uri: $uri"; 48 | echo_location /sub; 49 | } 50 | 51 | location /sub { 52 | echo "sub uri: $uri"; 53 | } 54 | 55 | 请求 C 的结果是 56 | 57 | :bash 58 | $ curl 'http://localhost:8080/main' 59 | main uri: /main 60 | sub uri: /sub 61 | 62 | 这依然是我们所期望的。 63 | 64 | 但不幸的是,并非所有的内建变量都作用于当前请求。少数内建变量只作用于“主请求”, 65 | 比如由标准模块 L 提供的内建变量 L. 66 | 67 | 变量 L 在读取时,总是会得 68 | 到“主请求”的请求方法,比如 C、C 之类。我们来测试一 69 | 下: 70 | 71 | :nginx 72 | location /main { 73 | echo "main method: $request_method"; 74 | echo_location /sub; 75 | } 76 | 77 | location /sub { 78 | echo "sub method: $request_method"; 79 | } 80 | 81 | 在这个例子里,C 和 C 接口都会分别输出 L 82 | 的值。同时,我们在 C 接口里利用 L 83 | 指令发起一个到 C 接口的 C “子请求”。我们现在利用 84 | C 命令行工具来发起一个到 C 接口的 C 85 | 请求: 86 | 87 | :bash 88 | $ curl --data hello 'http://localhost:8080/main' 89 | main method: POST 90 | sub method: POST 91 | 92 | 这里我们利用 C 程序的 C<--data> 选项,指定 C 93 | 作为我们的请求体数据,同时 C<--data> 选项会自动让发送的请求使用 C 94 | 请求方法。测试结果证明了我们先前的 95 | 预言,L 变量即使在 C 96 | “子请求” C 中使用,得到的值依然是“主请求” C 97 | 的请求方法,C. 98 | 99 | 有的读者可能觉得我们在这里下的结论有些草率,因为上例是先在“主请求”里读取(并 100 | 输出)L 变量,然后才发“ 101 | 子请求”的,所以这些读者可能认为这并不能排除 L 102 | 在进入子请求之前就已经把第一次读到的值给缓存住,从而影响到后续子请求中的输出结 103 | 果。不过,这样的顾虑是多余的,因为我们前面在 L 中也特 104 | 别提到过,缓存所依赖的变量的值 105 | 容器,是与当前请求绑定的,而由 L 模块发起的“子请求”都 106 | 禁用了父子请求之间的变量共享,所以在上例中,L 107 | 内建变量即使真的使用了值容器作为缓存(事实上它也没有),它也不可能影响到 C 108 | 子请求。 109 | 110 | 为了进一步消除这部分读者的疑虑,我们不妨稍微修改一下刚才那个例子,将 C 111 | 接口输出 L 变量的时间推迟 112 | 到“子请求”执行完毕之后: 113 | 114 | :nginx 115 | location /main { 116 | echo_location /sub; 117 | echo "main method: $request_method"; 118 | } 119 | 120 | location /sub { 121 | echo "sub method: $request_method"; 122 | } 123 | 124 | 让我们重新测试一下: 125 | 126 | :bash 127 | $ curl --data hello 'http://localhost:8080/main' 128 | sub method: POST 129 | main method: POST 130 | 131 | 可以看到,再次以 C 方法请求 C 接口的结果与原先那 132 | 个例子完全 133 | 一致,除了父子请求的输出顺序颠倒了过来(因为我们在本例中交换了 C 134 | 接口中那两条输出配置指令的先后次序)。 135 | 136 | 由此可见,我们并不能通过标准的 L 137 | 变量取得“子请求”的请求方法。为了达到我们最初的目的,我们需要求助于第三方模块 138 | L 提供的内建变量 L: 139 | 140 | :nginx 141 | location /main { 142 | echo "main method: $echo_request_method"; 143 | echo_location /sub; 144 | } 145 | 146 | location /sub { 147 | echo "sub method: $echo_request_method"; 148 | } 149 | 150 | 此时的输出终于是我们想要的了: 151 | 152 | :bash 153 | $ curl --data hello 'http://localhost:8080/main' 154 | main method: POST 155 | sub method: GET 156 | 157 | 我们看到,父子请求分别输出了它们各自不同的请求方法,C 和 C. 158 | 159 | 类似 L,内建变量 L 160 | 一般也返回的是“主请求”未经解析过的 URL,毕竟“子请求”都是在 Nginx 161 | 内部发起的,并不存在所谓的“未解析的”原始形式。 162 | 163 | 如果真如前面那部分读者所担心的,内建变量的值缓存在共享变量的父子请求之间起了 164 | 作用, 165 | 这无疑是灾难性的。我们前面在 L 中已经看到 L 166 | 模块 167 | 发起的“子请求”是与其“父请求”共享一套变量的。下面是一个这样的可怕例子 168 | : 169 | 170 | :nginx 171 | map $uri $tag { 172 | default 0; 173 | /main 1; 174 | /sub 2; 175 | } 176 | 177 | server { 178 | listen 8080; 179 | 180 | location /main { 181 | auth_request /sub; 182 | echo "main tag: $tag"; 183 | } 184 | 185 | location /sub { 186 | echo "sub tag: $tag"; 187 | } 188 | } 189 | 190 | 这里我们使用久违了的 L 指令来把内建变量 L 191 | 的值映射到用户变量 C<$tag> 上。当 L 192 | 的值为 C 时,则赋予 C<$tag> 值 1,当 L 193 | 取值 C 时,则赋予 C<$tag> 值 2,其他情况都赋 C<0>. 194 | 接着,我们在 C 接口中先用 L 195 | 模块的 L 指令发 196 | 起到 C 接口的子请求,然后再输出变量 C<$tag> 的值。而在 197 | C 接口中,我们直接输出变量 C<$tag>. 猜猜看,如果我 198 | 们访问接口 C,将会得到什么样的输出呢? 199 | 200 | $ curl 'http://localhost:8080/main' 201 | main tag: 2 202 | 203 | 咦?我们不是分明把 C 这个值映射到 C<1> 上的么?为什么实际 204 | 输出的是 C 映射的结果 C<2> 呢? 205 | 206 | 其实道理很简单,因为我们的 C<$tag> 变量在“子请求” C 中 207 | 首先被读取,于是在那里计算出了值 C<2>(因 208 | 为 L 在那里取值 C,而根据 L 209 | 映射规则,C<$tag> 应当取值 C<2>),从此就被 C<$tag> 的值容 210 | 器给缓存住了。而 L 211 | 发起的“子请求”又是与“父请求”共享一套变量的,于是当 Nginx 的执行 212 | 流回到“父请求”输出 C<$tag> 变量的值时,Nginx 就直接返回缓存住的 213 | 结果 C<2> 214 | 了。这样的结果确实太意外了。 215 | 216 | 从这个例子我们再次看到,父子请求间的变量共享,实在不是一个好主意。 217 | 218 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables07.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(七) = 2 | 3 | 在 L 中我们提到过,Nginx 变量的值只有一种类型, 4 | 那 5 | 就是字符串,但是变量也有可能压根就不存在有意义的值。没有值的变量也有两种特殊的值 6 | :一种是“不合法”(invalid),另一种是“没找到”(not found)。 7 | 8 | 举例说来,当 Nginx 用户变量 C<$foo> 创建了却未被赋值时,C<$foo> 9 | 的值便是“不合法”;而如果当前请求的 URL 参数串中并没有提及 C 10 | 这个参数,则 L<$arg_XXX> 内建变量的值便是“没找到”。 11 | 12 | 无论是“不合法”也好,还是“没找到”也罢,这两种 Nginx 变量所拥有 13 | 的特 14 | 殊值,和空字符串("")这种取值是完全不同的,比如 JavaScript 语言中 15 | 也有专门 16 | 的 C 和 C 这两种特殊值,而 Lua 语言 17 | 中也有专门的 C 值: 18 | 它们既不等同于空字符串,也不等同于数字 C<0>,更不是布尔值 C. 19 | 其实 SQL 语言中的 C 也是类似的一种东西。 20 | 21 | 虽然前面在 L 中我们看到,由 L 22 | 指令创建的变量未初始化就用在“变量插值”中时,效果等同于空字符串,但那是因为 L 23 | 指令为它创建的变量自动注册了一个“取处理程序”,将“不合法”的变量值转换为空字符 24 | 串。 25 | 为了验证这一点,我们再重新看一下 L 中讨论过的那个例子 26 | : 27 | 28 | :nginx 29 | location /foo { 30 | echo "foo = [$foo]"; 31 | } 32 | 33 | location /bar { 34 | set $foo 32; 35 | echo "foo = [$foo]"; 36 | } 37 | 38 | 这里为了简单起见,省略了原先写出的外围 C 配置块。在这个例子 39 | 里,我们在 C 接口中用 L 指令 40 | 隐式地创建了 C<$foo> 变量这个名字,然后我们在 C 接口中不 41 | 对 C<$foo> 进行初始化就直接使用 L 指令 42 | 输出。我们当时测试 C 接口的结果是 43 | 44 | :bash 45 | $ curl 'http://localhost:8080/foo' 46 | foo = [] 47 | 48 | 从输出上看,未初始化的 C<$foo> 变量确实和空字符串的效果等同。但细心的读 49 | 者当时应该就已经注意到,对于上面这个请求,Nginx 的错误日志文件(一般文件名 50 | 叫做 F)中多出一行类似下面这样的警告: 51 | 52 | [warn] 5765#0: *1 using uninitialized "foo" variable, ... 53 | 54 | 这一行警告是谁输出的呢?答案是 L 指令为 C<$foo> 55 | 注册的“取处理程序”。当 C 接口中的 L 56 | 指令实 57 | 际执行的时候,它会对它的参数 C<"foo = [$foo]"> 进行“变量插值 58 | ”计算。于是,参数串中的 C<$foo> 变量会被读取,而 Nginx 会首先检 59 | 查其值容器里的取值,结果它看到了“不合法”这个特殊值,于是它这才决定继续调用 60 | C<$foo> 变量的“取处理程序”。于是 C<$foo> 变量的“取处理程序” 61 | 开始运行,它向 Nginx 的错误日志打印出上面那条警告消息,然后返回一个空字符 62 | 串作为 C<$foo> 的值,并从此缓存在 C<$foo> 的值容器中。 63 | 64 | 细心的读者会注意到刚刚描述的这个过程其实就是那些支持值缓存的内建变量的工作原理, 65 | 只不过 L 指令在这里借用了这套机制来处理未正 66 | 确初始化的 Nginx 变量。值得一提的是,只有“不合法”这个特殊值才会触发 Nginx 67 | 调用变量的“取处理程序”,而特殊值“没找到”却不会。 68 | 69 | 上面这样的警告一般会指示出我们的 Nginx 配置中存在变量名拼写错误,抑或是 70 | 在错误的场合使用了尚未初始化的变量。因为值缓存的存在,这条警告在一个请 71 | 求的生命期中也不会打印多次。当然,L 模块专门提供了一 72 | 条 L 73 | 配置指令可用于禁止这条警告日志。 74 | 75 | 刚才提到,内建变量 L<$arg_XXX> 在请求 URL 参数 C 76 | 并不存在时会返回特殊值“找不到”,但遗憾的是在 Nginx 原生配置语言(我们估 77 | 且这么称呼它)中是不能很方便地把它和空字符串区分开来的,比如: 78 | 79 | :nginx 80 | location /test { 81 | echo "name: [$arg_name]"; 82 | } 83 | 84 | 这里我们输出 C<$arg_name> 变量的值同时故意在请求中不提供 URL 85 | 参数 C: 86 | 87 | :bash 88 | $ curl 'http://localhost:8080/test' 89 | name: [] 90 | 91 | 我们看到,输出特殊值“找不到”的效果和空字符串是相同的。因为这一回是 Nginx 92 | 的“变量插值”引擎自动把“找不到”给忽略了。 93 | 94 | 那么我们究竟应当如何捕捉到“找不到”这种特殊值的踪影呢?换句话说,我们应当如何把 95 | 它和空字符串给区分开来呢?显然,下面这个请求中,URL 参数 C 是 96 | 有值的,而且其值应当是空字符串: 97 | 98 | :bash 99 | $ curl 'http://localhost:8080/test?name=' 100 | name: [] 101 | 102 | 但我们却无法将之和前面完全不提供 C 参数的情况给区分开。 103 | 104 | 幸运的是,通过第三方模块 L,我们可以轻松地在 Lua 代码中 105 | 做到这一点。 106 | 请看下面这个例子: 107 | 108 | :nginx 109 | location /test { 110 | content_by_lua ' 111 | if ngx.var.arg_name == nil then 112 | ngx.say("name: missing") 113 | else 114 | ngx.say("name: [", ngx.var.arg_name, "]") 115 | end 116 | '; 117 | } 118 | 119 | 这个例子和前一个例子功能上非常接近,除了我们在 C 接口中使用了 120 | L 121 | 模块的 L 配置指令,嵌入了一小 122 | 段我 123 | 们自己的 Lua 代码来对 Nginx 变量 C<$arg_name> 的特殊值 124 | 进行 125 | 判断。在这个例子中,当 C<$arg_name> 的值为“没找到”(或者“ 126 | 不合法”)时,C 127 | 接口会输出 C 这一行结果: 128 | 129 | :bash 130 | $ curl 'http://localhost:8080/test' 131 | name: missing 132 | 133 | 因为这是我们第一次接触到 L 模块,所以需要先简单介绍一下。L 134 | 模块将 Lua 语言解释器(或者 L 135 | 即时编译器)嵌入到了 Nginx 核 136 | 心中,从而可以让用户在 Nginx 核心中直接运行 Lua 语言编写的程序。我们 137 | 可以选择在 Nginx 不同的请求处理阶段插入我们的 Lua 代码。这些 Lua 138 | 代码既可以直接内联在 Nginx 配置文件中,也可以单独放置在外部 F<.lua> 139 | 源码文件(或者 Lua 字节码文件)里,然后在 Nginx 配置文件中引用这些 140 | 文件的路径。 141 | 142 | 回到上面这个例子,我们在 Lua 代码里引用 Nginx 变量都是通过 C 143 | 这个由 L 模块提供的 Lua 接口。比如引用 Nginx 144 | 变量 C<$VARIABLE> 时, 145 | 就在 Lua 代码里写作 L 146 | 就可以了。当 147 | Nginx 变量 C<$arg_name> 为特殊值“没找到”(或者“不合法”)时 148 | , 149 | C 在 Lua 世界中的值就是 C, 150 | 即 Lua 语言里的“空”(不同于 Lua 空字符串)。我们在 Lua 里输出 151 | 响应体内容的时候,则使用了 L 这个 Lua 152 | 函数,也是 L 模块提供的,功能上等价于 L 153 | 模块的 L 配置指令。 154 | 155 | 现在,如果我们提供空字符串取值的 C 参数,则输出就和刚才不相同了: 156 | 157 | :bash 158 | $ curl 'http://localhost:8080/test?name=' 159 | name: [] 160 | 161 | 在这种情况下,Nginx 变量 C<$arg_name> 的取值便是空字符串,这 162 | 既不是“没找到”,也不是“不合法”,因此在 Lua 里,C 163 | 就返回 Lua 空字符串(""),和刚才的 Lua C 值就完全区分开 164 | 了。 165 | 166 | 这种区分在有些应用场景下非常重要,比如有的 web service 接口会根 167 | 据 C 这个 URL 参数是否存在来决定是否按 C 属性 168 | 对数据集合进行过滤,而显然提供空字符串作为 C 参数的值,也会导致对 169 | 数 170 | 据集中取值为空串的记录进行筛选操作。 171 | 172 | 不过,标准的 L<$arg_XXX> 变量还是有一些局限,比如我们用下面这个 173 | 请求来测试刚才那个 C 接口: 174 | 175 | $ curl 'http://localhost:8080/test?name' 176 | name: missing 177 | 178 | 此时,C<$arg_name> 变量仍然读出“找不到”这个特殊值,这就明显有些违 179 | 反常识。此外,L<$arg_XXX> 变量在请求 URL 中有多个同名 C 180 | 参数时,就只会返回最先出现的那个 C 参数的值,而默默忽略掉其他实例: 181 | 182 | :bash 183 | $ curl 'http://localhost:8080/test?name=Tom&name=Jim&name=Bob' 184 | name: [Tom] 185 | 186 | 要解决这些局限,可以直接在 Lua 代码中使用 L 模块提供的 187 | L 函数。 188 | 189 | -------------------------------------------------------------------------------- /zh-cn/01-NginxVariables08.tut: -------------------------------------------------------------------------------- 1 | = Nginx 变量漫谈(八) = 2 | 3 | 与 L<$arg_XXX> 类似,我们在 L 中提到过的 4 | 内建变量 L<$cookie_XXX> 变量也会在名为 C 的 cookie 5 | 不存在时返回特殊值“没找到”: 6 | 7 | :nginx 8 | location /test { 9 | content_by_lua ' 10 | if ngx.var.cookie_user == nil then 11 | ngx.say("cookie user: missing") 12 | else 13 | ngx.say("cookie user: [", ngx.var.cookie_user, "]") 14 | end 15 | '; 16 | } 17 | 18 | 利用 C 命令行工具的 C<--cookie name=value> 19 | 选项可以指定 C 为当前请求携带的 cookie(通过添 20 | 加相应的 C 请求头)。下面是若干次测试结果: 21 | 22 | $ curl --cookie user=agentzh 'http://localhost:8080/test' 23 | cookie user: [agentzh] 24 | 25 | $ curl --cookie user= 'http://localhost:8080/test' 26 | cookie user: [] 27 | 28 | $ curl 'http://localhost:8080/test' 29 | cookie user: missing 30 | 31 | 我们看到,cookie C 不存在以及取值为空字符串这两种情况被很 32 | 好地区分开了:当 cookie C 不存在时,Lua 代码中的 C 33 | 返回了期望的 Lua C 值。 34 | 35 | 在 Lua 里访问未创建的 Nginx 用户变量时,在 Lua 里也会得到 C 36 | 值,而不会像先前的例子那样直接让 Nginx 拒绝加载配置: 37 | 38 | :nginx 39 | location /test { 40 | content_by_lua ' 41 | ngx.say("$blah = ", ngx.var.blah) 42 | '; 43 | } 44 | 45 | 这里假设我们并没有在当前的 F 配置文件中创建过用户变量 46 | C<$blah>,然后我们在 Lua 代码中通过 C 47 | 直接引用它。上面这个配置可以顺利启动,因为 Nginx 在加载配置时只会编译 L 48 | 配置指令指定的 Lua 代码而不会实际执行它,所以 Nginx 并不知道 Lua 49 | 代码里面引用了 C<$blah> 这个变量。于是我们在运行时也会得到 C 50 | 值。而 L 提供的 L 51 | 函数会自动把 Lua 的 C 值格式化为字符串 C<"nil"> 输出 52 | ,于是访问 C 接口的结果是: 53 | 54 | :bash 55 | curl 'http://localhost:8080/test' 56 | $blah = nil 57 | 58 | 这正是我们所期望的。 59 | 60 | 上面这个例子中另一个值得注意的地方是,我们在 L 61 | 配置指令的参数中提及了 C<$blah> 符号,但却并没有触发“变量插值”(否则 62 | Nginx 会在启动时抱怨 C<$blah> 未创建)。这是因为 L 63 | 配置指令并不支持参数的“变量插值”功能。我们前面在 L 64 | 中提到过,配置指令的参数是否允许“变量插值”,其实取决于该指令的实现模块。 65 | 66 | 设计返回“不合法”这一特殊值的例子是困难的,因为我们前面在 L 中已经看到,由 L 指令创建的变量在未初 68 | 始化时确实是“不合法”,但一旦尝试读取它们时,Nginx 就会自动调用其“取 69 | 处理程序”,而它们的“取处理程序”会自动返回空字符串并将之缓存住。于是我们最终得 70 | 到的是完全合法的空字符串。下面这个使用了 Lua 代码的例子证明了这一点: 71 | 72 | :nginx 73 | location /foo { 74 | content_by_lua ' 75 | if ngx.var.foo == nil then 76 | ngx.say("$foo is nil") 77 | else 78 | ngx.say("$foo = [", ngx.var.foo, "]") 79 | end 80 | '; 81 | } 82 | 83 | location /bar { 84 | set $foo 32; 85 | echo "foo = [$foo]"; 86 | } 87 | 88 | 请求 C 接口的结果是: 89 | 90 | :bash 91 | $ curl 'http://localhost:8080/foo' 92 | $foo = [] 93 | 94 | 我们看到在 Lua 里面读取未初始化的 Nginx 变量 C<$foo> 时 95 | 得到的是空字符串。 96 | 97 | 最后值得一提的是,虽然前面反复指出 Nginx 变量只有字符串这一种数据类 98 | 型,但这并不能阻止像 L 这样的第三方模块让 99 | Nginx 变量也能存放数组类型的值。下面就是这样的 100 | 一个例子: 101 | 102 | :nginx 103 | location /test { 104 | array_split "," $arg_names to=$array; 105 | array_map "[$array_it]" $array; 106 | array_join " " $array to=$res; 107 | 108 | echo $res; 109 | } 110 | 111 | 这个例子中使用了 L 模块的 C、 112 | C 和 C 这三条配置指令,其含 113 | 义很接近 Perl 语言中的内建函数 C、C 和 C(当 114 | 然,其他脚本语言也有类似的等价物)。我们来看看访问 C 接口的结果 115 | : 116 | 117 | :bash 118 | $ curl 'http://localhost:8080/test?names=Tom,Jim,Bob' 119 | [Tom] [Jim] [Bob] 120 | 121 | 我们看到,使用 L 模块可以很方便地处理这样具有不 122 | 定个数的组成元素的输入数据,例如此例中的 C URL 参数值就是由 123 | 不 124 | 定个数的逗号分隔的名字所组成。不过,这种类型的 125 | 复杂任务通过 L 来做通常会更灵活而且更容易维护。 126 | 127 | 至此,本系列教程对 Nginx 变量的介绍终于可以告一段落了。我们在这个过程中接 128 | 触到了许多标准的和第三方的 Nginx 模块,这些模块让我们得以很轻松地构造出许 129 | 多有趣的 130 | 小例子,从而可以深入探究 Nginx 变量的各种行为和特性。在后续的教程中,我们 131 | 还会 132 | 有很多机会与这些模块打交道。 133 | 134 | 通过前面讨论过的众多例子,我们应当已经感受到 Nginx 变量在 Nginx 135 | 配置语言中所扮演的重要角色:它是获取 Nginx 中各种信息(包括当前请求的信息 136 | )的主要途径和载体,同时也是各个模块之间传递数据的主要媒介之一。在后续的教程 137 | 中,我们会经常看到 Nginx 变量的身影,所以现在很好地理解它们是非常重要的。 138 | 139 | 在下一个系列的教程,即 L 中,我们将深入探讨 Nginx 配置指令的执行顺序以及请求的各个处理阶段,因 140 | 为 141 | 很多 142 | Nginx 用户都搞不清楚他们书写的众多配置指令之间究竟是按照何种时间顺序执行 143 | 的,也搞不懂为什么这些指令实际执行的顺序经常和配置文件里的书写顺序大相径庭。 144 | 145 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder01.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(一) = 2 | 3 | 大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是 4 | 当同一个 C 配置块使用了多个 Nginx 模块的 5 | 配置指令时,这些指令的执行顺序很可能会 6 | 跟它们的书写顺序大相径庭。于是许多人选择了“试错法”,然后他们的配置文件 7 | 就时常被改得一片狼藉。这个系列的教程就旨在帮助读者逐步地理解这些配置指令背后的执 8 | 行时间和 9 | 先后顺序的奥秘。 10 | 11 | 现在就来看这样一个令人困惑的例子: 12 | 13 | :nginx 14 | ? location /test { 15 | ? set $a 32; 16 | ? echo $a; 17 | ? 18 | ? set $a 56; 19 | ? echo $a; 20 | ? } 21 | 22 | 从这个例子的本意来看,我们期望的输出是一行 C<32> 和一行 C<56>, 23 | 因为我们第一次用 L 配置指令输出了 C<$a> 24 | 变量的值以后,又紧接着使用 L 配置指令修改了 25 | C<$a>. 然而不幸的是,事实并非如此: 26 | 27 | :bash 28 | $ curl 'http://localhost:8080/test' 29 | 56 30 | 56 31 | 32 | 我们看到,语句 C 似乎在第一条 C 语 33 | 句之前就执行过了。这究竟是为什么呢?难道我们遇到了 Nginx 中的一个 bug? 34 | 35 | 显然,这里并没有 Nginx 的 bug;要理解这里发生的事情,就首先需要 36 | 知道 Nginx 处理每一个用户请求时,都是按照若干个不同阶段(phase) 37 | 依次处理的。 38 | 39 | Nginx 的请求处理阶段共有 11 个之多,我们先介绍其中 3 个比较常见的。 40 | 按照它们 41 | 执行时的先后顺序,依次是 C 阶段、C 阶段 42 | 以及 C 阶段(后面我们还有机会见到其他更多的处理阶段)。 43 | 44 | 所有 Nginx 模块提供的配置指令一般只会注册并运行在其中的某一个处理阶段。比 45 | 如上例中的 L 指令就是在 C 46 | 阶段运行的,而 L 指令就只会在 C 47 | 阶段运行。前面我们已经知道,在单个请求的处理过程中,C 阶段总 48 | 是在 C 阶段之前执行,因此属于 C 阶段的 49 | 配置指令也总是会无条件地在 C 阶段的配置指令之前执行。于是 50 | 在同一个 C 配置块中,L 51 | 指令总是会在 L 指令之前执行,即使我们在配置文件 52 | 中有意把 L 53 | 语句写在 L 语句的后面。 54 | 55 | 回到刚才那个例子, 56 | 57 | :nginx 58 | set $a 32; 59 | echo $a; 60 | 61 | set $a 56; 62 | echo $a; 63 | 64 | 实际的执行顺序应当是 65 | 66 | :nginx 67 | set $a 32; 68 | set $a 56; 69 | echo $a; 70 | echo $a; 71 | 72 | 即先在 C 阶段执行完这里的两条 L 73 | 赋值语句,然后再在后面的 C 阶段依次执行那两条 L 74 | 语句。分属两个不同处理阶段的配置指令之间是不能穿插着运行的。 75 | 76 | 为了进一步验证这一点,我们不妨借助 Nginx 的“调试日志”(debug log)来一窥 77 | Nginx 的实际执行过程。 78 | 79 | 因为这是我们第一次提及 Nginx 的“调试日志”,所以有必要先简单介绍一下它的 80 | 启用 81 | 方法。“调试日志”默认是禁用的,因为它会引入比较大的运行时开销,让 Nginx 服务 82 | 器显著变慢。一般我们需要重新编译和构造 Nginx 可执行文件,并且在调用 Nginx 83 | 源码包提供的 C<./configure> 脚本时传入 C<--with-debug> 84 | 命令行选项。例如我们下载完 Nginx 源码包后在 Linux 或者 Mac 85 | OS X 等系统上构建时,典型的步骤是这样的: 86 | 87 | :bash 88 | tar xvf nginx-1.0.10.tar.gz 89 | cd nginx-1.0.10/ 90 | ./configure --with-debug 91 | make 92 | sudu make install 93 | 94 | 如果你使用的是我维护的 L 95 | 软件包,则同样可以向 96 | 它的 C<./configure> 脚本传递 C<--with-debug> 97 | 命令行选项。 98 | 99 | 当我们启用 C<--with-debug> 选项重新构建好调试版的 Nginx 100 | 之后,还需要同时在配置文件中通过标准的 L 配置指令为 101 | 错误日 102 | 志使用 C 日志级别(这同时也是最低的日志级别): 103 | 104 | :nginx 105 | error_log logs/error.log debug; 106 | 107 | 这里重要的是 L 指令的第二个参数,C,而前 108 | 面第一个参数是错误日志文件的 109 | 路径,F. 当然,你也可以指定其他路径,但 110 | 后面 111 | 我们会检查这个文件的内容,所以请特别留意一下这里实际 112 | 配置的文件路径。 113 | 114 | 现在我们重新启动 Nginx(注意,如果 Nginx 可执行文件也被更新过,仅仅 115 | 让 Nginx 重新加载配置是不够的,需要关闭再启动 Nginx 主服务进程), 116 | 然后再请求一 117 | 下我们刚才那个示例接口: 118 | 119 | :bash 120 | $ curl 'http://localhost:8080/test' 121 | 56 122 | 56 123 | 124 | 现在可以检查一下前面配置的 Nginx 错误日志文件中的输出。因为文件中的输 125 | 出比较多(在我的机器上有 700 多行),所以不妨用 C 命令在终 126 | 端上过滤出我们感兴趣的部分: 127 | 128 | :bash 129 | grep -E 'http (output filter|script (set|value))' logs/error.log 130 | 131 | 在我机器上的输出是这个样子的(为了方便呈现,这里对 C 命令的实 132 | 际输出作 133 | 了一些简单的编辑,略去了每一行的行首时间戳): 134 | 135 | :text 136 | [debug] 5363#0: *1 http script value: "32" 137 | [debug] 5363#0: *1 http script set $a 138 | [debug] 5363#0: *1 http script value: "56" 139 | [debug] 5363#0: *1 http script set $a 140 | [debug] 5363#0: *1 http output filter "/test?" 141 | [debug] 5363#0: *1 http output filter "/test?" 142 | [debug] 5363#0: *1 http output filter "/test?" 143 | 144 | 这里需要稍微解释一下这些调试信息的具体含义。L 145 | 配置指令在实际运行时 146 | 会打印出两行以 C 起始的调试信息,其中第一行信息是 147 | L 语句中被赋予的值,而第二行则是 L 148 | 语句中被赋值的 Nginx 变量名。于是上面首先过滤出来的 149 | 150 | :text 151 | [debug] 5363#0: *1 http script value: "32" 152 | [debug] 5363#0: *1 http script set $a 153 | 154 | 这两行就对应我们例子中的配置语句 155 | 156 | :nginx 157 | set $a 32; 158 | 159 | 而接下来这两行调试信息 160 | 161 | :text 162 | [debug] 5363#0: *1 http script value: "56" 163 | [debug] 5363#0: *1 http script set $a 164 | 165 | 则对应配置语句 166 | 167 | :nginx 168 | set $a 56; 169 | 170 | 此外,凡在 Nginx 中输出响应体数据时,都会调用 Nginx 的所谓“ 171 | 输出过滤器”(output filter),我们一直在使用的 L 172 | 指令自然也不例外。而一旦调用 Nginx 的“输出过滤器”,便会产生类似下面这样 173 | 的调试信息: 174 | 175 | :text 176 | [debug] 5363#0: *1 http output filter "/test?" 177 | 178 | 当然,这里的 C<"/test?"> 部分对于其他接口可能会发生变化,因为它显示 179 | 的是当前请求 180 | 的 URI. 这样联系起来看,就不难发现,上例中的那两条 L 181 | 语句确实都是在那两条 L 语句之前执行的。 182 | 183 | 细心的读者可能会问,为什么这个例子明明只使用了两条 L 184 | 语句进行输出 185 | ,但却有三行 C 调试信息呢?其实,前 186 | 两行 C 信息确实分别对应那两条 187 | L 语句,而最后那一行信息则是对应 L 188 | 模块输出指示响应体末尾的结 189 | 束标记。正是为了输出这个特殊的结束标记,才会多出一次对 Nginx “输出过滤器 190 | ”的调用。包括 L 在内的许多模块在输出响应体数据流时都具 191 | 有此 192 | 种行为。 193 | 194 | 现在我们就不会再为前面那个例子输出两行一模一样的 C<56> 而感到惊讶了。我们 195 | 根本没有机会在第二条 L 语句之前用 L 196 | 输出。幸运的是,仍然可以借助一些小技巧来达到最初的目的: 197 | 198 | :nginx 199 | location /test { 200 | set $a 32; 201 | set $saved_a $a; 202 | set $a 56; 203 | 204 | echo $saved_a; 205 | echo $a; 206 | } 207 | 208 | 此时的输出便符合那个问题示例的初衷了: 209 | 210 | :bash 211 | $ curl 'http://localhost:8080/test' 212 | 32 213 | 56 214 | 215 | 这里通过引入新的用户变量 C<$saved_a>,在改写 C<$a> 之前及时 216 | 保存了 C<$a> 的初始值。而对于多条 L 217 | 指令而 218 | 言,它们之间的执行顺序是由 L 模块来保证与书写 219 | 顺序相一致的。 220 | 同理,L 模块自身也会保证它的多条 L 221 | 指令之间的执行顺序。 222 | 223 | 细心的读者应当发现,我们在 L 的示例中已经广泛使用 224 | 了这种技巧,来绕过因处理阶段而引起的指令执行顺序上的限制。 225 | 226 | 看到这里,有的读者可能会问:“那么我在使用一条陌生的配置指令之前,如何知道它究竟 227 | 运行 228 | 在哪一个处理阶段呢?”答案是:查看该指令的文档(当然,高级开发人员也可以直接查看 229 | 模 230 | 块的 C 源码)。在许多模块的文档 231 | 中,都会专门标记其配置指令所运行的具体阶段。例如 L 232 | 指令的文档中有这么一行: 233 | 234 | :text 235 | phase: content 236 | 237 | 这一行便是说,当前配置指令运行在 C 阶段。如果你使用的 238 | Nginx 模块碰巧没有指示运行阶段的文档,可以直接联系该模块的作者请求补充。不 239 | 过,值得一提的是,并非所有的配置指令都与某个处理阶段相关联,例如我们先前在 L 中提到过的 L 指令以及在 L 中介绍过的 L 指令。这些不与处理阶 242 | 段相关联的配置指令基本上都是“声明性的”(declarative), 243 | 即不直接产生某种动作或者过程。Nginx 的作者 Igor Sysoev 在公开 244 | 场合曾不止一次地强调,Nginx 245 | 配置文件所使用的语言本质上是“声明性的”,而非“过程性 246 | 的”(procedural)。 247 | 248 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder02.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(二) = 2 | 3 | 我们前面已经知道,当 L 指令用在 C 4 | 配置块中时,都是在当前请求的 C 阶段运行的。事实上,在此上下 5 | 文中,L 模块中的几乎 6 | 全部指令,都运行在 C 7 | 阶段,包括 L 中介绍过的 L 8 | 指令。不过,值得一提的是,当这些指令使用在 C 配置块中时,则会 9 | 运行在一个我们 10 | 尚未提及的更早的处理阶段,C 阶段。 11 | 12 | L 中介绍过的 L 13 | 模块的 L 指令同 14 | 样也运行在 C 阶段。特别地,L 15 | 模块的指令还可以和 L 的指令混合在一起依次执行。我们 16 | 来看这样的一个例子: 17 | 18 | :nginx 19 | location /test { 20 | set $a "hello%20world"; 21 | set_unescape_uri $b $a; 22 | set $c "$b!"; 23 | 24 | echo $c; 25 | } 26 | 27 | 访问这个接口可以得到: 28 | 29 | :bash 30 | $ curl 'http://localhost:8080/test' 31 | hello world! 32 | 33 | 我们看到,L 语句 34 | 前后的 L 语句都按书写时的顺序一前一后地执行 35 | 了。 36 | 37 | 为了进一步确认这一点,我们不妨再检查一下 Nginx 的“调试日志”(如果你 38 | 还不清楚如何开启“调试日志”的话,可以参考 L 中的 39 | 步骤): 40 | 41 | :bash 42 | grep -E 'http script (value|copy|set)' t/servroot/logs/error.log 43 | 44 | 过滤出来的调试日志信息如下所示: 45 | 46 | :text 47 | [debug] 11167#0: *1 http script value: "hello%20world" 48 | [debug] 11167#0: *1 http script set $a 49 | [debug] 11167#0: *1 http script value (post filter): "hello world" 50 | [debug] 11167#0: *1 http script set $b 51 | [debug] 11167#0: *1 http script copy: "!" 52 | [debug] 11167#0: *1 http script set $c 53 | 54 | 开头的两行信息 55 | 56 | :text 57 | [debug] 11167#0: *1 http script value: "hello%20world" 58 | [debug] 11167#0: *1 http script set $a 59 | 60 | 就对应我们的配置语句 61 | 62 | :nginx 63 | set $a "hello%20world"; 64 | 65 | 而接下来的两行 66 | 67 | :text 68 | [debug] 11167#0: *1 http script value (post filter): "hello world" 69 | [debug] 11167#0: *1 http script set $b 70 | 71 | 则对应配置语句 72 | 73 | :nginx 74 | set_unescape_uri $b $a; 75 | 76 | 我们看到第一行信息与 L 指令略有区别,多了 77 | C<"(post filter)"> 这个标记,而且最后显示出 URI 解码操作 78 | 确实如我们期望的那样工作了,即 C<"hello%20world"> 在这里被成 79 | 功解码为 C<"hello world">. 80 | 81 | 而最后两行调试信息 82 | 83 | :text 84 | [debug] 11167#0: *1 http script copy: "!" 85 | [debug] 11167#0: *1 http script set $c 86 | 87 | 则对应最后一条 L 语句: 88 | 89 | :nginx 90 | set $c "$b!"; 91 | 92 | 注意,因为这条指令在为 C<$c> 变量赋值时使用了“变量插值”功能,所以 93 | 第一行调试信息是以 C 起始的,后面则是拼 94 | 接到最终取值的字符串常量 C<"!">. 95 | 96 | 把这些调试信息联系起来看,我们不难发现,这些配置指令的实际执行顺序是: 97 | 98 | :nginx 99 | set $a "hello%20world"; 100 | set_unescape_uri $b $a; 101 | set $c "$b!"; 102 | 103 | 这与它们在配置文件中的书写顺序完全一致。 104 | 105 | 我们在 L 中初识了第三方模块 L, 106 | 它提供的 L 配置指令也和 L 107 | 模块的指令一样,可以和 L 模块的指令混合使用。L 108 | 指令支持通过一小段用户 Lua 代码来计算出一个结果,然后赋给指定的 Nginx 109 | 变量。和 L 指令相似,L 110 | 指令也有自动创建不存在的 Nginx 变量的功能。 111 | 112 | 下面我们就来看一个 L 指令与 L 113 | 指令混合使用的例子: 114 | 115 | :nginx 116 | location /test { 117 | set $a 32; 118 | set $b 56; 119 | set_by_lua $c "return ngx.var.a + ngx.var.b"; 120 | set $equation "$a + $b = $c"; 121 | 122 | echo $equation; 123 | } 124 | 125 | 这里我们先将 C<$a> 和 C<$b> 变量分别初始化为 C<32> 和 C<56>, 126 | 然后利用 L 指令内联一行我们自己指定的 127 | Lua 代码,计算出 Nginx 变量 C<$a> 和 C<$b> 的“代数和 128 | ”(sum),并赋给变量 C<$c>,接着利用“变量插值”功能,把变量 C<$a>、 129 | C<$b> 和 C<$c> 的值拼接成一个字符串形式的等式,赋予变量 C<$equation>, 130 | 最后再用 L 指令输出 C<$equation> 131 | 的值。 132 | 133 | 这个例子值得注意的地方是:首先,我们在 Lua 代码中是通过 L 134 | 接口来读取 Nginx 变量 C<$VARIABLE> 的;其次,因为 Nginx 135 | 变量的值只有字符串这一种类型,所以在 Lua 代码里读取 C 136 | 和 C 时得到的其实都是 Lua 字符串类型的值 C<"32"> 137 | 和 C<"56">;接着,我们对两 138 | 个字符串作加法运算会触发 139 | Lua 对加数进行自动类型转换(Lua 会把两个加数先转换为数值类型再求和);然 140 | 后,我们在 Lua 141 | 代码中把最终结果通过 C 语句返回给外面的 Nginx 142 | 变量 C<$c>;最后,L 模块在给 C<$c> 实际赋值之前 143 | ,也会把 C 语句返回的数值类型的结果,也就是 Lua 加法计算 144 | 得出的“和”,自动转换为字符串(这同样是因为 Nginx 变量的值只能是字符串)。 145 | 146 | 这个例子的实际运行结果符合我们的期望: 147 | 148 | :bash 149 | $ curl 'http://localhost:8080/test' 150 | 32 + 56 = 88 151 | 152 | 于是这验证了 L 指令确实也可以和 L 153 | 这样的 L 模块提供的指令混合在一起工作。 154 | 155 | 还有不少第三方模块,例如 L 156 | 中介绍过的 L 以及后面即将接触到的用于加解密 157 | 用户会话(session)的 L, 158 | 也都可以和 L 模块的指令无缝混合工作。 159 | 160 | 标准 L 模块的应用是如此广泛,所以能够和它的配置 161 | 指令混合使用的第三方模块是幸运的。事实上,上面提到的这些第三方模块都采用了特殊的 162 | 技术,将它们自己的配置指令“注入”到了 L 模块的指令 163 | 序 164 | 列中(它们都借 165 | 助了 Marcus Clyne 编写的第三方模块 L)。 166 | 换句话说,更多常规的在 Nginx 的 C 阶段注册和运行指令 167 | 的第三方模块就没那么幸运了。这些“常规模块”的指令虽然也运行在 C 168 | 阶 169 | 段,但其配置指令和 L 模块(以及同一阶段内的其他模块 170 | )都是分 171 | 开独立执行的。在运行时,不同模块的配置指令集之间的先后顺序一般是不确定的(严格来 172 | 说, 173 | 一般是由模块的加载顺序决定的,但也有例外的情况)。比如 C
和 C 174 | 两个模块都在 C 阶段运行指令,于是要么是 C 模块的 175 | 所有指令全部执行完再执行 C 模块的那些指令,要么就是反过来,把 C 176 | 的指 177 | 令全部执行完,再去运行 C 的指令。除非模块的文档中有明确的交待, 178 | 否则用户 179 | 一般不应编写依赖于此种不确定顺序的配置。 180 | 181 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder03.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(三) = 2 | 3 | 如前文所述,除非像 L 模块那样使用特殊技术, 4 | 其他模块的配置指令即使是在 C 阶段运行,也不能和 L 5 | 模块的指令混合使用。不妨来看几个这样的例子。 6 | 7 | 第三方模块 L 提供了一系列配置指令,用于操 8 | 纵当前请求的请求头和响应头。其中有一条名叫 L 9 | 的指令可以在 C 阶段改写指定的请求头(或者在请求头不存在时自 10 | 动创建)。这条指令总是运行在 C 阶段的末尾,该指令的文档中有 11 | 这么一行标记: 12 | 13 | :text 14 | phase: rewrite tail 15 | 16 | 其中的 C 的意思就是 C 阶段的 17 | 末尾。 18 | 19 | 既然运行在 C 阶段的末尾,那么也就总是会运行在 C 20 | 模块的指令之后,即使我们在配置文件中把它写在前面,例如: 21 | 22 | :nginx 23 | ? location /test { 24 | ? set $value dog; 25 | ? more_set_input_headers "X-Species: $value"; 26 | ? set $value cat; 27 | ? 28 | ? echo "X-Species: $http_x_species"; 29 | ? } 30 | 31 | 这个例子用到的 L<$http_XXX> 内建变量在读取时会返回当前请求中名为 32 | C 的请求头,我们在 L 33 | 中曾经简单提过 34 | 它。需要注意的是,L<$http_XXX> 变量在匹配请求头时会自动对请求头的名 35 | 字进行归一化,即将名字的大写字母转换为小写字母,同时把间隔符(C<->)替换 36 | 为下划线(C<_>),所以变量名 C<$http_x_species> 才得以成 37 | 功匹配 L 38 | 语句中设置的请求头 C. 39 | 40 | 此例书写的指令顺序会误导我们认为 C 接口输出的 C 41 | 头的值是 C, 42 | 然而实际的结果却并非如此: 43 | 44 | :bash 45 | $ curl 'http://localhost:8080/test' 46 | X-Species: cat 47 | 48 | 显然,写在 L 49 | 指令之后的 C 语句却先执行了。 50 | 51 | 上面这个例子证明了即使运行在同一个请求处理阶段,分属不同模块的配置指令也可能会分 52 | 开独立运行(除非像 L 等模块那样针对 L 53 | 模块提供特殊支持)。换句话说,在单个请求处理阶段内部,一般也会以 Nginx 模 54 | 块为单位进一步地划分出内部子阶段。 55 | 56 | 第三方模块 L 提供的 L 57 | 配置指令也和 L 58 | 一样运行在 C 阶段的末尾。我们来验证一下: 59 | 60 | :nginx 61 | ? location /test { 62 | ? set $a 1; 63 | ? rewrite_by_lua "ngx.var.a = ngx.var.a + 1"; 64 | ? set $a 56; 65 | ? 66 | ? echo $a; 67 | ? } 68 | 69 | 这里我们在 L 语句内联的 Lua 70 | 代码中对 Nginx 变量 C<$a> 进行了自增计算。从该例的指令书写顺序上看 71 | ,我们或许会期望输出是 C<56>,可是因为 L 72 | 会在所有的 L 语句之后执行,所以结果是 C<57>: 73 | 74 | :bash 75 | $ curl 'http://localhost:8080/test' 76 | 57 77 | 78 | 显然,L 指令的行为不同于我们前 79 | 面在 L 中介绍过的 L 80 | 指令。 81 | 82 | 有的读者可能要问,既然 L 83 | 和 L 指令都运行在 C 84 | 阶段的末尾,那么它们之间的先后顺序又是怎样的呢?答案是:不一定。我们应当避免写出 85 | 依赖它们二者间顺序的配置。 86 | 87 | Nginx 的 C 阶段是一个比较早的请求处理阶段,这个阶段的 88 | 配置指令一般用来对当前请求进行各种修改(比如对 URI 和 URL 参数进行 89 | 改写),或者创建并初始化一系列后续处理阶段可能需要的 Nginx 变量。当然, 90 | 也不能阻止一些用户在 C 阶段做一系列更复杂的事情,比如读取请 91 | 求体,或者访 92 | 问数据库等远方服务,毕竟有 L 93 | 这样的指令可以嵌入任意复杂的 Lua 代码。 94 | 95 | 在 C 阶段之后,有一个名叫 C 的请求处理阶 96 | 段。L 中介绍过的第三方模块 L 97 | 的指令就运行在 C 阶段。在 C 阶段运行的配置 98 | 指令多是执行访问控制性质的任务,比如检查用户的访问权限,检查用户的来源 IP 99 | 地址是否合法,诸如此类。 100 | 101 | 例如,标准模块 L 提供的 L 102 | 和 L 103 | 配置指令可用于控制哪些 IP 地址可以访问,哪些不可以: 104 | 105 | :nginx 106 | location /hello { 107 | allow 127.0.0.1; 108 | deny all; 109 | 110 | echo "hello world"; 111 | } 112 | 113 | 这个 C
接口被配置为只允许从本机(IP 地址为保留的 C<127.0.0.1>)访 114 | 问,而从其他 IP 地址访问都会被拒(返回 C<403> 错误页)。L 115 | 模块自己的多条配置指令之间是按顺序执行的,直到遇到第一条满足条件的指令就不再执行 116 | 后续的 L 和 L 117 | 指令。如果首先匹配的指令是 L,则会继续执行 118 | 后续其他模块的指令或者跳到后续的处理阶段;而如果首先满足的是 L 119 | 则会立即中止当前整个请求的处理,并立即返回 C<403> 错误页。 120 | 所以看上面这个例子,如果 121 | 是从本地访问 122 | 的,则首先匹配 C 这一条语句,于是 Nginx 123 | 就继续往下执行其他模块的指令以及后续的处理阶段;而如果是从其他机器访问,则首先匹 124 | 配的则是 C 这一条语句,即拒绝所有地址,它会导致 C<403> 125 | 错误页立即 126 | 返回给客户端。 127 | 128 | 我们来实测一下。从本机访问这个接口可以得到 129 | 130 | :bash 131 | $ curl 'http://localhost:8080/hello' 132 | hello world 133 | 134 | 而从另一台机器访问这台机器(假设运行 Nginx 的机器地址是 C<192.168.1.101>)提 135 | 供的接口时则得到 136 | 137 | :bash 138 | $ curl 'http://192.168.1.101:8080/hello' 139 | 140 | 403 Forbidden 141 | 142 |

403 Forbidden

143 |
nginx
144 | 145 | 146 | 147 | 值得一提的是,L 模块还支持所谓的“CIDR 记法” 148 | 来表示一个网段 149 | ,例如 C<169.200.179.4/24> 则 150 | 表示路由前缀是 C<169.200.179.0>(或者说子网掩码是 C<255.255.255.0>)的 151 | 网段。 152 | 153 | 因为 L 模块的指令运行在 C 阶段,而 154 | C 阶段又处于 C 阶段之后,所以前面我们见到 155 | 的所有那些在 C 阶段运行的配置指令,都总是在 L 156 | 和 L 之前执行,而无论它们在配置文件中的书写 157 | 顺序是怎样的。所以,为了避免阅读配置时的混乱,我们应该总是让指令的书写顺序和它们 158 | 的实际执行顺序保持一致。 159 | 160 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder04.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(四) = 2 | 3 | L 模块提供了配置指令 L, 4 | 用于在 C 请求处理阶段插入用户 Lua 代码。这条指令 5 | 运行于 C 阶段的末尾,因此总是在 L 6 | 和 L 这样的指令之后运行,虽然它们同属 C 7 | 阶段。一般我们通过 L 在 L 8 | 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的 9 | 请求验证 10 | 操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。 11 | 12 | 我们来看一个简单的例子,利用 L 13 | 来实现 L 模块的 IP 地址过滤功能: 14 | 15 | :nginx 16 | location /hello { 17 | access_by_lua ' 18 | if ngx.var.remote_addr == "127.0.0.1" then 19 | return 20 | end 21 | 22 | ngx.exit(403) 23 | '; 24 | 25 | echo "hello world"; 26 | } 27 | 28 | 这里在 Lua 代码中通过引用 Nginx 标准的内建变量 L 29 | 来获取字符串形式的客户端 IP 地址,然后用 Lua 的 C 30 | 语句判断是否为本机地址,即是否等于 C<127.0.0.1>. 如果是本机 31 | 地址,则 32 | 直接利用 Lua 的 C 语句返回,让 Nginx 继续执行后续 33 | 的请求处理阶段(包括 L 指令所处的 C 34 | 阶段);而如果不是本机地址,则通过 L 模块提供的 Lua 函 35 | 数 L 中断当前的整个请求处理流程,直接 36 | 返回 C<403> 错误页给客户端。 37 | 38 | 这个例子在功能上完全等价于先前在 L 中介绍过的 39 | 那个使用 L 模块的例子: 40 | 41 | :nginx 42 | location /hello { 43 | allow 127.0.0.1; 44 | deny all; 45 | 46 | echo "hello world"; 47 | } 48 | 49 | 虽然这两个例子在功能上完全相同,但在性能上还是有区别的,毕竟 L 50 | 是用纯 C 实现的专门化的 Nginx 模块。 51 | 52 | 下面我们不妨来实际测量一下这两个例子的性能差别。因为我们使用 Nginx 就是为 53 | 了追求性能,而量化的性能比较,在工程上具有很大的现实意义,所以我们顺便介绍一 54 | 下重要的测量技术。由于无论是 L 还是 L 55 | 在进行 IP 地址验证方面的性能都非常之高,所以为了减少测量误差,我们希望能 56 | 对 57 | C 阶段的用时进行直接测量。为了做 58 | 到这一点,传统的做法一般会涉及到修改 Nginx 源码,自己插入专门的计时代码和 59 | 统计输出代码,抑或是重新编译 Nginx 以启用像 C 这 60 | 样专门的性能监测工具。 61 | 62 | 幸运的是,在新一点的 Solaris, Mac OS X, 以及 FreeBSD 63 | 等系统上存在一个叫做 C 的工具,可以对任意的用户程序进行微观性 64 | 能分析(以及行为分析),而无须对用户程序的源码进行修改或者对用户程序进行重新编译 65 | 。 66 | 因为 Mac OS X 10.5 以后就自带了 C, 67 | 所以为方便起见,下面在我的 MacBook Air 笔记本上演示一下这里的测量过 68 | 程。 69 | 70 | 首先,在 Mac OS X 系统中打开一个命令行终端,在某一个文件目录下面创 71 | 建一个名为 F 的文件,并编辑内容如下 72 | : 73 | 74 | :d 75 | #!/usr/bin/env dtrace -s 76 | 77 | pid$1::ngx_http_handler:entry 78 | { 79 | elapsed = 0; 80 | } 81 | 82 | pid$1::ngx_http_core_access_phase:entry 83 | { 84 | begin = timestamp; 85 | } 86 | 87 | pid$1::ngx_http_core_access_phase:return 88 | /begin > 0/ 89 | { 90 | elapsed += timestamp - begin; 91 | begin = 0; 92 | } 93 | 94 | pid$1::ngx_http_finalize_request:return 95 | /elapsed > 0/ 96 | { 97 | @elapsed = avg(elapsed); 98 | elapsed = 0; 99 | } 100 | 101 | 保存好此文件后,再赋予它可执行权限: 102 | 103 | :bash 104 | $ chmod +x ./nginx-access-time.d 105 | 106 | 这个 F<.d> 文件中的代码是用 C 工具自己提供的 C 107 | 语言来编写的 108 | (注意,这里的 C 语言并不同于 Walter Bright 作为另一种“ 109 | 更好的 C++”而设计的 C 语言)。由于本系列教程并不打算介绍如何编写 110 | C 的 C 脚本,同时理解这个脚本需要不少有关 Nginx 111 | 内部源码实现的细节,所以这里我们不展开介绍。大家只需要知道这个脚本的功能是:统 112 | 计指定的 Nginx worker 进程在处理每个请求时,平均花费在 C 113 | 阶段上的时间。 114 | 115 | 现在来演示一下这个 C 脚本的运行方法。这个脚本接受一个命令行参数用于指定 116 | 监视的 Nginx worker 进程的进程号(pid)。由于 Nginx 支持 117 | 多 worker 进程,所以我们测试时发起的 HTTP 请求可能由其中任意一个 118 | worker 119 | 进程服务。为了确保所有测试请求都为固定的 worker 进程处理,不妨在 120 | F 配置文件中指定只启用一个 worker 进程: 121 | 122 | :nginx 123 | worker_processes 1; 124 | 125 | 重启 Nginx 服务器之后,可以利用 C 命令得到当前 worker 126 | 进程的进程号: 127 | 128 | :bash 129 | $ ps ax|grep nginx|grep worker|grep -v grep 130 | 131 | 在我机器上的一次典型输出是 132 | 133 | :text 134 | 10975 ?? S 0:34.28 nginx: worker process 135 | 136 | 其中第一列的数值便是我的 nginx worker 进程的进程号,C<10975>。 137 | 如果你 138 | 得到的输出不止一行,则通常意味着你的系统中同时运行着多个 Nginx 服务器实例 139 | ,或者当前 Nginx 实例启用了多个 worker 进程。 140 | 141 | 接下来使用刚刚得到的 worker 进程号以及 root 身份来运行 F 142 | 脚本: 143 | 144 | :bash 145 | $ sudo ./nginx-access-time.d 10975 146 | 147 | 如果一切正常,则会看到这样一行输出: 148 | 149 | :text 150 | dtrace: script './nginx-access-time.d' matched 4 probes 151 | 152 | 这行输出是说,我们的 C 脚本已成功向目标进程动态植入了 4 个 C 153 | “探针”(probe)。紧接着这个脚本就挂起了,表明 C 工具 154 | 正在对进程 C<10975> 进行持续监视。 155 | 156 | 然后我们再打开一个新终端,在那里使用 C 这样的工具多次请求我们正在 157 | 监视的接口 158 | 159 | :bash 160 | $ curl 'http://localhost:8080/hello' 161 | hello world 162 | 163 | $ curl 'http://localhost:8080/hello' 164 | hello world 165 | 166 | 最后我们回到原先那个一直在运行 C 脚 167 | 本的终端,按下 C 组合键中止 C 的运行。而该 168 | 脚 169 | 本在退出时会向终端打印出最终统计结果。例如我的终端此时是这个样子的: 170 | 171 | :bash 172 | $ sudo ./nginx-access-time.d 10975 173 | dtrace: script './nginx-access-time.d' matched 4 probes 174 | ^C 175 | 19219 176 | 177 | 最后一行输出 C<19219> 便是那几次 C 请 178 | 求在 C 阶段的平均用时(以纳秒,即 10 的负 9 次方秒为单 179 | 位)。 180 | 181 | 通过上面介绍的步骤,可以通过 F 182 | 脚本分别统计出各种不同的 Nginx 配置下 C 阶段的平均 183 | 用时。针对我们感兴趣的三种情况可以进行三组平行试验,即使用 L 184 | 过滤 IP 地址的情况 185 | ,使用 L 过滤 IP 地址的情况 186 | ,以及 187 | 不在 C 阶段使用任何配置指令的情况。最后一种情况属于“空白对照 188 | 组”, 189 | 用于校正测试过程中因 C 探针等其他因素而引入的“系统误 190 | 差”。 191 | 另外,为了最小化各种不可控的“随机误差”,可以用 C 这样的批量 192 | 测试工具来取代 C 发起连续十万次以上的请求,例如 193 | 194 | :bash 195 | $ ab -k -c1 -n100000 'http://127.0.0.1:8080/hello' 196 | 197 | 这样我们的 C 脚本统计出来的平均值将更加接近“真实值”。 198 | 199 | 在我的苹果系统上,一次典型的测试结果如下: 200 | 201 | :text 202 | ngx_access 组 18146 203 | access_by_lua 组 35011 204 | 空白对照组 15887 205 | 206 | 把前两组的结果分别减去“空白对照组”的结果可以得到 207 | 208 | :text 209 | ngx_access 组 2259 210 | access_by_lua 组 19124 211 | 212 | 可以看到,L 组比 L 213 | 组快了大约一个数量级,这正是我们所预期的。不过其绝对时间差是极小的,对于我的 C 的 CPU 而言,也只有区区十几微秒, 215 | 或者说是在十万分之一秒的量级。 216 | 217 | 当然,上面使用 L 的例子还可以通 218 | 过换用 L 内建变量 219 | 进行优化,因为 L 220 | 读出的是二进制形式的 IP 地址,而 L 221 | 则返回更长一些的字符串形式的地址。更短的地址意味着用 Lua 进行字符串比较时通 222 | 常可以更快。 223 | 224 | 值得注意的是,如果按 L 中介绍的方法为 Nginx 225 | 开启了“调试日志”的话,上面统计出来的时间会显著增加,因为“调试日志”自身的开销 226 | 是很大的。 227 | 228 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder05.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(五) = 2 | 3 | Nginx 的 C 阶段是所有请求处理阶段中最为重要的一个,因 4 | 为运行在这个阶段的配置指令一般都肩负着生成“内容”(content)并输出 HTTP 5 | 响应的使命。正因为其重要性,这个阶段的配置指令也异常丰富,例如前面我们一 6 | 直 7 | 在示例中广泛使用的 L 指令,在 L 中接触到的 L 指令, 9 | L 中接触到的 L 10 | 指令,L 中介绍过的 L 11 | 指令,以及 L 中介绍过的 L 12 | 指令,都运行在这个阶段。 13 | 14 | C 阶段属于一个比较靠后的处理阶段,运行在先前介绍过的 C 15 | 和 C 这两个阶段之后。当和 C、C 16 | 阶段的指令一起使用时,这个阶段的指令总是最后运行,例如: 17 | 18 | :nginx 19 | location /test { 20 | # rewrite phase 21 | set $age 1; 22 | rewrite_by_lua "ngx.var.age = ngx.var.age + 1"; 23 | 24 | # access phase 25 | deny 10.32.168.49; 26 | access_by_lua "ngx.var.age = ngx.var.age * 3"; 27 | 28 | # content phase 29 | echo "age = $age"; 30 | } 31 | 32 | 这个例子中各个配置指令的执行顺序便是它们的书写顺序。测试结果完全符合预期: 33 | 34 | :bash 35 | $ curl 'http://localhost:8080/test' 36 | age = 6 37 | 38 | 即使改变它们的书写顺序,也不会影响到执行顺序。其中,L 39 | 指令来自 40 | L 模块,运行于 C 阶段;而 L 41 | 指令来自 L 模块,运行于 C 阶段的末尾;接 42 | 下来,L 指令来自 L 43 | 模块,运行于 C 阶段;再下来,L 44 | 指令同样来自 L 模块,运行于 C 阶段的末尾 45 | ;最后,我们的老朋友 L 指令则来自 L 46 | 模块,运行在 C 阶段。 47 | 48 | 这个例子展示了通过同时使用多个处理阶段的配置指令来实现多个模块协同工作的效果。在 49 | 这 50 | 个过程中,Nginx 变量则经常扮演着在指令间乃至模块间传递(小份)数据的角色。 51 | 这些配置指令的执行顺序,也强烈地受到请求处理阶段的影响。 52 | 53 | 进一步地,在 C 和 C 这两个阶段,多个模块 54 | 的配置指令可以同时使用,譬如上例中的 L 指令 55 | 和 56 | L 指令同处 C 57 | 阶段,而 L 指令和 L 58 | 指令则同处 C 阶段。但不幸的是,这通常不适用于 C 59 | 阶段。 60 | 61 | 绝大多数 Nginx 模块在向 C 阶段注册配置指令时,本质上 62 | 是在当前的 63 | C 配置块中注册所谓的“内容处理程序”(content handler)。 64 | 每一个 C 只能有一个“内容处理程序”,因此,当在 C 65 | 中同时使用多个模块的 C 阶段指令时,只有其中一个模块能成功 66 | 注册“内容处理程序”。考虑下面这个有问题的例子: 67 | 68 | :nginx 69 | ? location /test { 70 | ? echo hello; 71 | ? content_by_lua 'ngx.say("world")'; 72 | ? } 73 | 74 | 这里,L 模块的 L 指令和 75 | L 模块的 L 76 | 指令同处 C 阶段,于是只有其中一个模块能注册和运行这个 C 77 | 的“内容处理程序”: 78 | 79 | :bash 80 | $ curl 'http://localhost:8080/test' 81 | world 82 | 83 | 实际运行结果表明,写在后面的 L 84 | 指令反而胜出了,而 L 指令则完全没有运行。具体哪 85 | 一个模块的指令会胜出是不确定的,例如把上例中的 L 86 | 语句和 L 语句交换顺序,则输出 87 | 就会变成 C,即 L 模块胜出。所以我们应当 88 | 避免在同一个 C 中使用多个模块的 C 阶 89 | 段指令。 90 | 91 | 将上例中的 L 指令替换 92 | 为 L 指令就可以如愿了: 93 | 94 | :nginx 95 | location /test { 96 | echo hello; 97 | echo world; 98 | } 99 | 100 | 测试结果证明了这一点: 101 | 102 | :bash 103 | $ curl 'http://localhost:8080/test' 104 | hello 105 | world 106 | 107 | 这里使用多条 L 指令是没问题的,因为它们同属 L 108 | 模块,而且 L 模块规定和实现了它们之间的执行顺序。值得一提 109 | 的是,并非所有模块的指令都支持在同一个 C 中被使用多次,例 110 | 如 L 就只能使用一次,所以下面 111 | 这个例子是错误的: 112 | 113 | :nginx 114 | ? location /test { 115 | ? content_by_lua 'ngx.say("hello")'; 116 | ? content_by_lua 'ngx.say("world")'; 117 | ? } 118 | 119 | 这个配置在 Nginx 启动时就会报错: 120 | 121 | :text 122 | [emerg] "content_by_lua" directive is duplicate ... 123 | 124 | 正确的写法应当是: 125 | 126 | :nginx 127 | location /test { 128 | content_by_lua 'ngx.say("hello") ngx.say("world")'; 129 | } 130 | 131 | 即在 L 内联的 Lua 代码中 132 | 调用两次 L 函数,而不是在当前 C 133 | 中使用两次 L 指令。 134 | 135 | 类似地,L 模块的 L 136 | 指令和 L 指令也不能同时用在一个 C 137 | 中,因为它们也同属 C 阶段。不少 Nginx 新手都会犯类似 138 | 下 139 | 面这样的错误: 140 | 141 | :nginx 142 | ? location /test { 143 | ? echo "before..."; 144 | ? proxy_pass http://127.0.0.1:8080/foo; 145 | ? echo "after..."; 146 | ? } 147 | ? 148 | ? location /foo { 149 | ? echo "contents to be proxied"; 150 | ? } 151 | 152 | 这个例子表面上是想在 L 模块返回的内容前后,通过 L 153 | 模块的 L 指令分别输出字符串 C<"before..."> 154 | 和 C<"after...">,但其实只有其中一个模块能在 C 155 | 阶段运行。测试结果表明,在这个例子中是 L 模块胜出,而 156 | L 模块的 L 指令根本没有运 157 | 行: 158 | 159 | :bash 160 | $ curl 'http://localhost:8080/test' 161 | contents to be proxied 162 | 163 | 要实现这个例子希望达到的效果,需要改用 L 模块提供的 164 | L 和 L 165 | 这两条配置指令: 166 | 167 | :nginx 168 | location /test { 169 | echo_before_body "before..."; 170 | proxy_pass http://127.0.0.1:8080/foo; 171 | echo_after_body "after..."; 172 | } 173 | 174 | location /foo { 175 | echo "contents to be proxied"; 176 | } 177 | 178 | 测试结果表明这一次我们成功了: 179 | 180 | $ curl 'http://localhost:8080/test' 181 | before... 182 | contents to be proxied 183 | after... 184 | 185 | 配置指令 L 和 L 186 | 之所以可以和其他模块运行在 C 阶段的指令一起工作,是因为它们 187 | 运行在 Nginx 的“输出过滤器”中。前面我们在 L 188 | 中分析 L 指令产生的“调试日志”时 189 | 已经知道,Nginx 在输出响应体数据时都会调用“输出过滤器”,所以 190 | L 模块才有机会在“输出过滤器”中对 L 191 | 模块产生的响应体输出进行修改(即在首尾添加新的内容)。值得一提的是,“输出过滤器 192 | ”并不属于 L 中提到的那 11 个请 193 | 求处理阶段(毕竟许多阶段都可以通过输出响应 194 | 体数据来调用“输出过滤器”),但这并不妨碍 L 195 | 和 L 指令在文档中标记 196 | 下面这一行: 197 | 198 | phase: output filter 199 | 200 | 这一行的意思是,当前配置指令运行在“输出过滤器”这个特殊的阶段。 201 | 202 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder06.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(六) = 2 | 3 | 前面我们在 L 中提到,在一个 C 4 | 中使用 C 阶段指令时,通常情况下就是对应的 Nginx 5 | 模块注册该 C 中的“内容处理程序”。那么当一个 C 6 | 中未使用任何 C 阶段的指令,即没有模块注册“内容处理程序”时 7 | ,C 阶段会发生什么事情呢?谁又来担负起生成内容和输出响应的重 8 | 担呢?答案就是那些把当前请求的 URI 映射到文件系统的静态资源服务模块。当存在 9 | “内容处理程序”时,这些静态资源服务模块并不会起作用;反之,请求的处理权就会自动 10 | 落到这些模块上。 11 | 12 | Nginx 一般会在 C 阶段安排三个这样的静态资源服务模块(除 13 | 非你的 Nginx 在构造时显式禁用了这三个模块中的一个或者多个,又或者启用了这 14 | 种类型的其他模块)。 15 | 按照它们在 C 阶段的运行顺序,依次是 L 16 | 模块,L 模块,以及 L 17 | 模块。下面就来逐一介绍一下这三个模块。 18 | 19 | L 和 L 模块都只会 20 | 作用于那些 URI 以 C 结尾的请求,例如请求 C, 21 | 而对于不以 C 结尾的请求则会直接忽略, 22 | 同时把处理权移交给 C 阶段的下一个模块。而 L 23 | 模块则刚好相反,直接忽略那些 URI 以 C 结尾的请求。 24 | 25 | L 模块主要用于在文件系统目录中自动查找指定的首页文件,类 26 | 似 F 和 F 这样的,例如: 27 | 28 | :nginx 29 | location / { 30 | root /var/www/; 31 | index index.htm index.html; 32 | } 33 | 34 | 这样,当用户请求 C 地址时,Nginx 就会自动在 L 35 | 配置指令指定的文件系统目录下依次寻找 F 和 F 36 | 这两个文件。如果 F 文件存在,则直接发起“内部跳转”到 37 | C 这个新的地址;而如果 F 文件 38 | 不存在,则继续检查 F 是否存在。如果存在,同样发起“内 39 | 部跳转”到 C;如果 F 文件 40 | 仍然不存在,则放弃处理权给 C 41 | 阶段的下一个模块。 42 | 43 | 我们前面已经在 L 中提到, 44 | L 45 | 指令和 L 指令可以发起“内部跳转”。 46 | 这 47 | 种跳转会自动修改当前请求的 URI,并且重新匹配与之对应的 C 48 | 配置块,再重新执行 C、C、C 49 | 等处理阶段。因为是“内部跳转”,所以有别于 HTTP 协议中定义的基于 50 | 302 51 | 和 301 响应的“外部跳转”,最终用户的浏览器的地址栏也不会发生变化,依然是 52 | 原来的 URI 位置。而 L 模块一旦找到了 L 53 | 指令中列举的文件之后,就会发起这样的“内部跳转”,仿佛用户是直接请求的这个文件所 54 | 对 55 | 应的 URI 一样。 56 | 57 | 为了进一步确认 L 模块在找到文件时的“内部跳转”行为,我 58 | 们不妨设计下面这个小例子: 59 | 60 | :nginx 61 | location / { 62 | root /var/www/; 63 | index index.html; 64 | } 65 | 66 | location /index.html { 67 | set $a 32; 68 | echo "a = $a"; 69 | } 70 | 71 | 此时我们在本机的 F 目录下创建一个空白的 F 72 | 文件,并确保该文件的权限设置对于运行 Nginx worker 进程的帐户可读。 73 | 然后我们来请求一下根位置(C): 74 | 75 | :bash 76 | $ curl 'http://localhost:8080/' 77 | a = 32 78 | 79 | 这里发生了什么?为什么输出不是 F 文件的内容(即空白)? 80 | 首先对于用户的原始请求 C,Nginx 81 | 匹配出 82 | C 来处理它,然后 C 阶段的 L 83 | 模块在 F 下找到了 F,于是立即 84 | 发起一个到 C 位置的“内部跳转”。 85 | 86 | 到这里,相信大家都不会有问题。接下来有趣的事情发生了!在重新为 C 这个 87 | 新位置匹配 C 配置块时,C 88 | 的优先级要高于 C,因为 C 块按照 89 | URI 前缀来匹配时遵循所谓的“最长子串匹配语义”。这样,在进入 C 配置块之后,又重新开始执行 C 91 | 、C、以及 C 等阶段。最终输出 C
自然也就在情理之中了。 93 | 94 | 我们接着研究上面这个例子。如果此时把 F 95 | 文件删除,再访问 C 又会发生什么事情呢?答案是返回 C<403 Forbidden> 96 | 出错页。为什么呢?因为 L 模块找不到 L 97 | 指令指定的文件(在这里就是 F),接着把处理权转给 C 98 | 阶段的后续模块,而后续的模块也都无法处理这个请求,于是 Nginx 只好放弃,输 99 | 出了错误页,并且在 Nginx 错误日志中留下了类似 100 | 这一行信息: 101 | 102 | :text 103 | [error] 28789#0: *1 directory index of "/var/www/" is forbidden 104 | 105 | 所谓 C 便是生成“目录索引”的意思,典型的方 106 | 式就是生成一个网页,上面列举出 C 目录下的所有文件和子目 107 | 录。而运行在 L 模块之后的 L 108 | 模块就可以用于自动生成这样的“目录索引”网页。我们来把上例修改一下: 109 | 110 | :nginx 111 | location / { 112 | root /var/www/; 113 | index index.html; 114 | autoindex on; 115 | } 116 | 117 | 此时仍然保持文件系统中的 F 文件不存 118 | 在。我们再访问 C 位置时,就会得到一张漂亮的网页: 119 | 120 | :bash 121 | $ curl 'http://localhost:8080/' 122 | 123 | Index of / 124 | 125 |

Index of /


../
126 |     cgi-bin/  08-Mar-2010 19:36   -
127 |     error/      08-Mar-2010 19:36   -
128 |     htdocs/    05-Apr-2010 03:55   -
129 |     icons/      08-Mar-2010 19:36   -
130 |     

131 | 132 | 133 | 生成的 HTML 源码显示,我本机的 F 目录下还有 F, 134 | F, F, 以及 F 这几个子 135 | 目录。在你的系统中尝试上面的例子,输出很可能会不太一样。 136 | 137 | 值得一提的是,当你的文件系统中存在 F 138 | 时,优先运行的 L 模块就会发起“内部跳转”,根本轮不到 139 | L 执行。感兴趣的读者可以自己测试一下。 140 | 141 | 在 C 阶段默认“垫底”的最后一个模块便是极为常用的 L 142 | 模块。这个模块主要实现服务静态文件的功能。比方说,一个网站的静态资源,包 143 | 括静态 F<.html> 文件、静态 F<.css> 文件、静态 F<.js> 144 | 文件、以及静态图片文件等等,全部可以通过这个模块对外服务。前面介绍的 L 145 | 模块虽然可以在指定的首页文件存在时发起“内部跳转”,但真正把相应的首页文件服 146 | 务出去(即把该文件的内容作为响应体数据输出,并设置相应的响应头),还是得靠这个 147 | L 模块来完成。 148 | 149 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder07.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(七) = 2 | 3 | 来看一个 L 模块服务磁盘文件的例子。我们使用下面这个配 4 | 置片段: 5 | 6 | :nginx 7 | location / { 8 | root /var/www/; 9 | } 10 | 11 | 同时在本机的 F 目录下创建两个文件,一个文件叫做 F, 12 | 内容是一行文本 C;另一个文件叫做 F, 13 | 内容是一行文本 C. 同时注意这两个文件的权限设置,确 14 | 保它们都对运行 Nginx worker 进程的系统帐户可读。 15 | 16 | 现在来通过 HTTP 协议请求一下这两个文件所对应的 URI: 17 | 18 | :bash 19 | $ curl 'http://localhost:8080/index.html' 20 | this is my home 21 | 22 | $ curl 'http://localhost:8080/hello.html' 23 | hello world 24 | 25 | 我们看到,先前创建的那两个磁盘文件的内容被分别输出了。 26 | 27 | 不妨来分析一下这里发生的事情:C 中没有使用运行在 C 28 | 阶段的模块指令,于是也就没有模块注册这个 C 的“内容处理程 29 | 序”,处理权便自动落到了在 C 阶段“垫底”的那 3 个静态资 30 | 源服务模块。首先运行的 L 和 L 31 | 模块先后看到当前请求的 URI,C 和 C, 32 | 并不以 C 结尾,于是直接弃权,将处理权转给了最后运行的 L 33 | 模块。L 模块根据 L 指 34 | 令指定的“文档根目录”(document root),分别将请求 URI C 35 | 和 C 映射为文件系统路径 F 36 | 和 F,在确认这两个文件存在后,将它 37 | 们的内容分别作为响应体输出,并自动设置 C、C 38 | 以及 C 等响应头。 39 | 40 | 为了确认 L 模块确实运行了,可以启用 L 中介绍过的 Nginx “调试日志”,然后再次请求 C 42 | 这个接口。此时,在 Nginx 错误日志文件中可以看到类似下面这一行的调试信息: 43 | 44 | :text 45 | [debug] 3033#0: *1 http static fd: 8 46 | 47 | 这一行信息便是 L 模块生成的,其含义是“正在输出的静态 48 | 文件的描述符是数字 C<8>”。当然,具体的文件描述符编号会经常发生变化,这里只 49 | 是我机 50 | 器的一次典型输出。值得一提的是,能生成这一行调试信息的还有标准模块 L 51 | ,但它默认是不启用的,后面会专门介绍到这个模块。 52 | 53 | 注意上面这个例子中使用的 L 配置指令只起到了声明 54 | “文档根目录”的作用,并不是它开启了 L 模块。L 55 | 模块总是处于开启状态,但是否轮得到它运行就要看 C 阶段先于它 56 | 运行的那些模块是否“弃权”了。为了进一步确认这一点,来看下面这个空白 C 57 | 的定义: 58 | 59 | :nginx 60 | location / { 61 | } 62 | 63 | 因为没有配置 L 指令,所以在访问这个接口时,Nginx 64 | 会自动计算出一个缺省的“文档根目录”。该缺省值是取所谓的“配置前缀”(configure 65 | prefix)路径下的 F 子目录。举一个例子,假设“配置前缀”是 66 | F, 67 | 则缺省的“文档根目录”便是 F. 68 | 69 | 那么“配置前缀 70 | ”是由什么来决定的呢?默认情况下,就是 Nginx 安装时的根目录(或者说 Nginx 71 | 构造时传递给 F<./configure> 脚本的 C<--prefix> 选项 72 | 的 73 | 路径值)。如果 Nginx 安装到了 C 74 | 下,则“配置前缀”便是 C,同时默认的“文 75 | 档根目录”便是 C. 不过,我们 76 | 也可以在启动 Nginx 的时候,通过 C<--prefix> 命令行选项临时指 77 | 定自 78 | 己的“配置前缀”路径。假设我们启动 Nginx 时使用的命令是 79 | 80 | :bash 81 | nginx -p /home/agentzh/test/ 82 | 83 | 则对于该服务器实例,其“配置前缀”便是 F, 84 | 而默认的“文档根目录”便是 F. 85 | “配置前缀”不仅会决定默认的“文档根目录”,还决定着 Nginx 配置文件中许多 86 | 相对路径值如何解释为绝对路径,后面我们还会看到许多需要引用到“配置前缀”的例子。 87 | 88 | 获取当前“文档根目录”的路径有一个非常简便的方法,那就是请求一个肯定不存在的文件 89 | 所 90 | 对应的资源名,例如: 91 | 92 | :nginx 93 | $ curl 'http://localhost:8080/blah-blah.txt' 94 | 95 | 404 Not Found 96 | 97 |

404 Not Found

98 |
nginx
99 | 100 | 101 | 102 | 我们会很自然地得到 C<404> 错误页。此时再看 Nginx 错误日志文件,应 103 | 该会看到类似下面这一行错误消息: 104 | 105 | :text 106 | [error] 9364#0: *1 open() "/home/agentzh/test/html/blah-blah.txt" failed (2: No such file or directory) 107 | 108 | 这条错误消息是 L 模块打印出来的,因为它并不能在文件系 109 | 统的对应路径上找到名为 F 的文件。因为这条错误信 110 | 息中包含有 L 试图打开的文件的绝对路径,所以从这个路径 111 | 不难看出,当前的“文档根目录”是 F. 112 | 113 | 很多初学者会想当然地把 C<404> 错误理解为某个 C 不 114 | 存在,其实上面这个例子表明,即使 C 存在并成功匹配,也是可 115 | 能返回 C<404> 错误页的。因为决定着 C<404> 错误页的是 116 | 抽象的“资源”是否存在,而非某个具体的 C 是否存在。 117 | 118 | 初学者常犯的一个错误是忘记配置 C 阶段的模块指令,而他们自己 119 | 其实并不期望使用 C 阶段缺省运行的静态资源服务,例如: 120 | 121 | :nginx 122 | location /auth { 123 | access_by_lua ' 124 | -- a lot of Lua code omitted here... 125 | '; 126 | } 127 | 128 | 显然,这个 F 接口只定义了 C 阶段的配置指令, 129 | 即 L,并未定义任何 C 130 | 阶段的配置指令。于是当我们请求 C 接口时,在 C 131 | 阶段的 Lua 代码会如期执行,然后 C 阶段的那些静态文件服 132 | 务会紧接着自动发生作用,直至 L 模块去文件系统上找名 133 | 为 F 的文件。而经常地,C<404> 错误页会抛出,除非运气太好, 134 | 在对应路径上确实存在一个叫做 F 的文件。所以,一条经验是,当遇到意 135 | 外的 C<404> 错误并且又不涉及静态文件服务时,应当首先检查是否在对应的 C 136 | 配置块中恰当地配置了 C 阶段的模块指令,例如 L、 137 | L 以及 L 138 | 之类。当然,Nginx 的 F 文件一般总是会提供各种意外 139 | 问题的答案,例如对于上面这个例子,我的 F 中有下面这条错 140 | 误信息: 141 | 142 | :text 143 | [error] 9364#0: *1 open() "/home/agentzh/test/html/auth" failed (2: No such file or directory) 144 | 145 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder08.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(八) = 2 | 3 | 前面我们详细讨论了 C、C 和 C 4 | 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这 5 | 三个阶段的众多 Nginx 模块及其配置指令。同时可以看到,请求处理阶段的划分 6 | 直接 7 | 影响到了配置指令的执行顺序, 8 | 熟悉这些阶段对于正确配置不同的 Nginx 模块并实现它们彼此之间的协同工作是非 9 | 常必要的。所以接下来我们接着讨论余下的那些阶段。 10 | 11 | 前面在 L 中提到,Nginx 处理请求的过程一共划 12 | 分为 11 个阶段,按照执行顺序依次是 C、C、C、C、C、C、C、C、C、C 13 | 以及 C. 14 | 15 | 最先执行的 C 阶段在 Nginx 读取并解析完请求头(request 16 | headers)之后就立即开始运行。这个阶段像前面介绍过的 C 17 | 阶段那样支持 Nginx 模块注册处理程序。比如标准模块 L 18 | 就在 C 阶段注册了处理程序,它的功能是迫使 Nginx 19 | 认为当前请求的来源地址是指定的某一个请求头的值。下面这个例子就使用了 L 20 | 模块提供的 L 和 L 21 | 这两条配置指令: 22 | 23 | :nginx 24 | server { 25 | listen 8080; 26 | 27 | set_real_ip_from 127.0.0.1; 28 | real_ip_header X-My-IP; 29 | 30 | location /test { 31 | set $addr $remote_addr; 32 | echo "from: $addr"; 33 | } 34 | } 35 | 36 | 这里的配置是让 Nginx 把那些来自 C<127.0.0.1> 的所有请求 37 | 的来源地址,都改写为请求头 C 所指定的值。同时该例使 38 | 用了标准内建变量 L 来 39 | 输出当前请求的来源地址,以确认是否被成功改写。 40 | 41 | 首先在本地请求一下这个 C 接口: 42 | 43 | :bash 44 | $ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test 45 | from: 1.2.3.4 46 | 47 | 这里使用了 curl 工具的 C<-H> 选项指定了额外的 HTTP 48 | 请求头 C. 从输出可以看到,L 49 | 变量的值确实在 C 阶段就已经成为了 C 50 | 请求头中指定的值,即 C<1.2.3.4>. 那么 Nginx 究竟是在什么时候 51 | 改写了当 52 | 前请求的来源地址呢?答案是:在 C 阶段。由于 C 53 | 阶段的运行远在 C 阶段之后,所以当在 C 54 | 配置块中通过 L 配置指令读取 L 55 | 内建变量时,读出的来源地址已经是经过 C 阶段篡改过的。 56 | 57 | 如果在请求上例中的 C 接口时没有指定 C 请求 58 | 头,或者提供的 C 请求头的值不是合法的 IP 地址,那么 Nginx 59 | 就不会对来源地址进行改写,例如: 60 | 61 | :bash 62 | $ curl localhost:8080/test 63 | from: 127.0.0.1 64 | 65 | $ curl -H 'X-My-IP: abc' localhost:8080/test 66 | from: 127.0.0.1 67 | 68 | 如果从另一台机器访问这个 C 接口,那么即使指定了合法的 C 69 | 请求头,也不会触发 Nginx 对来源地址进行改写。这是因为上例已经使用 70 | L 指令规定了来源地址 71 | 的改写操作只对那些来自 C<127.0.0.1> 的请求生效 72 | 。这种过滤机制可以避免来自其他不受信任的地址的恶意欺骗。当 73 | 然,也可以通过 L 74 | 指令指定一个 IP 网段(利用 L 中介绍过的“CIDR 75 | 记法”)。此外,同时配置多个 L 76 | 语句也是允许的,这样可以指定多个受信任的来源地址或地址段。下面是一个例子: 77 | 78 | :nginx 79 | set_real_ip_from 10.32.10.5; 80 | set_real_ip_from 127.0.0.0/24; 81 | 82 | 有的读者可能会问,L 模块究竟有什么实际用途呢?为什么我 83 | 们需要去改写请求的来源地址呢?答案是:当 Nginx 处理的请求经过了某个 HTTP 84 | 代理服务器的转发时,这个模块就变得特别有用。当原始的用户请求经过转发之 85 | 后,Nginx 接收到的请求的来源地址无一例外地变成了该代理服务器的 IP 地址 86 | ,于是 Nginx 以及 Nginx 背后的应用就无法知道原始请求的真实来源 87 | 。所以,一般我们会在 Nginx 之前的代理服务器中把请求的原始来源地址编码进某 88 | 个特殊的 HTTP 请求头中(例如上例中的 C 请求头),然后 89 | 再在 Nginx 一侧把这个请求头中编码的地址恢复出来。这样 Nginx 中的 90 | 后续处理阶段(包括 Nginx 背后的各种后端应用)就会认为这些 91 | 请求直接来自那些原始 92 | 的地址,代理服务器就仿佛不存在一样。正是因为这个需求,所以 L 93 | 模块才需要在第一个处理阶段,即 C 阶段,注册处理程序,以 94 | 便尽 95 | 可能早地改写请求的来源。 96 | 97 | C 阶段之后便是 C 98 | 阶段。我们曾在 L 中简单提到,当 L 99 | 模块的配置指令直接书写在 C 配置块中时,基本上都是运行在 C 100 | 阶段。下面就来看这样的一个例子: 101 | 102 | :nginx 103 | server { 104 | listen 8080; 105 | 106 | location /test { 107 | set $b "$a, world"; 108 | echo $b; 109 | } 110 | 111 | set $a hello; 112 | } 113 | 114 | 这里,配置语句 C 直接写在了 C 115 | 配 116 | 置块中,因此它就运行在 C 阶段。而 C 117 | 阶段要早于 C 阶段运行,因此写在 C 配 118 | 置块中的语句 C 便晚于外面的 C 语句运行。该例的测试结果证明了这一点: 120 | 121 | :bash 122 | $ curl localhost:8080/test 123 | hello, world 124 | 125 | 由于 C 阶段位于 C 阶 126 | 段之后,所以 C 配置块中的 L 127 | 指令也就总是运行在 L 模块改写请求的来源地址之后。来看 128 | 下面这个例子: 129 | 130 | :nginx 131 | server { 132 | listen 8080; 133 | 134 | set $addr $remote_addr; 135 | 136 | set_real_ip_from 127.0.0.1; 137 | real_ip_header X-Real-IP; 138 | 139 | location /test { 140 | echo "from: $addr"; 141 | } 142 | } 143 | 144 | 请求 C 接口的结果如下: 145 | 146 | :bash 147 | $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test 148 | from: 1.2.3.4 149 | 150 | 在这个例子中,虽然 L 指令写在了 L 151 | 的配置指令之前,但仍然晚于 L 模块执行。所以 C<$addr> 152 | 变量在 C 阶段被 L 153 | 指令赋值时,从 L 变量读出的来源 154 | 地址已经是经过改写过的了。 155 | 156 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder09.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(九) = 2 | 3 | 紧接在 C 阶段后边的是 C 4 | 阶段。这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 5 | 核心来完成当前请求与 C 配置块之间的配对工作。换句话 6 | 说,在此阶段之前,请求并没有与任何 C 配置块相关联。因此 7 | ,对于运行在 C 阶段之前的 C 8 | 和 C 阶段来说,只有 C 配置 9 | 块以及更外层作用域中的配置指令才会起作用。这就是为什么只有写在 C 10 | 配置块中的 L 模块的指令才会运行在 C 11 | 阶段,这也是为什么前面所有例子中的 L 模块的指令也都特 12 | 意写在了 C 配置块中,以确保其注册在 C 13 | 阶段的处理程序能够生效。 14 | 15 | 当 Nginx 在 C 阶段成功匹配了一个 C 16 | 配置块后,会立即打印一条调试信息到错误日志文件中。我们来看这样的一个例子: 17 | 18 | :nginx 19 | location /hello { 20 | echo "hello world"; 21 | } 22 | 23 | 如果启用了 Nginx 的“调试日志”,那么当请求 C
24 | 接口时,便可以在 F 文件中过滤出下面这一行信息: 25 | 26 | :bash 27 | $ grep 'using config' logs/error.log 28 | [debug] 84579#0: *1 using configuration "/hello" 29 | 30 | 我们有意省略了信息行首的时间戳,以便放在这里。 31 | 32 | 运行在 C 阶段之后的便是我们的老朋友 C 33 | 阶段。由于 Nginx 已经在 C 阶段完成了当前请 34 | 求与 C 的配对,所以从 C 阶 35 | 段开始,C 配置块中的指令便可以产生作用。前面已经介绍过, 36 | 当 L 模块的指令用于 C 块中时 37 | ,便是 38 | 运行在这个 C 阶段。另外,L 模 39 | 块的指令也是如此,还有 L 模块的 L 40 | 指令和 L 指令也不例外。 41 | 42 | C 阶段再往后便是所谓的 C 阶段 43 | 。这个阶段也像 C 阶段那样不接受 Nginx 模块注 44 | 册处理程序,而是由 Nginx 核心完成 C 阶段所要求的“内 45 | 部跳转”操作(如果 C 阶段有此要求的话)。先前 46 | 在 L 中已经介绍过了“内部跳转”的概念,同时演示了如何 47 | 通过 L 指令或者 L 48 | 指令来发起“内部跳转”。由于 L 指令运 49 | 行在 C 阶段,与这里讨论的 C 50 | 阶段无关,于是我们感兴趣的便只剩下运行在 C 阶段的 L 51 | 指令。回顾一下 L 中演示过的这个例子: 52 | 53 | :nginx 54 | server { 55 | listen 8080; 56 | 57 | location /foo { 58 | set $a hello; 59 | rewrite ^ /bar; 60 | } 61 | 62 | location /bar { 63 | echo "a = [$a]"; 64 | } 65 | } 66 | 67 | 这里在 C 中通过 L 68 | 指令把当前请求的 URI 无条件地改写为 C,同时发起一个“内部跳转 69 | ”,最终跳进了 C 中。这里比较有趣的地方是“内部 70 | 跳转”的工作原理。“内部跳转”本质上其实就是把当前的请求处理阶段强行倒退到 C 71 | 阶段,以便重新进行请求 URI 与 C 配置块的配对。 72 | 比如上例中,运行在 C 阶段的 L 73 | 指令就让当前请求的处理阶段倒退回了 C 阶段。由于此时 74 | 当前请求的 URI 已经被 L 75 | 指令修改为了 C,所以这一次换成了 C 76 | 与当前请求相关联,然后再接着从 C 阶段往下执行。 77 | 78 | 不过这里更有趣的地方是,倒退回 C 阶段的动作并不是发 79 | 生在 C 阶段,而是发生在后面的 C 80 | 阶段。上例中的 L 指令只是简单地指示 81 | Nginx 有必要在 C 阶段发起 82 | “内部跳转”。这个设计对于 Nginx 初学者来说,或许显得有些古怪:“为什 83 | 么不直接在 L 指令执行时立即进行跳转 84 | 呢?”答案其实很简单,那就是为了在最初匹配的 C 块中支持多 85 | 次 86 | 反复地改写 URI,例如: 87 | 88 | :nginx 89 | location /foo { 90 | rewrite ^ /bar; 91 | rewrite ^ /baz; 92 | 93 | echo foo; 94 | } 95 | 96 | location /bar { 97 | echo bar; 98 | } 99 | 100 | location /baz { 101 | echo baz; 102 | } 103 | 104 | 这里在 C 中连续把当前请求的 URI 105 | 改写了两遍:第一遍先无条件地改写为 C,第二遍再无条件地改写为 106 | C. 而这两条 L 语句只 107 | 会最终导致 C 阶段发生一次“内部跳转”操作,从而不至于 108 | 在第一次改写 URI 时就直接跳离了当前的 C 而导 109 | 致后面的 L 语句没有机会执行。请求 110 | C 接口的结果证实了这一点: 111 | 112 | :bash 113 | $ curl localhost:8080/foo 114 | baz 115 | 116 | 从输出结果可以看到,上例确实成功地从 C 一步跳到了 C 117 | 中。如果启用 Nginx “调试日志”的话,还可以从 C 118 | 阶段生成的 C 块的匹配信息中进一步证实这一点: 119 | 120 | :bash 121 | $ grep 'using config' logs/error.log 122 | [debug] 89449#0: *1 using configuration "/foo" 123 | [debug] 89449#0: *1 using configuration "/baz" 124 | 125 | 我们看到,对于该次请求,Nginx 一共只匹配过 C 和 C 126 | 这两个 C,从而只发生过一次“内部跳转”。 127 | 128 | 当然,如果在 C 配置块中直接使用 L 129 | 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”,因为此时 URI 130 | 改写发生在 C 阶段,早于执行 C 131 | 配对的 C 阶段。比如下面这个例子: 132 | 133 | :nginx 134 | server { 135 | listen 8080; 136 | 137 | rewrite ^/foo /bar; 138 | 139 | location /foo { 140 | echo foo; 141 | } 142 | 143 | location /bar { 144 | echo bar; 145 | } 146 | } 147 | 148 | 这里,我们在 C 阶段就把那些以 C 149 | 起始的 URI 改写为 C,而此时请求并没有和任何 C 150 | 相关联,所以 Nginx 正常往下运行 C 阶段 151 | ,完成最终的 C 匹配。如果我们请求上例中的 C 152 | 接口,那么 C 根本就没有机会匹配,因为在第一次(也 153 | 是 154 | 唯一的一次)运行 C 阶段时,当前请求的 URI 已经 155 | 被 156 | 改写为 C,从而只会匹配 C. 实 157 | 际请求的输出正是如此: 158 | 159 | $ curl localhost:8080/foo 160 | bar 161 | 162 | Nginx “调试日志”可以再一次佐证我们的结论: 163 | 164 | :bash 165 | $ grep 'using config' logs/error.log 166 | [debug] 92693#0: *1 using configuration "/bar" 167 | 168 | 可以看到,Nginx 总共只进行过一次 C 匹配,并无“内部 169 | 跳转”发生。 170 | 171 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder10.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(十) = 2 | 3 | 运行在 C 阶段之后的是所谓的 C 4 | 阶段。该阶段在 C 阶段之前执行,故名 C. 5 | 6 | 标准模块 L 和 L 7 | 就运行在此阶段,前者可以控制请求的访问频度,而后者可以限制访问的并发度。这里我 8 | 们仅仅 9 | 和它们打个照面,后面还会有机会专门接触到这两个模块。 10 | 11 | 前面反复提到的标准模块 L 其实也在这个阶段注册了处理程 12 | 序。有些读者可能会问:“这是为什么呢?它不是已经在 C 阶 13 | 段 14 | 注册处理程序了吗?”我们不妨通过下面这个例子来揭晓答案: 15 | 16 | :nginx 17 | server { 18 | listen 8080; 19 | 20 | location /test { 21 | set_real_ip_from 127.0.0.1; 22 | real_ip_header X-Real-IP; 23 | 24 | echo "from: $remote_addr"; 25 | } 26 | } 27 | 28 | 与先看前到的例子相比,此例最重要的区别在于把 L 29 | 的配置指令放在了 C 配置块中。前面我们介绍过,Nginx 30 | 匹配 C 的动作发生在 C 阶 31 | 段,而 C 阶段远远晚于 C 阶 32 | 段执行,所以在 C 阶段,当前请求还没有和任何 C 33 | 相关联。在这个例子中,因为 L 的配置指令都写在了 34 | C 配置块中,所以在 C 阶段,L 35 | 模块的处理程序没有看到任何可用的配置信息,便不会执行来源地址的改写工作了。 36 | 37 | 为了解决这个难题,L 模块便又特意在 C 38 | 阶段注册了处理程序,这样它才有机会运行 C 块中的配置指 39 | 令。正是因为这个缘故,上面这个例子的运行结果才符合直觉预期: 40 | 41 | $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test 42 | from: 1.2.3.4 43 | 44 | 不幸的是,L 模块的这个解决方案还是存在漏洞的,比如下面 45 | 这个例子: 46 | 47 | :nginx 48 | server { 49 | listen 8080; 50 | 51 | location /test { 52 | set_real_ip_from 127.0.0.1; 53 | real_ip_header X-Real-IP; 54 | 55 | set $addr $remote_addr; 56 | echo "from: $addr"; 57 | } 58 | } 59 | 60 | 这里,我们在 C 阶段将 L 61 | 的值保存到了用户变量 C<$addr> 中,然后再输出。因为 C 62 | 阶段先于 C 阶段执行,所以当 L 63 | 模块尚未在 C 阶段改写来源地址时,最初的来源地址就已经在 64 | C 阶段被读取了。上例的实际请求结果证明了我们的结论: 65 | 66 | :bash 67 | $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test 68 | from: 127.0.0.1 69 | 70 | 输出的地址确实是未经改写过的。Nginx 的“调试日志”可以进一步确认这一点: 71 | 72 | :bash 73 | $ grep -E 'http script (var|set)|realip' logs/error.log 74 | [debug] 32488#0: *1 http script var: "127.0.0.1" 75 | [debug] 32488#0: *1 http script set $addr 76 | [debug] 32488#0: *1 realip: "1.2.3.4" 77 | [debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F 78 | [debug] 32488#0: *1 http script var: "127.0.0.1" 79 | 80 | 其中第一行调试信息 81 | 82 | :text 83 | [debug] 32488#0: *1 http script var: "127.0.0.1" 84 | 85 | 是 L 语句读取 L 86 | 变量时产生的。信息中的字符串 C<"127.0.0.1"> 便是 L 87 | 当时读出来的值。 88 | 89 | 而第二行调试信息 90 | 91 | :text 92 | [debug] 32488#0: *1 http script set $addr 93 | 94 | 则显示我们对变量 C<$addr> 进行了赋值操作。 95 | 96 | 后面两行信息 97 | 98 | :text 99 | [debug] 32488#0: *1 realip: "1.2.3.4" 100 | [debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F 101 | 102 | 是 L 模块在 C 阶段改写当前请 103 | 求的来源地址。我们看到,改写后的新地址确实是期望的 C<1.2.3.4>. 但很 104 | 明显这个操作发生在 C<$addr> 变量赋值之后,所以已经太迟了。 105 | 106 | 而最后一行信息 107 | 108 | :text 109 | [debug] 32488#0: *1 http script var: "127.0.0.1" 110 | 111 | 则是 L 配置指令在输出时读取变量 C<$addr> 112 | 时产生的,我们看到它的值是改写前的来源地址。 113 | 114 | 看到这里,有的读者可能会问:“如果 L 模块不在 C 115 | 阶段注册处理程序,而在 C 阶段注册,那么上例不就可以工作了? 116 | ”答案是:不一定。因为 L 模块的处理程序也同样注册在 117 | C 阶段,而前面我们在 L 中特 118 | 别提到,在这种情况下,不同模块之间的执行顺序一般是不确定的,所以 L 119 | 的处理程序可能仍然在 L 语句之后执行。 120 | 121 | 一个建议是:尽量在 C 配置块中配置 L 122 | 这样的模块,以避免上面介绍的这种棘手的例外情况。 123 | 124 | 运行在 C 阶段之后的则是我们的另一个老朋友,C 125 | 阶段。前面我们已经知道了,标准模块 L、第三方模块 126 | L 以及第三方模块 L 127 | 的 L 指令就运行在这个阶段。 128 | 129 | C 阶段之后便是 C 阶段。从这个阶段 130 | 的名字,我们也能一眼看出它是紧跟在 C 阶段后面执行的。这个阶段 131 | 也和 132 | C 阶段类似,并不支持 Nginx 模块注册处理程 133 | 序,而是由 Nginx 核心自己完成一些处理工作。C 134 | 阶段 135 | 主要用于配合 C 阶段实现标准 L 136 | 模块提供的配置指令 L 的功能。 137 | 138 | 对于多个 Nginx 模块注册在 C 阶段的处理程序,L 139 | 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 140 | B 都在 C 阶段注册了与访问控制相关的处理程序,那就有两种协作 141 | 方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 142 | 只要其中任一个通过验证就算通过。第一种协作方式称为 C 方式(或者说“ 143 | 与关系”),第二种方 144 | 式则被称为 C 方式(或者说“或关系”)。默认情况下,Nginx 使用 145 | 的是 C 146 | 方式。下面是一个例 147 | 子: 148 | 149 | :nginx 150 | location /test { 151 | satisfy all; 152 | 153 | deny all; 154 | access_by_lua 'ngx.exit(ngx.OK)'; 155 | 156 | echo something important; 157 | } 158 | 159 | 这里,我们在 C 接口中同时配置了 L 模 160 | 块和 L 模块,这样 C 阶 161 | 段就由这两个模块一起来做检验工作。其中,语句 C 会让 L 162 | 模块的处理程序总是拒绝当前请求,而语句 C 163 | 则总是允许访问。当我们通过 L 指令配置了 164 | C 165 | 方式时,就需要 C 阶段的所有模块都通过验证,但不幸的是,这里 166 | L 模块总是会拒绝访问,所以整个请求就会被拒: 167 | 168 | :bash 169 | $ curl localhost:8080/test 170 | 171 | 403 Forbidden 172 | 173 |

403 Forbidden

174 |
nginx
175 | 176 | 177 | 178 | 细心的读者会在 Nginx 错误日志文件中看到类似下面这一行的出错信息: 179 | 180 | :text 181 | [error] 6549#0: *1 access forbidden by rule 182 | 183 | 然而,如果我们把上例中的 C 语句更改为 C, 185 | 186 | :nginx 187 | location /test { 188 | satisfy any; 189 | 190 | deny all; 191 | access_by_lua 'ngx.exit(ngx.OK)'; 192 | 193 | echo something important; 194 | } 195 | 196 | 结果则会完全不同: 197 | 198 | :bash 199 | $ curl localhost:8080/test 200 | something important 201 | 202 | 即请求反而最终通过了验证。这是因为在 C 方式下,C 203 | 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中,L 204 | 模块的 L 语句总是会通过验证的。 205 | 206 | 在配置了 C 的情况下,只有当 C 阶 207 | 段 208 | 的所有模块的处理程序都拒绝访问时,整个请求才会被拒,例如: 209 | 210 | :nginx 211 | location /test { 212 | satisfy any; 213 | 214 | deny all; 215 | access_by_lua 'ngx.exit(ngx.HTTP_FORBIDDEN)'; 216 | 217 | echo something important; 218 | } 219 | 220 | 此时访问 C 接口才会得到 C<403 Forbidden> 错误 221 | 页。这里,C 阶段参与了 C 阶段各模 222 | 块处理程序的“或关系”的实现。 223 | 224 | 值得一提的是,上面这几个的例子需要 L 0.5.0rc19 225 | 或以上版本;之前的版本是不能和 C 配置语句一起工作的 226 | 。 227 | 228 | -------------------------------------------------------------------------------- /zh-cn/02-NginxDirectiveExecOrder11.tut: -------------------------------------------------------------------------------- 1 | = Nginx 配置指令的执行顺序(十一) = 2 | 3 | 紧跟在 C 阶段之后的是 C 阶 4 | 段。这个阶段专门用于实现标准配置指令 L 5 | 的功能,并不支持 Nginx 模块注册处理程序。由于 L 6 | 指令在许多 FastCGI 应用的配置中都有用到,所以我们不妨在这里简单介绍一下 7 | 。 8 | 9 | L 指令接受两个以上任意数量的参数,每个 10 | 参数都指定了一个 URI. 这里假设配置了 C 个参数,则 Nginx 11 | 会在 C 阶段,依次把前 C 个参数映射为文件 12 | 系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某 13 | 个文件系统对象存在,就会在 C 阶段把当前请求的 URI 14 | 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳 15 | 转” 16 | )。如果前 C 个参数所对应的文件系统对象都不存在,C 17 | 阶段就会立即 18 | 发起“内部跳转”到最后一个参数(即第 C 个参数)所指定的 URI. 19 | 20 | 前面在 L 和 L 21 | 中已经看到静态资源服务模块会把 22 | 当前请求的 URI 映射到文件系统,通过 L 配置 23 | 指令所指定的“文档根目录”进行映射。例如,当“文档根目录”是 F 24 | 的时候,请求 URI C 会被映射为文件 F, 25 | 而请求 URI C 则会被映射为目录 F. 26 | 注意这里是如何通过 URI 末尾的斜杠字符是否存在来区分“目录”和“文件”的 27 | 。我们正在讨论的 L 配置指令使用同样的 28 | 规则来完成其各个参数 URI 到文件系统对象的映射。 29 | 30 | 不妨来看下面这个例子: 31 | 32 | :nginx 33 | root /var/www/; 34 | 35 | location /test { 36 | try_files /foo /bar/ /baz; 37 | echo "uri: $uri"; 38 | } 39 | 40 | location /foo { 41 | echo foo; 42 | } 43 | 44 | location /bar/ { 45 | echo bar; 46 | } 47 | 48 | location /baz { 49 | echo baz; 50 | } 51 | 52 | 这里通过 L 指令把“文档根目录”配置为 F, 53 | 如果你系统中的 F 路径下存放有重要数据,则可以把它替换为 54 | 其他 55 | 任意路径,但此路径对运行 Nginx worker 进程的系统帐号至少有可读权限 56 | 。我 57 | 们在 C 中使用了 L 58 | 配置指令,并提供了三个参数,C、C 和 C. 59 | 根据前面对 L 指令的介绍,我们可以知道 60 | ,它会在 C 阶段依次检查前两个参数 C 和 61 | C 所对应的文件系统对象是否存在。 62 | 63 | 不妨先来做一组实验。假设现在 F 路径下是空的,则第一个参 64 | 数 C 65 | 映射成的文件 C 是不存在的;同样,对于第二个参数 66 | C 所映射成的目录 C 也是不存在的 67 | 。于是此时 Nginx 会在 C 阶段发起到最后一个参数 68 | 所指定的 URI(即 C)的“内部跳转”。实际的请求结果证实了这一点 69 | : 70 | 71 | :bash 72 | $ curl localhost:8080/test 73 | baz 74 | 75 | 显然,该请求最终和 C 绑定在一起,执行了输出 C 76 | 字符串的工作。上例中定义的 C 和 C 完全不会参与这里的运行过程,因为对于 L 78 | 的前 C 个参数,Nginx 只会检查文件系统,而不会去执行 URI 79 | 与 80 | C 之间的匹配。 81 | 82 | 对于上面这个请求,Nginx 会产生类似下面这样的“调试日志”: 83 | 84 | :bash 85 | $ grep trying logs/error.log 86 | [debug] 3869#0: *1 trying to use file: "/foo" "/var/www/foo" 87 | [debug] 3869#0: *1 trying to use dir: "/bar" "/var/www/bar" 88 | [debug] 3869#0: *1 trying to use file: "/baz" "/var/www/baz" 89 | 90 | 通过这些信息可以清楚地看到 C 阶段发生的事情:Nginx 91 | 依次检查了文件 F 和目录 F, 92 | 末了又处理了最后一个参数 C. 这里最后一条“调试信息”容易产生误解 93 | ,会让人误以为 Nginx 也把最后一个参数 C 给映射成了文件系统 94 | 对象进行 95 | 检查,事实并非如此。当 L 指令处理到它 96 | 的最后一个参数时,总是直接执行“内部跳转”,而不论其对应的文件系统对象是否存在。 97 | 98 | 接下来再做一组实验:在 F 下创建一个名为 F 99 | 的文件,其内容为 C(注意你需要有 F 100 | 目录下的写权限): 101 | 102 | :bash 103 | $ echo 'hello world' > /var/www/foo 104 | 105 | 然后再请求 C 接口: 106 | 107 | :bash 108 | $ curl localhost:8080/test 109 | uri: /foo 110 | 111 | 这里发生了什么?我们来看,L 指令的第一 112 | 个参数 C 可以映射为文件 F,而 Nginx 113 | 在 C 阶段发现此文件确实存在,于是立即把当前请求的 URI 114 | 改写为这个参数的值,即 C,并且不再继续检查后面的参数,而直接运行 115 | 后面的请求处理阶段。 116 | 117 | 上面这个请求在 C 阶段所产生的“调试日志”如下: 118 | 119 | :bash 120 | $ grep trying logs/error.log 121 | [debug] 4132#0: *1 trying to use file: "/foo" "/var/www/foo" 122 | 123 | 显然,在 C 阶段,Nginx 确实只检查和处理了 C 124 | 这一个参数,而后面的参数都被“短路”掉了。 125 | 126 | 类似地,假设我们删除刚才创建的 F 文件,而在 F 127 | 下创建一个名为 C 的子目录: 128 | 129 | :bash 130 | $ mkdir /var/www/bar 131 | 132 | 则请求 C 的结果也是类似的: 133 | 134 | :nginx 135 | $ curl localhost:8080/test 136 | uri: /bar 137 | 138 | 在这种情况下,Nginx 在 C 阶段发现第一个参数 C 139 | 对应的文件不存在,就会转向检查第二个参数对应的文件系统对象(在这里便是目录 F)。 140 | 由于此目录存在,Nginx 就会把当前请求的 URI 改写为第二个参数的值,即 141 | C(注意,原始参数值是 C,但 L 142 | 会自动去除末尾的斜杠字符)。 143 | 144 | 这一组实验所产生的“调试日志”如下: 145 | 146 | :bash 147 | $ grep trying logs/error.log 148 | [debug] 4223#0: *1 trying to use file: "/foo" "/var/www/foo" 149 | [debug] 4223#0: *1 trying to use dir: "/bar" "/var/www/bar" 150 | 151 | 我们看到,L 指令在这里只检查和处理了它 152 | 的前两个参数。 153 | 154 | 通过前面这几组实验不难看到,L 指 155 | 令本质上只是有条件地改写当前请求的 URI,而这里说的“条件”其实就是文件系统上 156 | 的对象是否存在。当“条件”都不满足时,它就会无条件地发起一个指定的“内部跳转”。 157 | 当然,除了无条件地发起“内部跳转”之外,L 158 | 指令还支持直接返回指定状态码的 HTTP 错误页,例如: 159 | 160 | :nginx 161 | try_files /foo /bar/ =404; 162 | 163 | 这行配置是说,当 C 和 C 参数所对应的文件系统对象 164 | 都不存在时,就直接返回 C<404 Not Found> 错误页。注意这里它是如 165 | 何使用等号字符前缀来标识 HTTP 状态码的。 166 | 167 | --------------------------------------------------------------------------------