Skip to content

Instantly share code, notes, and snippets.

@arunjayakumar01
Forked from BruceChen7/nginx.md
Created August 9, 2020 18:34
Show Gist options
  • Save arunjayakumar01/b1873d3a5a4011548a9336b7877d64b9 to your computer and use it in GitHub Desktop.
Save arunjayakumar01/b1873d3a5a4011548a9336b7877d64b9 to your computer and use it in GitHub Desktop.
[#nginx#配置]#nginx

资料

4 context

  • main
  • server
  • upstream
  • location

Directives in the main context apply to everything; directives in the server context apply to a particular host/port; directives in the upstream context refer to a set of backend servers; and directives in a location context apply only to matching web locations (e.g., "/", "/images", etc.) A location context inherits from the surrounding server context, and a server context inherits from the main context. The upstream context neither inherits nor imparts its properties;

变量

在Nginx 配置中,变量只能存放一种类型的值,因为也只存在一种类型的值,那就是字符串。

set	$a	"hello	world";
set	$b	"$a,	$a";

这两条指令顺序执行完之后,$a 的值是 hello,而 $b的值则是hello,hello。这种技术在Perl世界里被称为插值

server {
    listen 8080;
    location /test {
    	set $foo hello;
        echo "foo: $foo";
    }
}
$ curl 'http://localhost:8080/test'
foo: hello

我们看到echo支持变量插值,而不是所有的模块都支持变量插值

转义$

geo $dollar {
	default "$";
}
server {
    listen 8080;
    location /test {
    	echo "this is dollar sign: $dolloar"
    }
}

这里用到了标准模块ngx_geo提供的配置指令geo来为变量$dollar赋予字符串"$",这样我们在下面需要使用美元符的地方,就直接引用我们的$dollar变量就可以了。其实ngx_geo模块最常规的用法是根据客户端的IP地址对指定的Nginx变量进行赋值,这里只是借用它以便“无条件地”对我们的**$dollar**变量赋予“美元符”这个值。

当引用的变量名之后紧跟着变量名的构成字符时

server {
	listen 8080;
    location /test {
    	set $first "hello ";
    	echo "${first}world"
    }
}

如果直接写成**$firstworld**,那么nginx将不会识别出变量$first。注意set和geo指令都会创建变量,我们不能在没有创建变量,但是先读这个变量。Nginx变量的创建只能发生在Nginx配置加载的时候,或者说Nginx启动的时候;而赋值操作则只会发生在请求实际处理的时候。这意味着不创建而直接使用变量会导致启动失败,同时也意味着我们无法在请求处理时动态地创建新的Nginx变量。

跨配置的变量

server {
	listen 8080;
    location /foo {
    	echo "foo = [$foo]"
    }
    location /bar {
    	set $foo 32;
        echo "foo = [$foo]";
    }
}

在location /bar中,我们创建了变量$foo,所以在整个配置中,都是可见的,我们可以在location /foo中使用,而nginx不会报错。

$ curl http://localhost:8080/foo
foo = []
$ curl http://localhost:8080/bar
foo = [32]
$curl 'http://localhost:8080/foo'
foo = []

从这个例子我们可以窥见的另一个重要特性是,Nginx变量名的可见范围虽然是整个配置,但每个请求都有所有变量的独立副本,或者说都有各变量用来存放值的容器的独立副本,彼此互不干扰。比如前面我们请求了/bar接口后,$foo变量被赋予了值32,但它丝毫不会影响后续对/foo接口的请求所对应的 $foo值(它仍然是空的!),因为各个请求都有自己独立的$foo变量的副本。新手来说,最常见的错误之一,就是将 Nginx变量理解成某种在请求之间全局共享的东西,或者说“全局变量”。而事实上,Nginx变量的生命期是不可能跨越请求边界的

Nginx变量值容器的生命期是与当前正在处理的请求绑定的,而与location无关

server {
	listen 8080;
    location /foo {
    	set $a hello;
        echo_exec /bar;
    }
    location /bar {
    	echo "a = [$a]";
    }
}

这里使用了ngx_echo提供的echo_exec来执行内部跳转。。所谓“内部跳转”,就是在处理请求的过程中,于服务器内部,从一个location跳转到另一个 location的过程。这不同于利用HTTP状态码 301 和302所进行的“外部跳转”,因为后者是由HTTP客户端配合进行跳转的,而且在客户端,用户可以通过浏览器地址栏这样的界面,看到请求的URL地址发生了变化。

内建变量

内建变量一般用于获取请求和响应的信息,比如ngx_http_core模块提供的$uri变量

local /test {
	echo "uri = $uri";
    echo "request_uri = $request_uri"
}

$request_uri是获取未转码的uri信息,包含请求参数。$uri经过了解码,并不包含请求参数。

测试

$ curl http://localhost:8080/test
uri = /test
request_uri = /test
$ curl	'http://localhost:8080/test?a=3&b=4'
uri	= /test
request_uri =/test?a=3&b=4

$ curl 'http://localhost:8080/test/hello%20world?a=3&b=4'
uri = /test/hello world
request_uri = /test/hello%20world?a=3&b=4

$arg_xxx变量

local /test {
	echo "name = $arg_name";
    echo "class = $arg_class"
}

测试

curl 'http://localhost:8080/test'
name=	
class=

$ curl	'http://localhost:8080/test?name=Tom&class=3'
name:Tom
class:3

许多内建变量都是只读的,不能够对其改写,上面的$arg_xxx变量就不能够改写。

能改写的$args变量

location /test {
    set $orig_args $args
    set $args "a=5"
    echo "original a: $orig_args"
    echo "a:$arg_a"
}
$curl 'http://localhost:8080/test?a=3'
original a:3
a:5

变量的值容器

Nginx模块在创建变量时,可以选择是否为变量分配存放值的容器,以及是否自己提供与读写操作相对应的“存取处理程序”。不是所有的Nginx变量都拥有存放值的容器。拥有值容器的变量在Nginx核心中被称为“被索引的”(indexed);反之,则被称为“未索引的”(non-indexed)

像$arg_XXX这样具有无数变种的变量群,是“未索引的”。当读取这样的变量时,其实是它的“取处理程序”在起作用,即实时扫描当前请求的 URL 参数串,提取出变量名所指定的 URL 参数的值。很多新手都会对$arg_XXX的实现方式产生误解,以为Nginx会事先解析好当前请求的所有URL参数,并且把相关的$arg_XXX变量的值都事先设置好。然而事实并非如此,Nginx 根本不会事先就解析好 URL 参数串,而是在用户读取某个 $arg_XXX 变量时,调用其“取处理程序”,即时去扫描URL参数串。类似地,内建变量 $cookie_XXX 也是通过它的“取处理程序”,即时去扫描Cookie请求头中的相关定义的。

map

map $arg $foo {
    default 0;
    debug 1;
}

server {
	listen 8080;
    location /test {
    	set $orig_foo $foo
        set $args debug;
        echo "original foo : $orig_foo"
        echo "foo:$foo"
    }
}

第一行default是一个特殊的配置条件,当其他条件都不匹配的时候,这个条件才匹配。上面的map指定表示,当$arg精确匹配debug,$foo的值位1,否则位0。我们看其测试结果:

$ curl 'http://localhost:8080/test'
orginal foo:0
foo:0

为什么在改变args为debug,结果foo应该为1。为什么不是0呢?这是因为$foo变量第一次缓存之后,根据映射规则算出的值被缓存住了。因为ngx_map模块认为变量间的映射计算足够昂贵。

$curl 'http://localhost:8080/test?debug'
original foo:1
foo:1

请求与子请求

主请求:主请求就是HTTP客户端从Nginx外部发起的请求。而子请求则是由Nginx正在处理的请求在Nginx内部发起的一种级联请求。“子请求”在外观上很像HTTP请求,但实现上却和HTTP协议乃至网络通信一点儿关系都没有。它是Nginx内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地访问多个location接口,然后由这些location接口通力协作,共同完成整个“主请求”。当然,“子请求”的概念是相对的,任何一个“子请求”也可以再发起更多的“子子请求”,甚至可以玩递归调用(即自己调用自己)。

location main {
	echo_location /foo;
    echo_location /bar;
}
location /foo {
	echo foo;
}
location /bar {
	echo bar;
}

通过第三方ngx_echo模块的echo_location指令分别发起到/foo和/bar这两个接口的GET类型的“子请求”。由echo_location发起的“子请求”,其执行是按照配置书写的顺序串行处理的,即只有当/foo请求处理完毕之后,才会接着处理/bar请求。这两个“子请求”的输出会按执行顺序拼接起来,作为/main接口的最终输出。子请求方式在同一个虚拟主机内部进行,实现子请求时,只是调用了若干个函数,执行效率极高。

前面说,生命期是与当前请求相关联,现在也可以这样说,在父子请求中,同名变量一般不会相互干扰。

location /main {
    set $var main;
	echo_location /foo;
    echo_location /bar;
    echo "main: $var";
}
location /foo {
	set $var foo;
	echo "foo:$var";
}
location /bar {
	set $var bar;
	echo "bar:$var;
}

测试结果

$ curl 'http://localhost:8080/main'
foo: foo
bar: bar
main: main

然而一些模块是子请求共享父请求的变量值容器。

location /main {
    set $var main;
    auth_request /sub;
    echo "main: $var";
}
location /sub {
	set $var sub;
    echo "sub: $var";
};

测试结果:

nginx_lua

ngx_lua模块将Lua语言解释器(或者LuaJIT 即时编译器)嵌入到了Nginx核心中,从而可以让用户在Nginx核心中直接运行Lua语言编写的程序。我们可以选择在Nginx不同的请求处理阶段插入我们的Lua代码。这些Lua 代码既可以直接内联在Nginx配置文件中,也可以单独放置在外部 .lua源码文件(或者 Lua 字节码文件)里,然后在 Nginx配置文件中引用这些文件的路径。

简单的区分变量找不到和为空

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
   ';
}

