0. 前言
uri是url中除去协议和域名及参数后, 剩下的部分.
比如请求的url为: http://www.liuvv.com/test/index.php?page=1, 则uri 为 /test/index.php
1. location指令
1.1 location匹配uri的规则
1 | location [ = | ~ | ~* | ^~ ] uri { ... } |
=
精确匹配, uri要完全一样^~
这个是以某个开头, 不是正则匹配~
区分大小写正则匹配~*
不区分大小写正则匹配
1.2 location匹配uri的优先级
首先先检查使用前缀字符定义的location,选择最长匹配的项并记录下来。
如果找到了精确匹配的location,也就是使用了
=
修饰符的location,结束查找,使用它的配置。如果
^~
修饰符先匹配到最长的前缀字符串, 则不检查正则。然后按顺序查找使用正则定义的location,如果匹配则停止查找,使用它定义的配置。
如果没有匹配的正则location,则使用前面记录的最长匹配前缀字符location。
基于以上的匹配过程,我们可以得到以下启示:
- 使用正则定义的location在配置文件中出现的顺序很重要。因为找到第一个匹配的正则后,查找就停止了,后面定义的正则就是再匹配也没有机会了。
- 使用精确匹配可以提高查找的速度。例如经常请求
/
的话,可以使用=
来定义location。 - 优先级
=
>^~
> 正则
1.3 location 测试
1 | location = / { |
测试结果:
1 | levonfly@hk:~$ curl test.liuvv.com # 因为是= |
1.4 location @name的用法
@用来定义一个命名location。主要用于内部重定向,不能用来处理正常的请求。其用法如下:
1 | location / { |
上例中,当尝试访问url找不到对应的文件就重定向到我们自定义的命名location(此处为custom)。
值得注意的是,命名location中不能再嵌套其它的命名location。
1.5 root和alias的区别
nginx指定文件路径有两种方式root和alias.
- root
1 | [root] |
例如:
1 | location ^~ /t/ { |
如果一个请求的URI是/t/a.html时,web服务器将会返回服务器上的/www/root/html/t/a.html的文件。
- alias
1 | [alias] |
例如:
1 | location ^~ /t/ { # 特殊的规则是, alias必须以"/" 结束 |
如果一个请求的URI是/t/a.html时,web服务器将会返回服务器上的/www/root/html/new_t/a.html的文件。注意这里是new_t,因为alias会把location后面配置的路径丢弃掉,把当前匹配到的目录指向到指定的目录。
1.6 nginx显示目录结构
nginx默认是不允许列出整个目录的。如需此功能, 在server或location 段里添加上autoindex on;
1 | autoindex_exact_size off; |
可以下面的例子:
1 | location ^~ "/upload-preview" { |
1.7 URL尾部的/
需不需要
如果URL结构是
https://domain.com/
的形式,尾部有没有/
都不会造成重定向。因为浏览器在发起请求的时候,默认加上了/
。虽然很多浏览器在地址栏里也不会显示/
。这一点,可以访问baidu验证一下。如果URL的结构是
https://domain.com/some-dir/
。尾部如果缺少
/
将导致重定向。因为根据约定,URL尾部的/
表示目录,没有/
表示文件。所以访问
/some-dir/
时,服务器会自动去该目录下找对应的默认文件。如果访问
/some-dir
的话,服务器会先去找some-dir
文件,找不到的话会将some-dir
当成目录,重定向到/some-dir/
,去该目录下找默认文件。
2. rewirte规则
2.1 return指令
return指令写在server和location里面
1 | return code [text]; |
我们来看下面这个例子
1 | return 301 $scheme://www.baidu.com$request_uri; |
return 指令告诉 nginx 停止处理请求, 直接返回301代码和指定重写过的URL到客户端. $scheme是指协议(http),$request_uri指包含参数的完整URI
对于 3xx 系列响应码, url参数就是重写的url
1 | return (301 | 302 | 303 | 307) url; |
对于其他响应吗, 可以出现一个字符串
1 | return (1xx | 2xx | 4xx | 5xx)["text"] |
例如:
1 | return 401 "Access denied because token is expired or invalid"; |
2.2 rewrite指令
rewrite指令写在server和location里面, 规则会改变部分或整个用户的URL.
1 | rewrite regex URL [flag] |
regex 正则表达式
flag
last
停止当前这个请求,并根据rewrite匹配的规则重新发起一个请求。新请求又从第一阶段开始执行, 找寻新的location
break
break并不会重新发起一个请求,只是跳过当前的rewrite阶段,并执行本请求后续的执行阶段, 在同一个location里处理
redirect
返回包含302的临时重定向
permanent
返回包含301的永久重定向
rewrite只能返回301或302, 如果有其他,需要后面加上return, 例如:
1 | rewrite ^(/download/.*)/media/(\w+)\.?.*$ $1/mp3/$2.mp3 last; |
匹配/download开头的URL, 然后替换相关路径
/download/cdn-west/media/file1
->/download/cdn-west/mp3/file1.mp3
如果匹配不上, 将返回客户端403
2.3 try_files指令
try_files指令写在server和location里面.
1 | try_files file ... uri 或 try_files file ... = code |
try_files 指令的参数是一个或多个文件或目录的列表, 以及后面的uri参数. nginx会按照顺序检查文件或目录是否存在, 并用找到的第一个文件提供服务. 如果都不存在, 内部重定向到最后的这个uri
例如:
1 | location /images/ { |
try_files常用的变量:
$uri 表示域名以后的部分
$args 请求url中 ? 后面的参数 (不包括?本身)
$is_args 判断$args是否为空
1
try_files $uri $uri/ /index.php$is_args$args; #这样就能避免多余的?号
$query_string 和 $args相同
$document_root root指令指定的值
$request_filename 请求的文件路径
$request_uri 原始请求uri
我们看个例子:
1 | try_files /app/cache/ $uri @fallback; |
它将检测$document_root/app/cache/index.php,$document_root/app/cache/index.html 和 $document_root$uri是否存在,如果不存在着内部重定向到@fallback(@表示配置文件中预定义标记点) 。
你也可以使用一个文件或者状态码(=404)作为最后一个参数,如果是最后一个参数是文件,那么这个文件必须存在。
我们来看一个错误:
1 | location ~.*\.(gif|jpg|jpeg|png)$ { |
原意图是访问http://example.com/test.jpg
时先去检查/web/wwwroot/static/test.jpg
是否存在,不存在就取/web/wwwroot/test.jpg
但由于最后一个参数是一个内部重定向,所以并不会检查/web/wwwroot/test.jpg
是否存在,只要第一个路径不存在就会重新向然后再进入这个location造成死循环。结果出现500 Internal Server Error
1 | location ~.*\.(gif|jpg|jpeg|png)$ { |
这样才会先检查/web/wwwroot/static/test.jpg
是否存在,不存在就取/web/wwwroot/test.jpg
再不存在则返回404 not found
2.4 if指令
if不是系统级的指令, 是和rewrite配合的. if 必须写在server和location里面.
- 变量名: 如果是空字符串或”0”为FALSE
- = 判断相等, != 判断不相等
- ~ 和 ~*(不分区大小写) 将变量与正则匹配, 捕获可以用 $1 到 $9
- !~ 和 !~* 用作不匹配运算符
- 正则含有 } 或 ; 字符需要用引号括起来
- 常用判断指令
- -f 和 !-f 判断是否存在文件(file)
- -d 和 !-d 判断是否存在目录(directory)
- -e 和 !-e 判断是否存在文件或目录(exists)
- -x 和 !-x 判断文件是否可执行(execute)
例如下面的列子:
1 | if ($http_user_agent ~ Chrome) { |
3. proxy_pass模块
proxy_pass指令是将请求反向代理到URL参数指定的服务器上,URL可以是主机名或者IP地址+端口号的形式,例如:
1 | proxy_pass http://proxy_server; |
3.1 基本配置
- proxy_set_header:设置服务器获取用户的主机名或者真实ip地址,以及代理者的真实ip地址。
- client_body_buffer_size:用于指定客户端请求主体缓冲区大小,可以理解为先保存到本地再传给用户
- proxy_connect_timeout:表示连接服务器的超时时间,即发起tcp握手等候响应的超时时间
- proxy_send_time:服务器的数据传回时间,在规定时间内服务器必须传回完所有数据,否则,nginx将断开这个连接
- proxy_read_time:设置nginx从代理的后端服务器获取数据的时间,表示连接建立成功后,+ nginx等待服务器的响应时间,其实是nginx已经进入服务器的排队中等候处理的时间。
- proxy_buffer_size:设置缓冲区大小,默认该缓冲区大小等于proxy_buffers设置的大小
- proxy_buffers:设置缓冲区的数量和大小,nginx从代理的服务器获取响应数据会放置到缓冲区
- proxy_busy_buffers_size:用于设置系统很忙时可以使用的proxy_buffers大小,官方推荐大小为proxy_buffers*2
- proxy_temp_file_write_size:指定proxy缓存临时文件的大小
3.2 proxy_set_header
1 | 语法: proxy_set_header field value; |
允许重新定义或者添加发往后端服务器的请求头。value可以包含文本、变量或者它们的组合。 当且仅当当前配置级别中没有定义proxy_set_header指令时,会从上面的级别继承配置。默认情况下,只有两个请求头会被重新定义:
1 | proxy_set_header Host $proxy_host; |
proxy_set_header也可以自定义参数,如:proxy_set_header test paroxy_test;
3.3 获取真实ip
经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip, 通过$remote_addr变量拿到的将是反向代理服务器的ip地址. 如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:
1 | proxy_set_header X-real-ip $remote_addr; |
其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:request.getAttribute(“X-real-ip”)
3.4 常见配置解释
1 | server { |
proxy_set_header X-real-ip $remote_addr;
可以在web服务器端获得用户的真实ip, 但是,实际上要获得用户的真实ip,不是只有这一个方法。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
X-Forwarded-For 请求头格式非常简单,就这样:
1
X-Forwarded-For: client, proxy1, proxy2
可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。
如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:
1
X-Forwarded-For: IP0, IP1, IP2
proxy_set_header Host $http_host;
$host:请求中的主机头(HOST)字段,如果请求中的主机头不可用或者空,则为处理请求的server名称(处理请求的server的server_name指令的值)。值为小写,不包含端口!!!!
如果客户端发过来的请求的header中有’HOST’这个字段时,
$http_host
和$host
都是原始的’HOST’字段比如请求的时候HOST的值是www.csdn.net 那么反代后还是www.csdn.net
如果客户端发过来的请求的header中没有有’HOST’这个字段时, 建议使用$host,这表示请求中的server name。
4. websocket反向代理
nginx 首先确认版本必须是1.3以上。
map指令的作用: 该作用主要是根据客户端请求中$http_upgrade的值,来构造改变$connection_upgrade的值,即根据变量$http_upgrade的值创建新的变量$connection_upgrade, 创建的规则就是{}里面的东西。其中的规则没有做匹配,因此使用默认的,即 $connection_upgrade的值会一直是 upgrade。然后如果 $http_upgrade为空字符串的话, 那值会是 close。
1 | #必须添加的 |