基础概念
上一章单纯是为了跑起来,其中的配置是生产环境的配置,如果简单也是可以的,现在结构概念部分;
指令和上下文:
nginx的配置文件参照上文,/opt/nginx/conf/nginx.conf
①指令 – 可选项,包含名称和参数,以分号结尾
gzip on; //对响应数据进行Gzip压缩
②上下文 – 分块,你可以声明指令 – 类似于编程语言中的作用域
worker_processes 2; # 全局上下文指令
http { # http 上下文
gzip on; # http 上下文中的指令
server { # server 上下文
listen 80; # server 上下文中的指令
}
}
指令类型
普通指令:在每个上下文仅有唯一值。而且,它只能在当前上下文中定义一次。子级上下文可以覆盖父级中的值,并且这个
覆盖值只在当前的子级上下文中有效。
gzip on;
gzip off; # 非法,不能在同一个上下文中指定同一普通指令2次
server {
location /downloads {
gzip off;
}
location /assets {
# gzip is on here
}
}
数组指令
在同一上下文中添加多条指令,将添加多个值,而不是完全覆盖。在子级上下文中定义指令将覆盖给父级上下文中的值。
error_log /var/log/nginx/error.log;
error_log /var/log/nginx/error_notive.log notice;
error_log /var/log/nginx/error_debug.log debug;
server {
location /downloads {
# 下面的配置会覆盖父级上下文中的指令
error_log /var/log/nginx/error_downloads.log;
}
}
行动指令
行动是改变事情的指令。根据模块的需要,它继承的行为可能会有所不同。
例如rewrite指令,只要是匹配的都会执行
server {
rewrite ^ /foobar;
location /foobar {
rewrite ^ /foo;
rewrite ^ /bar;
}
}
如果用户想尝试获取 /sample
server的rewrite将会执行,从 /sample rewrite 到 /foobar
location /foobar 会被匹配
location的第一个rewrite执行,从/foobar rewrite到/foo
location的第二个rewrite执行,从/foo rewrite到/bar
return指令提供的是不同的行为:
server {
location / {
return 200;
return 404;
}
}
上面操作返回200
处理请求
在 Nginx 内部,你可以指定多个虚拟服务器,每个虚拟服务器用 server{} 上下文描述,但http只能有一个。
server {
listen *:80 default_server;
server_name netguru.co;
return 200 "Hello from netguru.co";
}
server {
listen *:80;
server_name foo.co;
return 200 "Hello from foo.co";
}
server {
listen *:81;
server_name bar.co;
return 200 "Hello from bar.co";
}
这将告诉 Nginx 如何处理到来的请求。Nginx 将会首先通过检查 listen 指令来测试哪一个虚拟主机在监听给定的 IP 端口组合。然后,server_name指令的值将检测 Host 头(存储着主机域名)。
Nginx 将会按照下列顺序选择虚拟主机:
-
匹配sever_name指令的IP-端口主机
-
拥有default_server标记的IP-端口主机
-
首先定义的IP-端口主机
-
如果没有匹配,拒绝连接。
例如:
Request to foo.co:80 => "Hello from foo.co"
Request to www.foo.co:80 => "Hello from netguru.co"
Request to bar.co:80 => "Hello from netguru.co"
Request to bar.co:81 => "Hello from bar.co"
Request to foo.co:81 => "Hello from bar.co"
server_name 指令
server_name指令接受多个值。它还处理通配符匹配和正则表达式。
server_name netguru.co www.netguru.co; # exact match
server_name *.netguru.co; # wildcard matching
server_name netguru.*; # wildcard matching
server_name ~^[0-9]*\.netguru\.co$; # regexp matching
当有歧义时,nginx 将使用下面的命令:
-
确切的名字
-
最长的通配符名称以星号开始,例如“* .example.org”。
-
最长的通配符名称以星号结尾,例如“mail.**”
-
首先匹配正则表达式(按照配置文件中的顺序)
Nginx 会存储 3 个哈希表:①确切的名字,②以星号开始的通配符,和③以星号结尾的通配符。如果结果不在任何表中,则将按顺序进行正则表达式测试。
值得谨记的是
server_name .netguru.co;
是一个来自下面的缩写
server_name netguru.co www.netguru.co *.netguru.co;
有一点不同,.netguru.co 存储在第二张表,这意味着它比显式声明的慢一点
listen 指令
在很多情况下,能够找到 listen 指令,接受IP:端口值
listen 127.0.0.1:80;
listen 127.0.0.1; # by default port :80 is used
listen *:81;
listen 81; # by default all ips are used
listen [::]:80; # IPv6 addresses
listen [::1]; # IPv6 addresses
然而,还可以指定 UNIX-domain 套接字。
listen unix:/var/run/nginx.sock;
最小化配置:
# /opt/nginx/nginx.conf
events {} # events context needs to be defined to consider config valid
http {
server {
listen 80;
server_name netguru.co www.netguru.co *.netguru.co;
return 200 "Hello";
}
}
页面访问后显示: Hello
root, location, 和 try_files 指令
root 指令
root 指令设置请求的根目录,允许 nginx 将传入请求映射到文件系统。
server {
listen 80;
server_name netguru.co;
root /var/www/netguru.co;
}
根据给定的请求,指定 nginx 服务器允许的内容
netguru.co:80/index.html # returns /var/www/netguru.co/index.html
netguru.co:80/foo/index.html # returns /var/www/netguru.co/foo/index.html
location 指令
location指令根据请求的 URI 来设置配置。
location [modifier] path
location /foo/ {
# ...
}
如果没有指定修饰符,则路径被视为前缀,其后可以跟随任何东西。
以上例子将匹配
/foo
/fooo
/foo123
/foo/bar/index.html
...
此外,在给定的上下文中可以使用多个 location 指令
server {
listen 80;
server_name netguru.co;
root /var/www/netguru.co;
location / {
return 200 "root";
}
location /foo/ {
return 200 "foo";
}
}
netguru.co:80 / # => "root"
netguru.co:80 /foo # => "foo"
netguru.co:80 /foo123 # => "foo"
netguru.co:80 /bar # => "root"
Nginx 也提供了一些修饰符,可用于连接 location。这些修饰符将影响 location 模块使用的地方,因为每个修饰符都分配了优先级(上至下)。
= - Exact match 准确匹配 ①
^~ - Preferential match 优先匹配 ②
~ && ~* - Regex match 正则匹配 ③
no modifier - Prefix match 前缀匹配 ④
(Nginx 会先检查精确匹配。如果找不到,我们会找优先级最高的。如果这个匹配依然失败,正则表达式匹配将按照出现的顺序进行测试。至少,最后一个前缀匹配将被使用.)
location /match {
return 200 'Prefix match: matches everything that starting with /match';
}
location ~* /match[0-9] {
return 200 'Case insensitive regex match';
}
location ~ /MATCH[0-9] {
return 200 'Case sensitive regex match';
}
location ^~ /match0 {
return 200 'Preferential match';
}
location = /match {
return 200 'Exact match';
}
/match/ # => 'Exact match'
/match0 # => 'Preferential match'
/match1 # => 'Case insensitive regex match'
/MATCH1 # => 'Case sensitive regex match'
/match-abc # => 'Prefix match: matches everything that starting with /match'
try_files 指令
尝试不同的路径,找到一个路径就返回。
try_files $uri index.html =404;
所以对于 /foo.html 请求,它将尝试按以下顺序返回文件:
$uri ( /foo.html )
index.html
如果什么都没找到则返回 404
有趣的是,如果我们在服务器上下文中定义 try_files,然后定义匹配的所有请求的 location —— try_files 将不会执行
这是因为在服务器上下文中定义的 try_files 是它的 pseudo-location(假地址),这是最不可能的位置。因此,定义 location/ 将比 pseudo-location 更具体。
server {
try_files $uri /index.html =404;
location / {
}
}
因此,应该避免在server 上下文中出现 try_files:
server {
location / {
try_files $uri /index.html =404;
}
}
tcp_nodelay, tcp_nopush 和 sendfile
tcp_nodelay (为了尽可能快地推送数据包)
起初用于针对Tcp流量冲突和堵塞的问题,Nagle 的算法为了防止通讯被大量的小包淹没,然后采取了限制,只针对比MSS(最大报文长度)小的包,只有当接收方成功将ACK返回,才能进行下次发送,等待期间,发送方可以在此期间缓冲更多的数据之后再进行发送。
在 TCP 通讯中,在发送数据后,需要接收回应包(ACK)来确认数据被成功传达(延时 ACK),延时 ACK 旨在解决线路被大量的 ACK 包拥堵的状况。为了减少 ACK 包的
数量,接收者等待需要回传的数据加上 ACK 包回传给发送方,如果没有数据需要回传,必须在至少每 2 个 MSS,或每 200 至 500 毫秒内发送 ACK(以防我们不再收到包)。
(在这个数据交换过程中,由于 Nagel 和延迟 ACK 之间的死锁,因此引入了 200ms 的延迟)
在大多数情况下,我们不会在我们的网站上使用它,因此可以通过添加 TCP_NODELAY 标志来安全地关闭它。(TCP中就会获得200ms的提速)
tcp_nodely on
tcp_nopush(一次性优化数据的发送量)
在发送给客户端之前,它将强制等待包达到最大长度(MSS)。而且这个指令只有在 sendfile 开启时才起作用。
sendfile on;
tcp_nopush on;
看起来 tcp_nopush 和 tcp_nodelay 是互斥的。但是,如果所有 3 个指令都开启了,nginx 会:
1、确保数据包在发送给客户之前是已满的
2、对于最后一个数据包,tcp_nopush 将被删除 —— 允许 TCP 立即发送,没有 200ms 的延迟
sendfile
正常来说,当要发送一个文件时需要下面的步骤:
malloc(3) – 分配一个本地缓冲区,储存对象数据。
read(2) – 检索和复制对象到本地缓冲区。
write(2) – 从本地缓冲区复制对象到 socket 缓冲区。
这涉及到两个上下文切换(读,写),并使相同对象的第二个副本成为不必要的。正如所看到的,这不是最佳的方式。值得庆幸的是还有另一个系统调用,提升了发送文件(的效率),它被称为:sendfile(2)(想不到吧!居然是这名字)。这个调用在文件 cache 中检索一个对象,并传递指针(不需要复制整个对象),直接传递到 socket 描述符
sendfile(2) 有一些注意事项:
1、不可用于 UNIX sockets(例如:当通过你的上游服务器发送静态文件时)
2、能否执行不同的操作,取决于操作系统
#打开方式:
sendfile on;
sendfile_max_chunk 512k;
# 较大的文件不要一次全读取了,浪费内存,意思是超过512k的文件不用缓存
反向代理配置:
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location /{
root html;
index index.html index.htm;
proxy_pass http://baidu.com; //指定请求转发到
proxy_connect_timeout 600;//设置连接超时
proxy_read_timeout 600;//设置读响应超时
}
proxy_connect_timeout 600;//设置连接超时
proxy_read_timeout 600;//设置读响应超时
}
1、proxy_set_header Host $host;
$host 时它的值在请求包含“Host”请求头时为“Host”字段的值,在请求未携带“Host”请求头时为虚拟主机的主域名:
2、proxy_set_header X-real-ip $remote_addr;
当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取$remote_addr),取得的是nginx的地址,即$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的,但是,nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:
proxy_set_header X-real-ip $remote_addr;
其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:
request.getAttribute("X-real-ip")
3、proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
我们先看看这里有个X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1, proxy1, proxy2,以逗号隔开各个地址,由于他是非rfc标准,所以默认是没有的,需要强制添加,在默认情况下经过proxy转发的请求,在后端看来远程地址都是proxy端的ip 。也就是说在默认情况下我们使用request.getAttribute("X-Forwarded-For")获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx添加如下配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
意思是增加一个$proxy_add_x_forwarded_for到X-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getAttribute("X-Forwarded-For")获得的将会是客户端ip和第一台nginx的ip。
那么$proxy_add_x_forwarded_for又是什么?
$proxy_add_x_forwarded_for变量包含客户端请求头中的"X-Forwarded-For",与$remote_addr两部分,他们之间用逗号分开。
举个例子,有一个web应用,在它之前通过了两个nginx转发,www.linuxidc.com 即用户访问该web通过两台nginx。
在第一台nginx中,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量的"X-Forwarded-For"部分是空的,所以只有$remote_addr,而$remote_addr的值是用户的ip,于是赋值以赋值以后,
X-Forwarded-For变量的值就是用户的真实的ip地址了。
到了第二台nginx,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值
以后,现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”,这样就清楚了吧。
$http_x_forwarded_for变量
这个变量就是X-Forwarded-For,由于之前我们说了,默认的这个X-Forwarded-For是为空的,所以当我们直接使用proxy_set_header X-Forwarded-For $http_x_forwarded_for
时会发现,web服务器端使用request.getAttribute("X-Forwarded-For")获得的值是null。如果想要通过request.getAttribute("X-Forwarded-For")获得用户ip,就必须先使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;这样就可以获得用户真实ip。