Nginx学习:代理模块(五)变量与其它配置
Nginx 在不同的模块中,会提供一些变量,这个我们之前应该已经看过很多了。特别是 HTTP 核心模块中,提供了非常多的变量可以供我们使用。而在 Proxy 模块中,也提供了几个非常简单的变量,今天就来一起学学。另外,最后还剩一些无法归到大类的其它配置,大家也可以了解一下,其中还是有重点配置的哦,而且是非常常用的,今天的内容绝对不水。
今天的配置指令基本上都可以在 http、server、location 中配置,有特殊情况的我会单独说。
Proxy 变量
在代理模块中,提供了三个变量。
$proxy_host
proxy_pass 指令中指定的代理服务器的名称和端口$proxy_port
proxy_pass 指令中指定的代理服务器的端口,或协议的默认端口- proxy_add_x_forwarded_for 附加了 remote_addr 变量的“X-Forwarded-For”客户端请求标头字段,以逗号分隔。如果客户端请求标头中不存在“X-Forwarded-For”字段,则 proxy_add_x_forwarded_for 变量等于 remote_addr 变量
这三个变量我们也可以通过记录到日志中来进行查看。
代码语言:javascript复制log_format proxy 'proxy_host=$proxy_host proxy_port=$proxy_port proxy_add_x_forwarded_for=$proxy_add_x_forwarded_for';
server{
listen 8027;
access_log logs/27.log proxy;
……………………
}
然后分别访问不同的代理服务器,就可以看到不同的效果。这三个代理就是第一篇文章中我们配过的那三个。
代码语言:javascript复制// proxy_pass http://192.168.56.89
proxy_host=192.168.56.89 proxy_port=80 proxy_add_x_forwarded_for=192.168.56.1
// proxy_pass https://www.baidu.com
proxy_host=www.baidu.com proxy_port=443 proxy_add_x_forwarded_for=192.168.56.1
// proxy_pass http://proxy1
proxy_host=proxy1 proxy_port=80 proxy_add_x_forwarded_for=192.168.56.1
proxy_host=proxy1 proxy_port=80 proxy_add_x_forwarded_for=192.168.56.1
其它
最后就是一些不便于大分类的配置了。其中也有几个比较重要和有用的,我没有最后综合进行测试,就是单独在讲某一个的时候如果进行了测试,就直接说了。大家也可以挑选着进行测试学习。
proxy_bind
与代理服务器的传出连接源自具有可选端口 (1.11.2) 的指定本地 IP 地址。
代码语言:javascript复制proxy_bind address [transparent] | off;
参数值可以包含变量(1.3.12)。特殊值 off (1.3.12) 取消了从先前配置级别继承的 proxy_bind 指令的效果,它允许系统自动分配本地 IP 地址和端口。
和 fastcgi_bind 也是类似的,咱们这么测试,先给虚拟机新添加一个网卡,然后配置 proxy_bind 到这个网卡对应的 IP 地址。
代码语言:javascript复制proxy_pass http://192.168.56.89;
proxy_bind 192.168.56.102;
然后本地测试代理到 89,访问 http://192.168.56.88:8027/1.php ,REOMTE_ADDR 的值会产生变化。
代码语言:javascript复制[root@localhost html]# netstat -nta|grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.56.89:80 192.168.56.102:45268 TIME_WAIT
而不使用 proxy_bind 的话,显示 88 的连接。
代码语言:javascript复制[root@localhost html]# netstat -nta|grep 80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.56.89:80 192.168.56.102:45268 TIME_WAIT
tcp 0 0 192.168.56.89:80 192.168.56.88:37220 TIME_WAIT
transparent 参数 (1.11.0) 允许到代理服务器的传出连接源自非本地 IP 地址,例如,来自客户端的真实 IP 地址。为了使此参数起作用,通常需要以超级用户权限运行 nginx 工作进程。在 Linux 上,不需要 (1.13.8) 就好像指定了 transparent 参数一样,工作进程从主进程继承 CAP_NET_RAW 能力。还需要配置内核路由表来拦截来自代理服务器的网络流量。
这个参数需要修改 ip 以及 iptables 等,做正向透明代理,没配成功。额,不用演示自己研究就翻车了。有小伙伴有经验的记得评论留言给点资料哈。
proxy_connect_timeout
定义与代理服务器建立连接的超时时间。
代码语言:javascript复制proxy_connect_timeout time;
默认值是 60s ,需要注意的是,这个超时时间通常不能超过 75 秒。
proxy_force_ranges
为来自代理服务器的缓存和未缓存响应启用字节范围支持,无论这些响应中的“Accept-Ranges”字段如何。
代码语言:javascript复制proxy_force_ranges on | off;
默认 off 。
proxy_http_version
设置代理的 HTTP 协议版本。默认情况下,使用 1.0 版。
代码语言:javascript复制proxy_http_version 1.0 | 1.1;
默认值 1.0 ,建议将 1.1 版与 keepalive 连接和 NTLM 身份验证一起使用。
proxy_ignore_client_abort
确定当客户端关闭连接而不等待响应时是否应关闭与代理服务器的连接。
代码语言:javascript复制proxy_ignore_client_abort on | off;
默认值 off 。
proxy_limit_rate
限制从代理服务器读取响应的速度。
代码语言:javascript复制proxy_limit_rate rate;
默认值是 0 ,速率以每秒字节数指定。零值禁用速率限制。限制是按请求设置的,因此如果 Nginx 同时打开到代理服务器的两个连接,则总体速率将是指定限制的两倍。仅当启用了来自代理服务器的响应缓冲时,该限制才有效。这个大家可以自己试试哦。
proxy_method
指定在转发到代理服务器的请求中使用的 HTTP 方法,而不是来自客户端请求的方法。
代码语言:javascript复制proxy_method method;
没有默认值,参数值可以包含变量(1.11.6)。这个好测,直接指定一个。
代码语言:javascript复制proxy_method POST;
然后使用 GET 请求,结果发现 PHP 打印的结果还是 POST 发过来的。这就说明 Nginx 在中间进行处理了,将全部请求都转成 POST 发送给后端了。
代码语言:javascript复制curl 'http://192.168.56.88:8027/other/fastcgi1/5.php'
………………
[REQUEST_METHOD] => POST
………………
proxy_pass_request_body
指示是否将原始请求正文传递给代理服务器。
代码语言:javascript复制 proxy_pass_request_body on | off;
默认值是 on ,关闭的话,如果 PHP 中对请求参数进行打印的话,还会一直卡到超时,最后的报错信息是这个:
代码语言:javascript复制 upstream timed out (110: Connection timed out) while reading response header from upstream
一般来说和 FastCGI 中类似的配置一样,不太会关闭它。
proxy_pass_request_headers
指示是否将原始请求的标头字段传递给代理服务器。
代码语言:javascript复制proxy_pass_request_headers on | off;
默认是 on ,改成 off 不会报错,可以在 $_SERVER
中看到变化。
proxy_read_timeout
定义从代理服务器读取响应的超时。
代码语言:javascript复制proxy_read_timeout time;
默认值 60s ,超时仅在两个连续的读取操作之间设置,而不是为整个响应的传输设置。如果代理服务器在这段时间内没有传输任何内容,则连接将关闭。
这个好测,设置为 1s ,然后访问一个 PHP 页面,只要 sleep() 超过 1s 就好了,Nginx 会直接返回 504 Gateway Time-out
错误,日志会记录
upstream timed out (110: Connection timed out) while reading response header from upstream
注意,如果我们的业务应用服务是耗时比较长的操作,会超过 60s 的话,那么这里要同步修改,拉长时间哦。
proxy_redirect
设置应在代理服务器响应的“Location”和“Refresh”标头字段中更改的文本。
代码语言:javascript复制proxy_redirect default;
proxy_redirect off;
proxy_redirect redirect replacement;
默认值是 default ,替换字符串中可以省略服务器名称,如果不带端口号则自动添加端口号。如果使用变量指定 proxy_pass,则不允许使用默认参数。替换字符串可以包含变量,重定向还可以包含 (1.1.11) 变量。可以使用正则表达式指定 (1.1.11) 该指令。在这种情况下,重定向应该以“~”符号开头以进行区分大小写的匹配,或者以“~*”符号开头以进行不区分大小写的匹配。可以在同一级别上指定多个 proxy_redirect 指令,如果可以将多个指令应用于代理服务器响应的标头字段,则将选择第一个匹配的指令。使用此指令,还可以将主机名添加到代理服务器发出的相对重定向中。
说白了,就是更改原本的后端响应回来的响应头中的 Location 重定向字段。咱们先写一个 PHP 页面,设置一个跳转。
代码语言:javascript复制<?php
header('Location: http://localhost/redirect/');
这个链接随便写的。然后通过 CURL -v 显示响应头信息,查看 Location 响应头字段就是我们在 PHP 中设置的内容。
代码语言:javascript复制curl -v 'http://192.168.56.88:8027/other/fastcgi1/proxy/3.redirect.php'
接着在 Nginx 中添加如下配置进行测试。
代码语言:javascript复制location ~ /other/(.*) {
proxy_pass http://192.168.56.88/$1?$args;
proxy_redirect http://localhost/redirect/ http://192.168.56.89/;
proxy_redirect ~/(. ).html /$1.html;
proxy_redirect ~(http://. )/(. ) http://192.168.56.88/$2;
}
在这里我们添加了三个配置,其实可以看出来,这个配置是可以在同一层级下配置多个的,以第一个匹配到的为准。
然后重载 Nginx 的配置文件之后,再次访问上面的链接,会发现 Location 返回的内容变成了 http://192.168.56.89/
。
接下来,我们继续在 PHP 代码上进行测试,注释或者在之前的 header
函数下面继续写跳转到其它不同的页面的代码,这里可以不需要前面的服务器名称。
header('Location: /aaa.html');
//header('Location: /index.html');
访问之后返回的 Location 变成了 Location: http://192.168.56.88:8027/aaa.html
。注意,这里走的匹配是第二条,也就是 ~/(. ).html
的匹配规则。不带服务器名称的会自动加上服务器 Host 及端口号。
然后我们再来测试一下更复杂的正则匹配的问题。
代码语言:javascript复制header('Location: http://192.168.56.89/1.php');
//header('Location: http://192.168.56.89/index.html');
第一个,会走最后一个匹配规则,得到的 Location 结果是:
代码语言:javascript复制Location: http://192.168.56.88/1.php
而第二个,则会被第二个 .html 的那个规则匹配到,结果是下面这样。
代码语言:javascript复制Location: http://192.168.56.88:8027//192.168.56.89/index.html
有的时候,我们希望不要去更改源代码,只是临时的修改一下重定向的规则或者内容,就可以在 Nginx 这一层上直接使用 proxy_redirect 来实现。这个功能可能平时用过的小伙伴不多,但是在做一些测试或者临时修改时,还是非常有用的。
proxy_request_buffering
启用或禁用客户端请求正文的缓冲。
代码语言:javascript复制proxy_request_buffering on | off;
启用缓冲后,会先从客户端读取整个请求正文,然后再将请求发送到代理服务器。禁用缓冲时,请求正文会在收到后立即发送到代理服务器。在这种情况下,如果 nginx 已经开始发送请求正文,则无法将请求传递给下一个服务器。当 HTTP/1.1 分块传输编码用于发送原始请求正文时,无论指令值如何,请求正文都将被缓冲,除非启用 HTTP/1.1 进行代理。
proxy_send_lowat
如果该指令设置为非零值,则 Nginx 将尝试使用 kqueue 方法的 NOTE_LOWAT 标志或具有指定大小的 SO_SNDLOWAT 套接字选项来最小化到代理服务器的传出连接上的发送操作数。
代码语言:javascript复制proxy_send_lowat size;
默认值是 0 ,该指令在 Linux、Solaris 和 Windows 上被忽略。
proxy_send_timeout
设置将请求传输到代理服务器的超时时间。
代码语言:javascript复制proxy_send_timeout time;
默认值 60s ,超时仅在两个连续的写操作之间设置,而不是为整个请求的传输设置。如果代理服务器在这段时间内没有收到任何内容,则连接将关闭。
proxy_set_body
允许重新定义传递给代理服务器的请求正文。
代码语言:javascript复制proxy_set_body value;
没有默认值,该值可以包含文本、变量及其组合。需要注意的是,它会覆盖原来的 POST 请求中的 Body 部分内容。比如我们设置:
代码语言:javascript复制proxy_set_body 'value=$uri';
那么使用 POST 并且是 application/x-www-form-urlencoded
方式请求之后,PHP 那边获得的 Body 里面的内容会是我们在这里设置的 uri
的内容,而不是真实请求中的数据,所以这个并不常用。
proxy_set_header
允许将字段重新定义或附加到传递给代理服务器的请求标头。
代码语言:javascript复制proxy_set_header field value;
默认值是这样的。
代码语言:javascript复制proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
该值可以包含文本、变量及其组合。当且仅当当前级别上没有定义 proxy_set_header 指令时,这些指令才从先前的配置级别继承。如果启用缓存,则从原始请求不会传递给代理服务器。可以像这样传递未更改的“Host”请求标头字段:
代码语言:javascript复制proxy_set_header Host $http_host;
但是,如果客户端请求标头中不存在此字段,则不会传递任何内容。在这种情况下,最好使用 $host 变量 - 如果该字段不存在,它的值等于“主机”请求标头字段中的服务器名称或主服务器名称:
代码语言:javascript复制proxy_set_header Host $host;
此外,服务器名称可以与代理服务器的端口一起传递:
代码语言:javascript复制proxy_set_header Host $host:$proxy_port;
如果标头字段的值为空字符串,则该字段将不会传递给代理服务器。
上面官网的例子都在讲修改 Host 的问题,但其实这个配置指令更大的作用是在于可以自定义头并且可以传递真实的客户端 IP 。我们先来看一下自定义一个头。
代码语言:javascript复制proxy_set_header aaa 123123;
设置完成之后,通过 PHP 打印 $_SERVER
,我们就可以看到一个 HTTP_AAA=123
这样的头信息出现。并且 Host 和 Connection 也是正常存在的,这说明它和 proxy_set_body 不同,不会覆盖原来的配置。
而对于真实 IP 的问题,相信只要是配置过 Nginx 反向代理或负载均衡的小伙伴都会知道,代理之后,我们在 PHP 代码中通过 REMOTE_ADDR
获取到的就是这台代理服务器的 IP ,比如说现在直接访问 PHP 页面,查看 $_SERVER
中输出的内容中 REMOTE_ADDR
的值就是 88 这台代理服务器的。通常来说,加上这样两个配置,然后在代码中去判断并优先获取这两段配置的请求头信息,就可以拿到真实的 IP 。
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
为什么是这两个请求头名称呢?这也是约定俗成的标准,X-Forwarded-For 是所有代理的历史,会将当前的 $remote_addr
不断附加上去,如果有多层代理就会很明显。我们可以看一下 TP6 中的源码。
protected $proxyServerIp = [];
protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];
public function ip(): string
{
if (!empty($this->realIP)) {
return $this->realIP;
}
$this->realIP = $this->server('REMOTE_ADDR', '');
// 如果指定了前端代理服务器IP以及其会发送的IP头
// 则尝试获取前端代理服务器发送过来的真实IP
$proxyIp = $this->proxyServerIp;
$proxyIpHeader = $this->proxyServerIpHeader;
if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
// 从指定的HTTP头中依次尝试获取IP地址
// 直到获取到一个合法的IP地址
…………
}
…………
…………
}
能看出来是啥意思吧,就是先从 REMOTE_ADDR 中获取 IP ,然后再通过两个代理属性配置的数组 proxyServerIpHeader 中去查找代理请求头的 IP 地址,前面两个正是我们上面配置的 X-Real-IP 和 X-Forwarded-For 。在 TP6 中,要使用这个功能,还需要在 App 目录下的 Request.php 文件中重写 proxyServerIp属性, protected
上述源码在 topthink/framework/src/think/Request.php 文件中,另外 Laravel 的源码中默认是使用的 X-Forwarded-For
,源码位于 laravel/framework/src/Illuminate/Http/Request.php 的 ip() 方法中,向下探索可以找到位于 symfony/http-foundation/Request.php 中的 getTrustedValues() 方法,大家可以自己查阅一下哦。设置信任代理主机 IP 直接在 app/Http/Middleware/TrustProxies.php 这个中间件中修改 $proxies
属性即可。
上面是我们在 PHP 框架中处理真实 IP 的情况,但其实 Nginx 也自带了一个处理真实 IP 的模块,下篇文章我们就会看到。
proxy_socket_keepalive
为到代理服务器的传出连接配置“TCP keepalive”行为。
代码语言:javascript复制proxy_socket_keepalive on | off;
默认情况下,操作系统的设置对套接字有效。如果该指令设置为值“on”,则为套接字打开 SO_KEEPALIVE 套接字选项。
proxy_store
允许将文件保存到磁盘。
代码语言:javascript复制proxy_store on | off | string;
默认 off ,on 参数保存具有与指令别名或根目录相对应的路径的文件。off 参数禁用保存文件。此外,可以使用带有变量的字符串显式设置文件名:
代码语言:javascript复制proxy_store /data/www$original_uri;
根据收到的“Last-Modified”响应头域设置文件的修改时间。响应首先被写入一个临时文件,然后文件被重命名。从版本 0.8.9 开始,临时文件和持久存储可以放在不同的文件系统上。但是,请注意,在这种情况下,文件是跨两个文件系统复制的,而不是廉价的重命名操作。因此,建议对于任何给定位置,保存的文件和保存临时文件的目录(由 proxy_temp_path 指令设置)都放在同一个文件系统上。
和 fastcgi_store 类似,不知道咋测,试了也没啥效果,期待大佬指点。
proxy_store_access
为新创建的文件和目录设置访问权限.
代码语言:javascript复制proxy_store_access users:permissions ...;
默认值是 user:rw ,如果指定了任何组或所有访问权限,则可以省略用户权限。
总结
别的不提,proxy_set_header 和 proxy_redirect 应该还是非常值回票价的吧,特别是 proxy_set_header 中处理真实 IP 的这一部分。不过这也是非常常见的一个配置,因为只要你做过反向代理或者负载均衡的项目,而且项目中有获取客户 IP 的功能,那么这个配置肯定用过。剩下的内容还是那句话,以了解为主吧。
至此,又一个大模块结束了。回想一下这部分的配置,最核心的其实还是在于概念的理解,也就是 正向 与 反向 代理这两个。
参考文档:
http://nginx.org/en/docs/http/ngx_http_proxy_module.html