NGINX杂谈——proxy和rewrite的区别
概述
使用NGINX服务器进行HTTP报文的处理和转发时,有一些容易混淆的概念。比如像正向代理和反向代理、root和alias、proxy和rewrite。
这篇博客主要想记录一下自己对proxy和rewrite的一些理解。(这里不去考虑rewrite的最后一项参数和区分301、302,建议想看完整细节的看这篇博客,更多关于NGINX的知识可以看官方文档)。
Little NGINX
我用C实现一个阉割版的NGINX,以此来感受proxy和rewrite两个过程中,HTTP报文变化的区别。具体的项目可以看这个代码仓库,我不是依据NGINX的源码进行的代码编写,所以具体的实现上会有所差异,实际使用的功能也没有NGINX那么丰富。还有就是代码虽然套了C的壳,但核心部分是基于Linux C编写的,所以只支持在Linux系统或MacOS等类Unix系统上使用。
克隆项目
git clone https://gitee.com/xuanyusan/little_nginx.git cd little_nginx
编译项目
g++ little_nginx.cpp -o little_nginx
开启little_nginx
./little_nginx
打印出已配置的服务则运行成功,此时可以在浏览器输入
localhost:8880
访问服务。开启nodejs koa服务
cd servers/node_koa/ npm install node index.js
开启nodejs服务后,在浏览器输入
localhost:8880/koa/
可以访问到nodejs的服务。在浏览器输入
localhost:8880/koa/path1
则可以体验proxy处理重定向的整个流程,little_nginx的stdout也会有对应输出,帮助理解。
proxy和rewrite的区别
proxy和rewrite的区别可以通过下面的两个图进行简单理解。对图中WEB服务的理解应该包括NGINX提供的WEB服务。
Created with Raphaël 2.2.0rewrite客户端客户端NGINXNGINXWEB服务WEB服务request根据rewrite参数构建状态为302的响应报文根据响应报文的Location参数进行重定向生成新的requestresponse Created with Raphaël 2.2.0proxy客户端客户端NGINXNGINXWEB服务WEB服务request根据proxy_pass的规则 修改客户端的requestresponse假设发生了重定向 则根据proxy_redirect的规则 修改response的Location参数rewrite
从图中可以看出,rewrite必定会发生重定向,因为它会给客户端返回一个302的响应报文,而其中的Location参数就是由一开始请求的path根据rewrite参数处理得到的。
假设nginx配置为:
rewrite ^/(.*) https://www.baidu.com/$1 redirect;
我们通过curl去看HTTP报文的信息则是这样的:
$ curl -v http://localhost/test/ * Trying ::1:80... * TCP_NODELAY set * Connection failed * connect to ::1 port 80 failed: Connection refused * Trying 127.0.0.1:80... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 80 (#0) > GET /test/ HTTP/1.1 > Host: localhost > User-Agent: curl/7.65.3 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 302 Moved Temporarily < Server: nginx/1.17.3 < Date: Wed, 15 Sep 2021 10:02:04 GMT < Content-Type: text/html < Content-Length: 145 < Connection: keep-alive < Location: https://www.baidu.com/test/ < <html> <head><title>302 Found</title></head> <body> <center><h1>302 Found</h1></center> <hr><center>nginx/1.17.3</center> </body> </html> * Connection #0 to host localhost left intact
可以看到相应头的Location参数就是由请求报文的
/test/保留了
test/部分并在前面接上
https://www.baidu.com/得到的。
而这就必须保证rewrite第二个参数的域名或者ip以及端口必须是客户端可以访问的,不行的话,就不能用rewrite。所以rewrite更常用于域名变更和网站搬迁。
proxy
而proxy是不一定会发生重定向的,即使发生重定向,也是由具体的服务决定的,而不是NGINX这个中间代理可以越俎代庖的。NGINX的proxy有两个关键配置,一个是proxy_pass,一个是proxy_redirect,分别对应请求报文和响应报文的处理。
proxy_pass就是修改请求的协议、路径和host,然后发给具体的服务端。proxy_redirect就是在具体服务端发回来一个302响应报文的时候,根据proxy_redirect参数的规则去修改Location。
假设我们现在的机器上,在本地回环的8881端口跑着一个nodejs的服务,然后我们通过机器某个局域网的ip地址去访问80端口的
/test。
nodejs服务入口文件如下:
const Koa = require('koa'); const Router = require('koa-router'); const Koa_Logger = require("koa-logger"); const koaBody = require('koa-body') const app = new Koa(); const router = new Router(); const logger = new Koa_Logger(); router.get("/baseurl", async (ctx)=>{ ctx.body = "<h1>Node Koa</h1>" }); router.get("/baseurl/path1", async (ctx)=>{ ctx.redirect("/baseurl/path3"); }); router.get("/baseurl/path3", async (ctx)=>{ ctx.body = "<h1>Node Koa Path3</h1>" }); app.use(logger); app.use(koaBody()); app.use(router.routes()); app.listen(8881, () => { console.log('应用已经启动,http://localhost:8881'); });
如果我们只配置了proxy_pass:
proxy_pass http://localhost:8881/baseurl;
访问非重定向的页面是正常的:
$ curl -v http://localhost/test/ * Trying ::1:80... * TCP_NODELAY set * Connection failed * connect to ::1 port 80 failed: Connection refused * Trying 127.0.0.1:80... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 80 (#0) > GET /test/ HTTP/1.1 > Host: localhost > User-Agent: curl/7.65.3 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Server: nginx/1.17.3 < Date: Wed, 15 Sep 2021 10:28:57 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 17 < Connection: keep-alive < * Connection #0 to host localhost left intact <h1>Node Koa</h1>
因为这个时候我们的HTTP请求的协议、ip和端口被修改为了proxy_pass参数里的
http、
localhost和
8881,并且发送给了localhost的8881端口。这时,监听8881端口的服务就可以接收报文并返回结果。
所以proxy_pass的参数就不要求客户端可以访问,只要代理服务器可以访问就可以了。客户甚至不知道他们访问的服务到底是谁提供的,这也是反向代理的一大特点。
但是,如果服务端发生了重定向,结果则如下:
$ curl -v http://localhost/test/path1 * Trying ::1:80... * TCP_NODELAY set * Connection failed * connect to ::1 port 80 failed: Connection refused * Trying 127.0.0.1:80... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 80 (#0) > GET /test/path1 HTTP/1.1 > Host: localhost > User-Agent: curl/7.65.3 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 302 Found < Server: nginx/1.17.3 < Date: Wed, 15 Sep 2021 10:29:54 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 59 < Connection: keep-alive < Location: /baseurl/path3 < * Connection #0 to host localhost left intact Redirecting to <a href="/baseurl/path3">/baseurl/path3</a>.
可以看到它重定向的结果不是我们预期中的http://localhost/test/path3,而是http://localhost/baseurl/path3。而想要让它重定向到http://localhost/test/path3,我们就不能直接把nodejs扔给我们的响应头原封不动地扔给客户端,而是要用proxy_redirect来指定相应头Location参数的修改规则。比如:
proxy_redirect /baseurl /test;
$ curl -v http://localhost/test/path1 * Trying ::1:80... * TCP_NODELAY set * Connection failed * connect to ::1 port 80 failed: Connection refused * Trying 127.0.0.1:80... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 80 (#0) > GET /test/path1 HTTP/1.1 > Host: localhost > User-Agent: curl/7.65.3 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 302 Found < Server: nginx/1.17.3 < Date: Wed, 15 Sep 2021 10:37:30 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 59 < Location: http://localhost/test/path3 < Connection: keep-alive < * Connection #0 to host localhost left intact Redirecting to <a href="/baseurl/path3">/baseurl/path3</a>.
这里可以明显看到Location的改变,当然proxy不会去修改报文的body部分,所以body部分还是
/baseurl/path3。这里万一因为网络原因,重定向没有完成,显示了这个页面,用户又去点击,就会出现错误。NGINX也不是万能的,还是需要我们自己去理解代理背后的逻辑,才能有效应对各种问题。
- nginx配置中$http_host、$host、$host:$proxy_port 简单区别
- 从头学习nginx(五)之location和proxy_pass后面有无/的区别
- nginx,rewrite,proxy_pass,post数据,表单
- nginx配置proxy_pass URL末尾加与不加/(斜线)的区别
- 浅谈apache和nginx的rewrite的区别
- nginx 域名跳转一例~~~(rewrite、proxy)
- nginx 域名跳转一例~~~(rewrite、proxy)
- nginx rewrite proxy_pass location 的理解
- Nginx配置的rewrite编写时last与break的区别分析
- Nginx与Apache的Rewrite规则的区别
- Nginx 基本配置(log_format,rewrite,proxy)(转)
- nginx 域名跳转一例~~~(rewrite、proxy)
- 浅谈apache和nginx的rewrite的区别
- nginx+rewrite+proxy+cache基本实验
- 浅谈apache和nginx的rewrite的区别
- Nginx与Apache的Rewrite规则的区别
- 怎样区别nginx中rewrite时break和last
- Nginx与Apache的Rewrite规则的区别
- [nginx]proxy_pass&rewrite知识点
- Nginx与Apache的Rewrite规则的区别