它触及了 Nginx 作为反向代理服务器的核心工作原理。观察到的现象正是 Nginx 设计的精妙之处。
核心原理:Nginx 是"中间人"
后端并没有直接"传给前端"数据,而是传给 Nginx,再由 Nginx 转发给前端。
整个数据流是这样的:
前端浏览器 ←→ Nginx(90端口) ←→ 后端应用(8080端口)
详细的工作流程
1. 请求阶段(前端 → Nginx → 后端)
前端浏览器 请求: http://域名:90/api Nginx:90端口 转发请求到 http://localhost:8080/api 后端Spring应用:8080端口
2. 响应阶段(后端 → Nginx → 前端)
后端Spring应用:8080端口 返回数据给 Nginx Nginx 将数据返回给前端浏览器
为什么后端不需要知道前端的存在?
关键机制:反向代理配置
Nginx 的配置中定义了代理规则,告诉 Nginx:"当收到对90端口的请求时,实际去8080端口获取数据"。
nginx
# Nginx 配置文件示例 (nginx.conf)
server {
listen 90; # Nginx 监听90端口
server_name localhost;
# 静态文件服务
location / {
root /usr/share/nginx/html; # 前端文件目录
index index.html index.htm;
}
# API 代理配置 - 这是关键!
location /api/ {
proxy_pass http://localhost:8080/; # 将/api请求转发到8080端口
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;
}
}
后端视角:谁在调用我?
从后端应用的角度看,它并不知道前端浏览器的存在。它只知道:
- 有一个客户端(Nginx)向我的8080端口发起了请求
- 我处理这个请求并返回响应给这个客户端(Nginx)
- 至于这个客户端(Nginx)之后要做什么,我完全不关心
java
// 后端Spring应用(运行在8080端口)
@RestController
public class UserController {
@GetMapping("/api/users")
public List<User> getUsers() {
// 这个方法只知道:有人调用了 /api/users
// 它返回数据给调用者(Nginx),不知道最终是浏览器
return userService.findAll();
}
}
这种架构的优势
1. 解耦前后端
- 前端只需要与 Nginx(90端口) 通信
- 后端只需要处理来自 Nginx 的请求
- 双方技术栈可以独立演进
2. 负载均衡
nginx
# Nginx 可以将请求分发到多个后端实例
upstream backend_servers {
server localhost:8080 weight=3; # 权重3
server localhost:8081 weight=2; # 权重2
server localhost:8082 weight=1; # 权重1
}
location /api/ {
proxy_pass http://backend_servers/;
}
3. 安全性
- 后端服务(8080)可以不暴露给外网,只允许 Nginx 访问
- Nginx 可以作为安全屏障,过滤恶意请求
4. 性能优化
nginx
# Nginx 可以处理静态资源,减轻后端压力
location /static/ {
root /usr/share/nginx/html;
expires 30d; # 缓存30天
}
location /api/ {
proxy_pass http://localhost:8080/;
# 添加缓存
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
}
实际的数据流示例
让我们跟踪一个具体的请求:
步骤1:浏览器发起请求
前端浏览器 → http://example.com:90/api/users
步骤2:Nginx 接收并转发
nginx
# Nginx 配置匹配到 location /api/
location /api/ {
proxy_pass http://localhost:8080/; # 转发到8080端口
}
实际转发为:http://localhost:8080/api/users
步骤3:后端处理并响应
java
// Spring 后端收到请求,返回JSON数据
@GetMapping("/api/users")
public List<User> getUsers() {
return Arrays.asList(
new User(1, "Alice"),
new User(2, "Bob")
);
}
// 响应:HTTP 200 + JSON数据
步骤4:Nginx 返回给浏览器
Nginx 收到后端响应,原样返回给浏览器
浏览器显示结果,但地址栏显示的是90端口
解决常见的跨域问题
这种架构还天然解决了前后端分离中的跨域问题:
没有 Nginx 时的跨域问题:
前端: http://localhost:3000 (React开发服务器)
后端: http://localhost:8080 (SpringBoot)
浏览器阻止请求:不同端口被视为不同源,违反同源策略
使用 Nginx 反向代理解决:
浏览器访问: http://localhost:90
Nginx 服务静态文件(前端) + 代理API请求到8080端口
对浏览器来说,所有资源都来自同一个源(localhost:90),没有跨域问题
总结
Nginx 能够成功中转数据的核心原因是:
- 反向代理机制:Nginx 作为中间人,接收前端请求并转发给后端
- 配置规则 :通过
proxy_pass
指令定义转发规则 - 网络可达性:Nginx 和后端应用在同一台机器或同一网络内,可以互相访问
- 透明传输:Nginx 只是中转数据,不修改业务逻辑(除非特别配置)
这种架构是现代 Web 应用的标准做法,它提供了更好的安全性、可扩展性和维护性。正好验证了 Nginx 反向代理正在正常工作!