nginx反向代理学习
在日常开发或测试中,我们常会遇到这样的场景:多个后端服务需要对外提供访问,但不想暴露过多端口;或者服务的 IP / 端口可能变化,希望前端能通过固定地址访问。这时候,Nginx 反向代理就是绝佳的解决方案 ------ 它能作为 "中间层",通过一个端口统一接收请求,再根据 URL 路径转发到对应的后端服务。
以两个 Express 服务为例,搭建「Nginx 反向代理 + 多服务 + 前端测试页面」的完整流程,最终实现:通过 ip:端口号服务标识/接口 访问不同后端,彻底解决多服务端口管理和地址灵活性问题。
nodejs实现后台服务
如果没有安装nodejs,需要安装nodejs。
安装后新建文件夹,在文件夹内执行:
# 初始化npm项目
npm init -y
# 安装Express依赖
npm install express
创建两个js文件(核心逻辑一致,仅监听端口不同):
文件 1:simple-http-server-v1.js(监听 3001 端口)
const express = require("express");
const app = express();
// 解析JSON和表单格式请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// POST接口:返回请求体
app.post("/post/test", (req, res) => {
const result = {
message: "Hello World!post",
success: true,
data: req.body
};
res.send(result);
});
// GET接口:返回查询参数
app.get("/get/test", (req, res) => {
const result = {
message: "Hello World!-get",
success: true,
data: req.query
};
res.send(result);
});
// 根路径接口:标识服务版本
app.get("/", (req, res) => {
const result = {
message: "Hello World!-get",
success: true,
data: "请求3001服务(v1版本)"
};
res.send(result);
});
// 监听3001端口
app.listen(3001, () => {
console.log("v1服务正在监听 3001 端口 !");
});
文件 2:simple-http-server-v2.js(监听 3002 端口)
const express = require("express");
const app = express();
// 解析JSON和表单格式请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// POST接口:返回请求体
app.post("/post/test", (req, res) => {
const result = {
message: "Hello World!post",
success: true,
data: req.body
};
res.send(result);
});
// GET接口:返回查询参数
app.get("/get/test", (req, res) => {
const result = {
message: "Hello World!-get",
success: true,
data: req.query
};
res.send(result);
});
// 根路径接口:标识服务版本
app.get("/", (req, res) => {
const result = {
message: "Hello World!-get",
success: true,
data: "请求3001服务(v1版本)"
};
res.send(result);
});
// 监听3001端口
app.listen(3002, () => {
console.log("v1服务正在监听 3002 端口 !");
});
启动后端服务
打开两个 PowerShell 窗口,分别执行:
powershell
# 启动v1服务
node simple-http-server-v1.js
# 启动v2服务
node simple-http-server-v2.js
nginx配置
/conf/nginx.conf
核心是通过 Nginx 的location指令,根据 URL 路径转发请求到对应的后端服务。
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 7963; # Nginx对外暴露的统一端口
server_name 0.0.0.0;
# 路径转发:/simple-http-server-v1/ 映射到v1服务(3001端口)
location /simple-http-server-v1/ {
proxy_pass http://localhost:3001/; # 末尾的/必须加,确保路径正确拼接
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 路径转发:/simple-http-server-v2/ 映射到v2服务(3002端口)
location /simple-http-server-v2/ {
proxy_pass http://localhost:3002/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 根路径:部署前端测试页面(index.html)
location / {
root html;
index index.html index.htm;
# 配置CORS跨域(解决前端fetch请求跨域问题)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
# 处理OPTIONS预检请求(跨域必备)
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
}
}
/html/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GET请求发送器</title>
</head>
<body>
<h1>GET请求发送器</h1>
<div>
<label for="urlInput">请输入请求路径:</label>
<br>
<input type="text" id="urlInput" placeholder="/api/" size="40" value="/api/">
</div>
<br>
<button onclick="sendGetRequest()">发送GET请求</button>
<br><br>
<div id="responseArea">
<h3>响应结果:</h3>
<pre id="responseText">等待请求...</pre>
</div>
<script>
function sendGetRequest() {
const urlInput = document.getElementById('urlInput');
const responseText = document.getElementById('responseText');
const path = urlInput.value.trim();
if (!path) {
responseText.textContent = '错误: 请输入有效的路径';
return;
}
console.log('请求路径:', path);
responseText.textContent = `正在请求: ${path}`;
// 直接获取响应,不做任何状态判断
fetch(path)
.then(response => {
console.log('响应状态:', response.status, response.statusText);
console.log('响应URL:', response.url);
// 无论什么状态码,都返回响应文本
return response.text().then(text => {
return {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries([...response.headers.entries()]),
url: response.url,
body: text
};
});
})
.then(result => {
// 显示完整的响应信息
responseText.textContent =
`响应状态: ${result.status} ${result.statusText}
响应URL: ${result.url}
响应头:
${JSON.stringify(result.headers, null, 2)}
响应体:
${result.body}`;
})
.catch(error => {
responseText.textContent = `请求异常: ${error.message}`;
});
}
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('urlInput').value = '/api/';
});
</script>
</body>
</html>
运行
运行nginx之后,使用http://localhost:7963/ 访问

访问服务,
访问的服务是 http://localhost:7963/simple-http-server-v2/
但是在nginx内部转为了 http://localhost:3002/

如果在nginx中
location /simple-http-server-v2/ {
proxy_pass http://localhost:3002/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
这里: proxy_pass http://localhost:3002/; 最后没有/,是这样写的: proxy_pass http://localhost:3002 ,最后的请求就会是: http://localhost:3002/simple-http-server-v2/

其他
对于已有的服务,或者前端页面。
服务
服务监听0.0.0.0
前端页面
在请的时候添加"服务标识符",用于让nginx转发。