测试

$ curl --cookie user=agentzh 'http://localhost:8090/test'
cookie user: [agentzh]
$ curl --cookie user= 'http://localhost:8090/test'
cookie user: []
$ curl 'http://localhost:8090/test'
cookie user: missing

lua中访问没有创建的变量

location /test {
	content_by_lua '
        ngx.say("$blah = ", ngx.var.blah)
    ';
}

nginx在加载配置时,只会编译content_by_lua配置指令,而不会执行。ngx say会自动将lua中的nil值格式化为nil字符串,上面的ngx.var.blah并没有触发变量插值。否则nginx会在启动的时候抱怨$blah未创建。

带上调试信息,查看日志。

编译的是时候,带上./configure --with-debug,同时需要配置

err_log logs/error.log debug

执行顺序

例子

location /test {
 set $a 32;
 echo $a;
 set $a 56;
 echo $a
}

正常思维是输出32, 56,实际输出

$ curl 'http://localhost:8000/test'
56
56

这就涉及到了执行顺序:rewrite阶段,access阶段,content阶段。rewrite阶段总是在content之前运行。echo指令在content阶段运行,set指令在rewrite阶段运行。上面的例子,实际上:

set $a 32;
echo $a;
set $a 56;
echo $a;

实际上的执行顺序:

