Nginx 反向代理学习:单端口统一访问多服务

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

启动成功后,可通过 服务1服务2 验证服务可用性。

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转发。

相关推荐
LFly_ice4 小时前
学习React-24-路由传参
前端·学习·react.js
陈天伟教授5 小时前
基于学习的人工智能(3)机器学习基本框架
人工智能·学习·机器学习·知识图谱
毕设源码-钟学长5 小时前
【开题答辩全过程】以 高校课程学习评价系统设计与实现为例,包含答辩的问题和答案
学习
fruge7 小时前
从第三方库中偷师:学习 Lodash 的函数封装技巧
学习
lingggggaaaa9 小时前
免杀对抗——C2远控篇&C&C++&DLL注入&过内存核晶&镂空新增&白加黑链&签名程序劫持
c语言·c++·学习·安全·网络安全·免杀对抗
陈天伟教授10 小时前
基于学习的人工智能(5)机器学习基本框架
人工智能·学习·机器学习
我先去打把游戏先10 小时前
ESP32学习笔记(基于IDF):基于OneNet的ESP32的OTA功能
笔记·物联网·学习·云计算·iphone·aws
初願致夕霞10 小时前
学习笔记——基础hash思想及其简单C++实现
笔记·学习·哈希算法
小女孩真可爱10 小时前
大模型学习记录(五)-------调用大模型API接口
pytorch·深度学习·学习