agentzh 的 Nginx 教程的学习笔记
server {
listen 8088;
location /test {
content_by_lua '
if ngx.var.cookie_user == nil then
ngx.say("cookie user: missing")
else
ngx.say("cookie user: [", ngx.var.cookie_user, "]")
end
';
}
location /exist {
# 通过 nginx lua,未定义的变量也能够正常输出
content_by_lua '
ngx.say("$blah = ", ngx.var.blah)
';
}
location /array {
array_split "," $arg_names to=$array;
## array_map 的作用就是将数组内的每个值改变并重新赋值
array_map "{$array_it}" $array;
array_join " " $array to=$res;
echo $res;
}
location /foo {
# 从下面例子可以看出,在Lua中未初始化的Nginx变量为空字符串。
content_by_lua '
if ngx.var.foo == nil then
ngx.say("foo is nil")
else
ngx.say("$foo = [", ngx.var.foo, "]")
end
';
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
}
server {
listen 8087;
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
location /test {
echo "name: [$arg_name]";
}
location /test2 {
content_by_lua '
if ngx.var.arg_name == nil then
ngx.say("name: missing")
else
ngx.say("name: [", ngx.var.arg_name, "]")
end
';
}
}
map $uri $tag {
default 0;
/cache 1;
/cache-sub 2;
}
server {
listen 8086;
# 下面这两个例子表明,$args 和 $uri 内建变量作用于当前请求(包括主请求和子请求)。
location /main {
echo "main args: $args";
echo_location /sub "a=1&b=2";
}
location /sub {
echo "sub args: $args";
}
location /uri {
echo "main uri: $uri";
echo_location /uri-sub;
}
location /uri-sub {
echo "sub uri: $uri";
}
# $request_method 变量总是作用于主方法
location /method {
echo_location /method-sub;
echo "main method: $echo_request_method";
}
location /method-sub {
echo "sub method: $echo_request_method";
}
# 从这个例子可以看出,auth_request发起的子请求和父请求共享变量 $tag
# $tag 首先在子请求中读取被缓存起来了,然后再来父请求中读取,读取的就是被缓存的值。
location /cache {
auth_request /cache-sub;
echo "main :$tag";
}
location /cache-sub {
echo "sub: $tag";
}
}
server {
listen 8085;
# ## 请求类型
# Nginx 的请求分为两种,主请求(main request)和子请求(subrequest)。
#
# 主请求既是真实世界中的HTTP请求(包括包含了echo_exec和rewrite的内部跳转)。
# 子请求是Nginx内部发起的级联请求,和真实世界中的HTTP请求和网络请求都无关,是Nginx用来将一个主请求分解成多个子请求的方法。
#
# ## 子请求
#
# + 子请求外观上很像HTTP请求,但内部其实是C函数调用,和套接字通信和任何网络通信都无关,所以效率很高。
# + 子请求的目的是为了将主请求分解成多个小的请求去并行地或者串行地执行,然后由各个子请求通力合作完成主请求
# + 子请求的概念是相对的,子请求可以调用子子请求,甚至可以递归调用
#
# ## 子请求例子
location /main {
echo_location /foo;
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}
# 在上面例子中,通过第三方`ngx_echo`模块的`echo_location`指令分别发起到`/foo`和`/bar`这两个接口GET类型的子请求,其执行是按照书写顺序串行执行的,两个子请求的执行结果按执行顺序拼接起来,作为`/main`接口的最终输出
## 子请求变量值容器
### 生命周期
# 变量值容器的生命周期是和当前请求(这个请求既指主请求,也指子请求)相关联的。
### 父子模块之间不共享变量
# 一些模块的子请求会保存父请求变量值容器的一份副本。如下面的例子所示:
location /parent {
set $name parent;
echo_location /alice;
echo_location /bob;
echo "main: $name";
}
location /alice {
set $name alice;
echo "alice: $name";
}
location /bob {
echo "bob before: $name";
set $name bob;
echo "bob after: $name";
}
# ➜ /home/yundongx $ curl '172.17.0.2:8085/parent'
# alice: alice
# bob before: bob
# bob after: bob
# main: parent
# 从输出结果中可以看出,`/alice`和`/bob`子模块改变了$name的值,但是并没有影响父模块,同时他们之间也没有互相影响。也就是说父模块和各个子模块各自有各自的变量容器副本,他们之间并不共享。
### 父子模块之间共享变量
# 另外一些Nginx模块发起的子请求会自动共享父请求的变量值容器,例如第三方的`ngx_auth_request`:
# 在我这里没有重现父子模块共享变量的情况。
# 这里理论上应该输出 "main: sub",实际输出是"main: main"
# auth_requst 就是利用子请求做一些判断,如果子请求返回的状态码不是2xx,那么它会中断当前父请求,然后返回相应出错页。否则就会忽略子请求的body,让父请求继续执行。
location /share {
set $var main;
auth_request /sub;
echo "main: $var";
}
location /sub {
set $var sub;
echo "sub: $var";
}
}
# 1. map 的作用是定义一个映射关系,将$args的值映射到$foo上
# 2. map 必须要放到 http 块中。
# 3. map 指令的工作原理就是为用户变量注册取处理程序,然后在取处理程序中取出这个值并进行缓存,缓存周期为一次请求
# 4. 根据map的工作原理(为用户变量注册取处理程序),只有当用户定义的变量进行取值的时候,才会对其值进行计算并缓存,这种方式称>为惰性求值。
# 5. 所以map定义在http块中,并不会使得每个location接口被请求的时候,都重新计算一遍这个变量的值。只有当某个接口用到这个值的>时候,才会对这个值进行计算和缓存。
# 6. Nginx 大部分情况使用主动求值。例如 `set $b "$a, $a"`,会在赋值的时候直接计算出$b的值,而不是等到对$b进行读取的时候,再
计算其值。
map $args $foo {
default 0;
debug 1;
}
server {
listen 8084;
location /test {
set $orig_foo $foo;
set $args debug;
echo 'original foo: $orig_foo';
echo "foo: $foo";
}
}
# ➜ /home/yundongx/Bundle/k-vim (bwangel) $ curl '172.17.0.2:7080/test?key=val'
# args: foo=1&bar=2
# ngx_proxy 模块的 proxy_pass 指令转发HTTP请求的时候,会把当前请求的URL参数也转发到远端
# 1. 在Nginx中,拥有值容器的变量成为被索引的(indexed),不拥有值容器的变量为未索引的(non-indexed)
# 2. 像arg_XXX 和 cookie_XXX 这种变量群,就是未被索引的,当用户读取这些变量的时候,这些变量的*取处理程序(get handler)* 就会解析Nginx的查询参数字段或者Cookie请求头,来动态地解析出这些参数的值。
server {
listen 7080;
location /test {
set $args "foo=1&bar=2";
proxy_pass http://127.0.0.1:7081/args;
}
}
server {
listen 7081;
location /args {
echo "args: $args";
}
}
server {
listen 8083;
# 1. $args 可以被修改
# 2. $args 和 $arg_XXX 一样,并不是通过在当前请求的容器中存放其值,而是通过在 Nginx 核心中进行了特别处理。
# 3. $args 读取和设置时会去 Nginx 核心中专门存放URL参数的位置去读取或设置数据
location /test {
set $orig_args $args;
set $args "a=3&b=4";
echo "original_args: $orig_args";
echo "args: $args";
}
# 4. 我们修改了 $args 的值,也影响了$arg_XXX 变量的值
# ➜ /home/yundongx/Bundle/k-vim (bwangel) $ curl '172.17.0.2:8083/test2?&a=0&b=1'
# original a: 0
# a: 5
location /test2 {
set $orig_a $arg_a;
set $args "a=5";
echo "original a: $orig_a";
echo "a: $arg_a";
}
}
server {
listen 8082;
# $arg_xxx 是一个变量群(Nginx 预定义变量),可以代表查询参数
# 1. 查询参数不区分大小写
# $ curl 'http://localhost:8082/test?NAME=4'
# name: 4
# class:
# 2. 不会对查询参数进行解码,返回的是原始内容
# $ curl 'http://localhost:8082/test?name=hello%20world&class=9'
# name: hello%20world
# class: 9
location /test {
echo "name: $arg_name";
echo "class: $arg_class";
}
# 3. 可以使用第三方模块 ngx_set_misc 提供的 set_unescap_uri 配置指令获取转义了的查询参数
# $ curl 'http://localhost:8082/unescape_test?name=hello%20world&class=9'
# name: hello world
# class: 9
location /unescape_test {
set_unescape_uri $name $arg_name;
set_unescape_uri $class $arg_class;
echo "name: $name";
echo "class: $class";
}
# 4. 变量群除了$arg_xxx 还有$cookie_xxx, $http_xxx, $send_http_xxx
# 5. 这些內建变量都是只读的,不能进行赋值,这样在某些版本的Nginx中甚至会导致崩溃
# $ nginx -s reload
# nginx: [emerg] the duplicate "uri" variable in /opt/openresty/nginx/conf/./conf.d/var2.conf:36
location /assignment {
# set $uri 3; # error config
echo "Error";
}
}
geo $dollar {
default "$";
}
server {
listen 8080;
location /test2 {
echo "This is a dollar sing: $dollar";
}
location /test3{
set $first hello;
echo "${first}world";
}
location /test {
set $foo hello;
echo "foo: $foo";
}
# Nginx 的变量创建是在服务器初始化的时候创建的,服务器启动以后无法动态地创建变量。
# 变量名是全局共享的,甚至可以跨 Server 块。
# Nginx 的赋值是在请求的时候被赋值的,且各个请求都有各自独立的变量值的副本,也就是说变量的值不会共享。
# Nginx 变量的生命周期是不可能跨越请求边界的。
# 所以下面的配置中,请求/foo并不会获得/bar中设置的$foo的值。
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
# 下面在/foo1中使用了一个内部跳转,即整体还是一个请求,Nginx内部从一个location跳转到了另外一个location。
# 内部跳转很像bash中的exec命令和C中的goto语法,都是有去无回。
# 在内部跳转中,由于是同一个请求,所以变量值是共享的,在/bar1 中可以使用/foo1中赋值的变量值。
# 若是直接请求 /bar1,那么$a的值就是空。
# Nginx 变量值的生命周期是和请求绑定的,和location无关。
location /foo1 {
set $a hello;
echo_exec /bar1;
}
# 利用rewrite指令也可以发起内部跳转。
location /foo2 {
set $a hello;
rewrite ^ /bar1;
}
location /bar1 {
echo "a = [$a]";
}
# $uri和$request_uri都是 ngx_http_core 模块的内建变量。
# $uri 表示请求地址,经过解码,不包含请求参数
# $request_uri 表示请求地址,未经过解码,包含请求参数
# 请求结果如下
# ➜ /root $ curl http://localhost:8080/test4?a=3&b=4
# uri = /test4
# request_uri = /test4?a=3&b=4
# ➜ /root $ curl http://localhost:8080/test4/hello%20world?a=3&b=4
# uri = /test4/hello world
request_uri = /test4/hello%20world?a=3&b=4
location /test4 {
echo "uri = $uri";
echo "request_uri = $request_uri";
}
}
server {
listen 7090;
# find-config 阶段是 Nginx 核心将请求和 location 关联起来
# 在这个阶段之前,请求并没有和某个 location 块关联起来
# [debug] 301#0: *1 using configuration "/hello"
# 这条调试语句即显示将请求和 location 块关联起来
location /hello {
echo "hello, world";
}
# post-rewrite 阶段不接受模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段要求的"内部跳转"操作。
## 1. rewrite 指令
## rewrite 指令发起一个内部跳转,修改url,跳转到另外一个块
## 这个指令是在 rewrite 阶段运行的,所以 /bar 中能够获取到 /foo 中设置的变量
location /foo {
set $a "xff";
rewrite ^ /bar;
}
location /bar {
echo "a = [$a]";
}
## 2. rewrite 指令的原理
## rewrite 指令的原理就是重写当前请求的uri,然后通知nginx内核,在 post-rewrite 阶段发起一个内部跳转
## 这个内部跳转其实就是让指令的阶段强行退回 find-config 阶段,让请求和 location 重新关联
## 之所以这样做的而不是在 rewrite 阶段直接跳转的原因是,这样可以支持多条 rewrite 指令,最终生效的只会是最后
## 一条rewrite指令。
## 下面这个例子的运行日志是:
## [debug] 333#0: *6 using configuration "/foz"
## [debug] 333#0: *6 using configuration "/bax"
## 日志表明只进入了/foz 和 /bax 两个location
location /foz {
rewrite ^ /baz;
rewrite ^ /bax;
set $foo "foz";
}
location /baz {
echo "baz: $foo";
}
location /bax {
echo "bax: $foo";
}
## 3. Server 块中的 rewrite 指令
## 下面指令的运行日志为:
## [debug] 461#0: *8 using configuration "/bay"
## server 块中的 rewrite 指令运行在 server-rewrite阶段中,这里直接改写 /fox url为 /bay
## 由于 server-rewrite 阶段在 find-config 之前,所以这里直接进入 /bay 块,不会进入 /fox 块
rewrite ^/fox /bay;
location /fox {
echo "fox";
}
location /bay {
echo "bay";
}
}
server {
listen 7080;
# Nginx 指令一共分为11阶段,按顺序依次是:
# 1. post-read
# 2. server-rewrite
# 3. find-config
# 4. rewrite
# 5. post-rewrite
# 6. preaccess
# 7. access
# 8. post-access
# 9. try-files
# 10. content
# 11. log
# 这两条指令的作用是,如果请求源ip 为 172.17.0.1,那么或从 `X-My-IP` 头中取出真是IP设置为源IP地址。
# 这两条指令运行在post-read 阶段
# ➜ /home/yundongx $ curl -H 'X-My-IP: 10.0.1.124' 172.17.0.2:7080/test
# from: 10.0.1.124
# 如果没有指定 `X-My-IP` 头或者指定的IP地址不合法,那么就不会改变源IP
# ➜ /home/yundongx $ curl 172.17.0.2:7080/test
# from: 172.17.0.1
# ➜ /home/yundongx $ curl -H 'X-My-IP: abc' 172.17.0.2:7080/test
# from: 172.17.0.1
set_real_ip_from 172.17.0.1;
real_ip_header X-My-IP;
# 这个指令的作用主要是,当在客户端和服务器之间存在代理服务器的时候,可以将源IP地址从代理服务器的地址改成客户端的地址。
location /test {
set $addr $remote_addr;
echo "from: $addr";
}
# 写在 server 块中的 ngx_rewrite 模块的指令基本都是运行在 server-rewrite 阶段
# 这里就是server块中的set 指令先运行,然后再运行 location 块中的。
location /server {
set $b "$a, world";
echo $b;
}
set $a "hello";
}
server {
listen 7070;
# 下面这条指令的运行顺序是:
# curl 172.17.0.2:7070/index.html
# 首先没有content阶段的指令运行(也就没有content阶段的模块来处理),
# 所以处理权落到了content阶段末尾的ngx_index, ngx_autoindex, ngx_static静态资源服务模块上
# 由于请求的url不以/结尾,所以由ngx_static 模块来处理。
# 这个模块根据 root 指令的配置,到指定目录下查找和url对应的文件。
location / {
root /opt/openresty/nginx/html/;
}
# 如果没有root指令的话,ngx_static 模块就会去 "缺省的文档根目录" 下查找对应的静态文件
# 缺省的文档根目录是 配置前缀 + html/
# 配置前缀是nginx的安装目录,即nginx编译的时候传给配置脚本的 --prefix 参数
# 或者我们也可以在启动 nginx 的时候通过 -p 参数来设置配置前缀
# 配置前缀也代表着Nginx运行时的当前目录
# 下面这里例子中,我们只使用了access阶段的指令,没有使用content阶段的指令
# 那么在content阶段就会由静态资源服务模块来处理,去查找 文档根目录下的 auth 文件
# 然后就会抛出 404 错误。
# 在DEBUG日志中可以看到这样的一条错误
# 2016/11/07 04:52:53 [error] 773#0: *12 open() "/opt/openresty/nginx/html/auth" failed
# (2: No such file or directory), client: 172.17.0.1, server: ,
# request: "GET /auth HTTP/1.1", host: "172.17.0.2:7070")
location /auth {
access_by_lua '
-- a lot of code omitted here
';
}
}
server {
listen 7076;
# 当在location中没有使用 content 阶段的指令的时候,Nginx会自动把当前URI映射到文件系统。使用静态资源服务模块
## 1. 如果有content阶段的指令,会注册“内容处理程序”
## 2. 如果没有“内容处理程序”,那么就会使用静态资源服务模块
## 3. Nginx 会依次运行这三个静态资源服务模块: ngx_index > ngx_autoindex > ngx_static
## ngx_index, ngx_autoindex 会只处理那些以 / 结尾的请求,对于不以 / 结尾的请求会直接忽略
## ngx_static 只处理不以 / 结尾的请求,对于以 / 结尾的请求会直接忽略
## ngx_index 模块主要用于在文件系统目录中自动查找指定的首页文件
## 如果发现了index.html或index.htm文件,会发起一个"内部跳转"到 /index.html或/index.htm
## 通过下面这个例子可以发现直接"内部跳转"到了 /index.html location。
## 同时注意内部跳转的新请求URI为 /index.html,在匹配location 块的时候,根据“最长子串匹配语义”,会匹配到 /index.html
location / {
root /opt/openresty/nginx/html/;
index index.html index.htm;
# autoindex on;
}
location /index.html {
set $a 32;
echo "a = $a";
}
## 在上面例子中,如果把 index.html 删除了,就会出现403错误,同时Nginx日志中会写上:
## 2016/11/01 04:52:51 [error] 966#0: *15 directory index of "/opt/openresty/nginx/html/" is forbidden,
## client: 172.17.0.1, server: , request: "GET / HTTP/1.1", host: "172.17.0.2:7076"
## 即这个目录无法被索引,我们可以开启 autoindex on 来开启索引,这样文件目录就会被显示出来了
## ngx_index 仅仅实现内部跳转,而真正把静态文件内容作为响应数据输出出去的就是 ngx_static 例子了。
}
server {
listen 7075;
# nginx 的content阶段在rewrite和access之后
# 肩负着生成内容并输出到 HTTP 响应的使命,比较重要
location / {
# rewrite phase
set $age 1; # module ngx_rewirte
rewrite_by_lua "ngx.var.age = ngx.var.age + 1"; # ngx_lua, rewrite tail
# access phase
deny 10.32.168.49; # ngx_access
access_by_lua "ngx.var.age = ngx.var.age * 3"; # access_by_lua, access tail
# content phase
echo "age = $age"; # ngx_echo, content tail
}
# 当第三方模块注册到 content 阶段的时候,其实就是在当前的 location 块中注册 “内容处理程序(content handler)”
# 而通常一个一个 location 块只能注册一个 “内容处理程序”
# 下面例子中,这两条指令分别来自于 ngx_lua 和 ngx_echo 这两个模块
# 由于一个 location 块只能为一个模块注册内容处理程序,所以这两条指令只会执行一个,而具体哪个会执行则是不确定的
# 所以我们应当避免在一个 location 块中使用多个模块的 content 阶段的指令
location /test {
content_by_lua 'ngx.say("World")';
echo hello;
}
# 并非所有模块的指令都支持在一个location中调用多次。
# 例如 ngx_lua 模块的 content_by_lua 指令就只支持在一个 location 中被调用一次
# 下面的例子在启动的时候就会报错
location /lua {
content_by_lua 'ngx.say("hello")';
content_by_lua 'ngx.say("World")';
# nginx: [emerg] "content_by_lua" directive is duplicate in /opt/openresty/nginx/conf/conf.d/order5.conf:33
# 正确的写法应该是 content_by_lua 'ngx.say("hello") ngx.say("world")';
}
# 类似的 ngx_proxy 的 proxy_pass 指令和 ngx_echo 的 echo 指令也不能一起执行
# 因为这两条指令属于不同的模块,不能在同一个 location 中执行
# 正确的执行做法应该是使用 echo_before_body 和 echo_after_body
# 这两条指令并不运行在请求处理的那是一个阶段当中,而是运行在"输出过滤器"中,所以可以和content阶段的指令写在一起
location /foo {
echo "contents to be proxied";
}
location /proxy {
# echo "before..."; # Error
echo_before_body "before...";
proxy_pass http://127.0.0.1:7075/foo;
# echo "after..."; # Error
echo_after_body "after...";
}
}
server {
listen 7073;
# 下面的这两个 more_set_input_headers 指令和 rewirte_by_lua 的运行阶段都是 rewrite tail。
# 即他们会等 ngx_rewrite_module 的指令都执行完了再来执行
location /test {
set $value dog;
more_set_input_headers "X-Species: $value";
set $value cat;
echo "X-Species: $http_x_species";
}
# 使用nginx 的第三方模块 ngx_access 做访问控制比 access_by_lua 模块要快一个数量级
# 不过他们之间差别极小,是十几微妙
location /lua {
set $a 1;
rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
set $a 56;
}
location /allow {
allow 172.17.0.1;
deny all;
echo "[$remote_addr]: Hello, World";
}
location /deny {
access_by_lua '
if ngx.var.remote_addr == "172.17.0.1" then
return
end
ngx.exit(403)
';
echo "hello world";
}
}
server {
listen 7082;
# 下面这几条指令是ngx_rewrite模块和ngx_set_misc模块中的指令混合执行。
# 他们都是运行在 rewrite 阶段
# 这几条指令是按顺序执行的
location /test {
set $a "hello%20world";
set_unescape_uri $b $a;
set $c "$b!";
echo $c;
}
# 这个例子也表明 ngx_lua 模块中的set_by_lua指令可以和 ngx_rewrite 模块中的set指令混合使用
location /lua {
set $a 32;
set $b 56;
set_by_lua $c "return ngx.var.a + ngx.var.b";
set $equation "$a + $b = $c";
echo $equation;
}
# 有些第三方模块的指令可以和 ngx_rewrite 模块的指令混合使用,但并不是所有三方模块都可以混合使用
# 这些模块使用了一些特殊的技术(ngx_devel_kit)将自己的执行指令注入到ngx_rewrite模块的指令序列中。
# 支持自己的指令和 ngx_rewrite 模块的指令混合使用的模块有 ngx_lua, nginx_array_var, nginx_encrypted_session, ngx_set_misc
# 在另外一些第三方模块中,尽管他们的指令都是在rewrite阶段执行的,但是它们并不能按顺序执行,而是先执行完一个模块的指令,再来执行另外一个模块的指令
# 除非文档有明确的交代,否则用户不应该依赖与第三方模块和 ngx_rewrite 模块的指令的执行顺序是按书写顺序执行的。
}
server {
listen 7011;
location /test {
set_real_ip_from 172.17.0.1;
real_ip_header X-Real-IP;
echo "from: $remote_addr";
set $addr $remote_addr;
echo "from: $remote_addr, $addr";
}
}
server {
listen 9091;
# Nginx 请求执行阶段
# 1. rewrite
# set 指令在 rewrite 阶段执行。
# 2. access
# 3. content
# echo 指令在 content 阶段执行。
location /test {
set $a 32;
set $saved_a $a;
set $a 56;
echo $saved_a;
echo $a;
}
# Nginx 某些指令并不与请求处理阶段相关联,例如 `geo`,`map`
# 这些不与处理阶段相关联的指令都是声明性(declarative)的,即不直接产生某种动作或者过程。
}