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服务。
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也不是万能的,还是需要我们自己去理解代理背后的逻辑,才能有效应对各种问题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步