Nginx+lua+openresty系列 | 第三篇:nginx反向代理
Posted 秃头A计划
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx+lua+openresty系列 | 第三篇:nginx反向代理相关的知识,希望对你有一定的参考价值。
上篇文章,我们了解了nginx的error_page和虚拟主机的配置。今天我们就讲讲nginx的反向代理。nginx的负载均衡配置就依赖于反向代理。
1.什么是反向代理
首先看官方说法:
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
有反向代理,那么是不是就应该有正向代理?我们讲个找媳妇儿的故事了解下正向和反向代理。
角色设定:(读完故事在看后面的解释)
秃头男:代表客户机。找媳妇动作=要访问促进中日交流的小网站
王婆:代表正向代理服务器。找月老动作=找到目标网站
月老:代表反向代理服务器。把媳妇说给王婆动作=把正向代理服务器要访问的网站内容返回
众仙子:代表服务器资源。等于网站内容。
话说秃头男,已年方二十有四,仍然还是单身狗一只,虽然钱多话少,依然得不到姑娘的芳心啊。秃头男着急啊,自己谈不到媳妇那就相亲吧!相亲得去找媒婆,村头的王婆可是这方面老手了,一相一个准。于是乎,秃头男厚着脸皮去找王婆:王婆,俺要找媳妇,俺现在就要!王婆欢心道:你可找对人了,媳妇可不是你想找就能找,没有本婆子,村里头的那些秃头们也只能靠麒麟臂了,这不村西的王二麻子刚刚给他找了个媳妇哟。
于是,王婆连线找到月老,告诉月老:又来生意了,快给我整个媳妇过来。月老回复到:666啊,马上就给你整。月老这边忙把众仙子叫来:你们都是犯了天条的仙子,玉皇大帝慈悲,只罚你们与凡人结亲。我从尔等中择一人,去与凡人结亲去吧。
故事看起来没什么意思,仅仅为了帮助理解。那我们就来分析下整个正向和反向代理的过程。
故事解析:
比如我要访问google网站,就是秃头男找媳妇的动作。在浏览器敲入www.google.com,但是你发现你的机器访问不了,为啥呢?这是政府企业层面的原因,咱小小平民也管不着啊。然后我就要访问怎么办?找个代理服务器,也就是俗话说的fanqiang软件,就是王婆了。这时候fq软件连接到了google服务器,但是谷歌考虑到了我是为全球用户服务,我的用户体验一定要好,响应速度要快,怎么办呢?简单做法多搞几台服务器(实际复杂的要死...),服务器内容是一模一样的,这样可以同时为多个用户服务。然后统一由google服务器根据策略选择内部的哪台服务器上的内容返回给用户。google服务器就是月老啦,对外表现就是一个反向代理服务器。内部的多台服务器就是众仙子。然后层层返回,最终我们访问到了谷歌网站,也就是娶到媳妇啦。
对于正向代理服务器来说,是主动方;反向代理服务器,是被动方。反向代理服务器感受不到客户机的存在,同样的,正向代理服务器感受不到众仙子的存在。一切都是由代理方也就是中间人去沟通,头尾做的事情就是一个要一个给。看起来就好像是,正向代理和反向代理两个人在通信。可以只存在正向代理,也可以只存在反向代理,又或者是两者皆存在。
2.反向代理有何用途
网关服务器
负载均衡
请求转发、请求统计
黑白名单拦截
nginx常用做反向代理服务器,用于请求拦截控制。作为一个代理人,把原本请求到nginx服务器上的请求,转发到符合规则和策略的资源上或者访问者是在黑名单上,拒绝访问。作为一个网站的统一入口,可以灵活的控制请求的访问次数、统计访问记录、拦截不安全访问等等。
3.如何配置反向代理
废话少说,还是直接撸代码比较直接。千言万语,不敌千次敲击。
这里先讲下location
第二篇文章简单了说了下location,并没有说怎么用的。
1location / {
2 root html;
3 index index.html index.htm;
4 }
上面代码片段是nginx的默认配置,启动nginx服务,程序就会监听该机器的80端口,然后location拦截与其URI路径相匹配(这里是根路径/)的请求。最终请求进入到location块,展示nginx欢迎页。我们的url现在比较流行的是restful形式,还有以.do结束的请求,还有静态文件的请求以 .jpg、.js、.css等请求,为了方便这些请求的管理,于是乎location就有了匹配规则。可以使用正则表达式拦截静态文件
location的先后匹配顺序不用过分研究,实践见真章,试试就能知道,心里大致有个数就好。
反向代理实战部分
我们做一个简单的demo演示:我们以代理服务器的身份访问Google网站,模拟网站内部服务器的调用。
操作步骤:
配置upstream模块,用于选择哪台服务器。该配置位于HTTP块内,server块外。
活学活用,使用虚拟主机模拟两台内部服务器
配置proxy_pass命令,使用反向代理功能 (待会详解概念含义,先看效果,再去理解)
启动测试
如上图所示:注意三个地方
特别注意:upstream别名不要使用下划线
upstream块,紧跟着后面有一个to_google别名,因为可以配置多个upstream块,所以为了区别需要别名,且在server块使用的时候需要指定该别名。块内是两个server,也就是选择哪台内部服务器展示网站内容的域名,也可以写成 ip:port 形式。也可以只写一个server,相当于请求转发;配置多个就是负载均衡。
location内的proxy_pass配置。使用 http:// + upstream别名的形式,也就是当访问www.google.com域名的时候,location匹配到根路径,然后使用反向代理功能,将该请求‘跳转’到upstream块中,由该块决定请求策略。upstream块发现有两个server可以使用,于是使用轮训策略(默认情况下),依次访问localhost:5588和localhost:6699
内部服务器使用的是上篇中的虚拟主机功能,使一台主机可以作为多个网站使用。(别忘了配置hosts文件哦,需要将谷歌网站指向本地)
我们看下访问效果:
这样nginx反向代理服务器对外就是一台真正的服务器,对内它是一个管理者,控制着请求的指向,也就是让你朝东你不能朝西,让你打狗你不准撵鸡...。
我们详细说说proxy_pass 这个东西。
聪明的读者都会发现,nginx的配置文件除了events块,http块,server块等块内容,基本都是key:value的形式。虽然nginx不是一门语言,但是它也有变量,可以自定义变量也可以直接使用模块提供的内置变量(我们一直说的模块就类似于Java的类库,有nginx自带的也有第三方的)。proxy_pass 就是nginx的ngx_http_proxy_module模块提供的配置。
打开nginx官网,点击右侧的documentation进入文档查看页,如下图
nginx的变量要讲的话几篇文章也搞不定,有兴趣的同学可以看看章亦春的漫谈变量系列http://blog.sina.com.cn/s/articlelist_1834459124_1_1.html。
打开proxy模块,我们可以看到很多的命令配置,包括HTTP内容的cache、header等相关命令。
我们ctrl+f 输入proxy_pass快速定位到该命令(这个肯定会吧?),然后点击超链接到指定的文档位置,查看文档释义
具体用法和含义都是英文文档,所以要有点英语功底能看懂,不行就复制到Google翻译去也行。模块的内置变量在最下面的Embedded Variables部分。因为proxy_pass这个命令很常用,反向代理必备,所以不用看文档也知道它是干嘛的,但是其他的命令可能就需要琢磨一下子了。
4.用户IP传递问题
1#左右滑动查看代码
2server {
3 listen 80;
4 server_name www.google.com;
5 location / {
6 echo 'Hello, This is 80 machine! remote_addr=$remote_addr';#使用内置变量获取客户机ip地址
7 #proxy_pass http://to_google; # 井号是注释符号
8 }
9}
1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:80
3Hello,This is 80 machine! remoteAddr= 192.168.1.132
1#左右滑动查看代码
2[root@localhost ~]# ifconfig
3ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
4 inet 192.168.1.132 netmask 255.255.255.0 broadcast 192.168.1.255
5 inet6 fe80::a093:c75b:1854:e4a prefixlen 64 scopeid 0x20<link>
6 ether 00:50:56:37:8c:de txqueuelen 1000 (Ethernet)
7 RX packets 5482 bytes 650903 (635.6 KiB)
8 RX errors 0 dropped 0 overruns 0 frame 0
9 TX packets 5211 bytes 537377 (524.7 KiB)
10 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
4.2.然后我们使用代理再来看下是什么结果,代码如下
1 #左右滑动查看代码
2 upstream to_google{
3 server localhost:5588;
4 server localhost:6699;
5 }
6
7 server {
8 listen 80;
9 server_name www.google.com;
10 location / {
11 proxy_pass http://to_google;#反向代理
12 }
13 }
14 server {
15 listen 5588;
16 location / {
17 echo 'Hello, This is 5588 machine! remote_addr=$remote_addr';
18 }
19 }
20 server {
21 listen 6699;
22 location / {
23 echo 'Hello, This is 5588 machine! remote_addr=$remote_addr';
24 }
25 }
当我们不代理的时候,初次请求确实是客户机请求过来的。但是经过代理之后,对于虚拟主机来说,它的上次请求是从nginx转发过去的,也就是本机,本机ip就是127.0.0.1咯。
1#左右滑动查看代码
2@RestController
3public class NginxController {
4 @RequestMapping("/nginx")
5 public String nginx(HttpServletRequest request){
6 String remoteAddr = request.getRemoteAddr();
7 return "remoteAddr:"+remoteAddr;
8 }
9}
结果就是和nginx的结果一致,不同的是只要代理到java程序,使用request获取到的ip都是nginx服务器的ip。所以不要质疑nginx的能力,它就是HTTP请求的私人定制。
解决方案:
1 #左右滑动查看代码
2 upstream springboot{
3 #springboot项目开启18080端口 最终访问者
4 server 127.0.0.1:18080;
5 }
6 upstream twoProxy{
7 server 127.0.0.1:5588;
8 }
9
10 server {
11 listen 80;
12 location / {
13 #访问80端口 直接代理到spring boot
14 proxy_pass http://springboot;#试验一次代理的情况
15 proxy_set_header X-Real-IP $remote_addr;
16 proxy_set_header Host $host;
17 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
18 }
19 }
20 server {
21 listen 81;
22 location / {
23 #访问81端口,先代理到5588端口,再通过5588代理到spring boot
24 proxy_pass http://twoProxy;#试验多次代理的情况
25 proxy_set_header X-Real-IP $remote_addr;
26 proxy_set_header Host $host;
27 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
28 }
29 }
30 server {
31 listen 5588;
32 location / {
33 proxy_pass http://springboot;
34 proxy_set_header X-Real-IP $remote_addr;
35 proxy_set_header Host $host;
36 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
37 }
38 }
看一下上面的代码配置。
一次代理:localhost:80 --> localhost:18080
二次代理:localhost:80 --> localhost:5588 --> localhost:18080
每个location块都贴上了proxy_set_header,且使用了多个内置变量。
proxy_set_header的意思是将每次代理的HTTP请求都设置了一个请求头,key X-Real-IP, value就是内置变量,这样在java项目中就可以使用request获取到请求头中的内容。X-Real-IP只是一种头的字符串表示,因为用的多了所以也就成了标准,大家都用,看到了也就明白什么意思,类似的X-Forwarded-For也是。$proxy_add_x_forwarded_for则是proxy模块的一个内置变量
$proxy_add_x_forwarded_for
the “X-Forwarded-For” client request header field with the
$remote_addr
variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the$proxy_add_x_forwarded_for
variable is equal to the$remote_addr
variable.
碰到这么长的字符串,很多人就会怕,感觉很难。因为大脑是惰性的,碰到陌生的东西就反感。当你去了解去接触的时候,就会发现就那么回...
我们看下springboot项目的代码,很简单,获取请求头中的内容并输出
1#左右滑动查看代码
2@RestController
3public class NginxController {
4
5 @RequestMapping("/nginx")
6 public String nginx(HttpServletRequest request, HttpServletResponse response) throws IOException {
7
8 String remoteAddr = request.getRemoteAddr();
9 String remoteHost = request.getRemoteHost();
10 String realIP = request.getHeader("X-Real-IP");
11 String forward = request.getHeader("X-Forwarded-For");
12 String Host = request.getHeader("Host");
13
14 PrintWriter writer = response.getWriter();
15
16 writer.println("remoteAddr:"+remoteAddr);
17 writer.println("remoteHost:"+remoteHost);
18 writer.println("Host:"+Host);
19 writer.println("realIP:"+realIP);
20 writer.println("forward:"+forward);
21
22 return null;
23 }
24}
最后测试看结果:(因为只有一台机器,只能使用VMware虚拟机访问)
一次代理
1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:80/nginx
3remoteAddr:127.0.0.1
4remoteHost:127.0.0.1
5Host:192.168.1.1
6realIP:192.168.1.132
7forward:192.168.1.132
二次代理
1#左右滑动查看代码
2[root@localhost ~]# curl 192.168.1.1:81/nginx
3remoteAddr:127.0.0.1
4remoteHost:127.0.0.1
5Host:192.168.1.1
6realIP:127.0.0.1
7forward:192.168.1.132, 127.0.0.1
分析:
proxy模块还有其他更多的指令配置,一般常见就是设置头和设置反向代理一起使用,有兴趣可以研究下其他指令,官方文档写的都很详细。
我们后续将研究nginx负载均衡的功能。
本文如有瑕疵错误,欢迎指正!
以上是关于Nginx+lua+openresty系列 | 第三篇:nginx反向代理的主要内容,如果未能解决你的问题,请参考以下文章
高并发 Nginx+Lua OpenResty系列——Lua模版渲染
高并发 Nginx+Lua OpenResty系列——Lua模版渲染
Nginx+lua+openresty系列 | 第三篇:nginx反向代理
高并发 Nginx+Lua OpenResty系列——HTTP服务