set $a 32;
set $a 56;
echo $a;
echo $a;

要输出32和56,可以这样:

location /test {
	set $a 32;
    set $saved_a $a;
    set $a 56;
    echo $saved_a;
    echo $a;
}

如何查看配置运行的阶段,查看指令手册。

content阶段

location /test {
	# rewrite phase
    set $age 1;
    rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
    # access phase
    deny 10.32.168.49;
    acess_by_lua "ngx.var.age = ngx.var.age * 3";
    
    # content phase
    echo "age = $age";
}
$ curl 'http://locahost:8080/test'
age = 6

nginx_index, ngx_autoindex, ngx_static模块

完整的执行顺序

分为11个顺序,分别是,post-read, server-rewrite, find-config, rewrite, post-rewrite, preaccess, acess, post-access, try-files, content 以及log阶段。

post-read

post-read 在Nginx读取并解析完请求头(request headers)之后就立即开始运行,和rewrite阶段,支持nginx模块处理程序。

server {
	listen 8080;
    set_real_ip_from 127.0.0.1
    real_ip_header X-My-IP;
    location /test {
    	set $addr $remote_addr;
        echo "from: $addr";
    }
}

ngx_realip模块提供set_real_ip_from 和 real_ip_header,上面的代码是将所有来自127.0.0.1的请求地址,都改写成X-My-IP的值。

server-rewrite

server {
	listen 8080;
    location /test {
    	set $b "$a world";
        echo $b
    }
    set $a hello;
}

ngx_rewrite模块的配置指令直接书写在server配置块中时,运行在server-rewrite阶段。

$ curl localhost:8080/test
hello, world

find-config阶段

并不支持模块注册处理程序。

post-access阶段

并不支持模块注册处理程序。而是由nginx核心自己完成一些处理工作,主要用于配合access阶段实现标准nginx-http-core模块提供的配置指令satisfy功能。

try-files功能

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment