bun替代nginx,反向代理服务器

通过实现部分nginx功能,掌握bun的高级用法

  • Setp0: 确保bun已经正确安装
shell 复制代码
$ bun -v

1.3.4

如果还未安装,请到B站看下我的安装视频:bun win11 安装 bun linux 安装

  • Setp1: 建立bun脚手架项目bun-nginx
shell 复制代码
$ bun init bun-nginx

? Select a project template - Press return to submit.
❯ Blank
  React
  Library

✓ Select a project template: Blank

 + .gitignore
 + CLAUDE.md
 + .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc -> CLAUDE.md
 + index.ts
 + tsconfig.json (for editor autocomplete)
 + README.md
  • Setp2: 改成js项目(本文使用js示例)
  1. 将index.ts重命名为nginx-proxy.js
  2. package.json 的module 改为 nginx-proxy.js
js 复制代码
//类似nginx 配置upstream代理
const upstream = {
  "vip": {//模拟VIP业务
    "servers": [
      { "host": "192.168.1.10:3000", "weight": 1 },//算法 1/(1+2)
      { "host": "192.168.1.10:4000", "weight": 2 },
    ],
    "ip_hash": false//开启ip_hash,weight不生效
  },
  "normal": {//模拟普通业务
    "servers": [
      { "host": "192.168.1.2:8080", "weight": 1 },//算法 1/(1+2)
      { "host": "192.168.1.3:8080", "weight": 2 },
    ],
    "ip_hash": false//关闭ip_hash,weight生效
  }
}

// 按权重随机选择(带概率)
function randomSelectByProbability(list) {
  if (!Array.isArray(list) || list.length === 0) {
    throw new Error('Input must be a non-empty array');

  }
  // 可选:校验 probability 是否存在且有效
  const totalProb = list.reduce((sum, item) => sum + (item.probability || 0), 0);
  if (totalProb <= 0) {
    throw new Error('Total probability must be greater than 0');
  }
  // 计算随机数 rand,范围 [0, totalProb)
  const rand = Math.random() * totalProb; // [0, totalProb)
  let cumulative = 0;
  for (const item of list) {
    cumulative += item.probability;
    if (rand < cumulative) {
      return item;
    }
  }

  // 理论上不会走到这里(如果 totalProb > 0)
  return list[list.length - 1];
}

// 处理 CORS 预检请求
function handleCORS(request) {
  const origin = request.headers.get('Origin') || '*';
  // 允许的 CORS 头(所有响应都带)
  const corsHeaders = {
    'Access-Control-Allow-Origin': '*', // 注意:若需 credentials,不能用 '*',见下方说明
    'Access-Control-Allow-Credentials': 'true',
  };

  // 如果是 OPTIONS 预检请求
  if (request.method === 'OPTIONS') {
    return new Response(null, {
      status: 204,
      headers: {
        ...corsHeaders,
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers':
          'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range',
        'Access-Control-Max-Age': '1728000',
        'Content-Type': 'text/plain; charset=utf-8',
        'Content-Length': '0',
      },
    });

  }
  // 非 OPTIONS 请求:不返回 Response,由主逻辑处理
  return null;
}

// 代理请求
async function fetchProxy(url, method, msg, hdrs) {
  console.log(`Fetching=> ${url}`);
  const response = await fetch(url, {
    method: method,
    body: msg,
    headers: hdrs,
  });
  return response;
}

const server = Bun.serve({
  port: 80,
  idleTimeout: 10,

  reusePort: false,
  async fetch(req) {
    const corsResponse = handleCORS(req);

    if (corsResponse) {
      return corsResponse;
    }

    const urlObj = new URL(req.url);
    const path = urlObj.pathname;
    const queryParams = urlObj.searchParams;
    const fullPath = urlObj.pathname + urlObj.search;


    const method = req.method;
    const msg = req.body ? await req.text() : null;
    const hdrs = req.headers;

    // respond with text/html
    if (path === "/") return new Response("Welcome to Bun!");

    const upstream_name = fullPath.split("/")[1].split("?")[0];
    const upstream_config = upstream[upstream_name];
    if (!upstream_config) {
      return new Response("Upstream not found", { status: 404 });
    } else if (upstream_config.ip_hash) {
      // 使用ip_hash算法
      const client_ip = req.headers.get("x-forwarded-for") || req.headers.get("remote-addr");
      let index = 0;
      if (client_ip) {
        index = Bun.hash(client_ip) % upstream_config.servers.length;
      } else {
        index = Math.floor(Math.random() * upstream_config.servers.length);
      }
      const server = upstream_config.servers[index];
      console.log('fullPath', fullPath);
      const url = `http://${server.host}${fullPath}`;

      return await fetchProxy(url, method, msg, hdrs);
    } else {

      const serverConfig = upstream_config.servers;
      //没有配置weight的,默认weight设置为1
      serverConfig.forEach((server, index) => {
        if (!server.weight) {
          server.weight = 1;
        }
      });

      const total_weight = serverConfig.reduce((sum, server) => sum + server.weight, 0);

      serverConfig.forEach((server, index) => {
        server.probability = server.weight / total_weight;
      });

      const server = randomSelectByProbability(serverConfig);
      const url = `http://${server.host}${fullPath}`;
      console.log(`Proxying to ${url}`);
      return await fetchProxy(url, method, msg, hdrs);
    }

  },
});
  • Setp3: 编写模拟后台http服务程序,这个程序也可以用python,java等语言模拟
  • nginx-proxy-backend-server1.js
js 复制代码
import minimist from 'minimist';

function getPort() {

    // 🔥 关键:跳过 bun run 部分,只取脚本后的参数
    const args = minimist(process.argv.slice(2)); // ["-p", "3000"] 或 ["--port", "3000"]
    // 获取端口(支持 -p 和 --port)
    const portArg = args.p || args.port;
    if(!portArg){
        throw new Error('Port argument is required. Use -p or --port to specify the port.');
        process.exit(1);
    }
    
    const PORT = parseInt(portArg, 10); // 默认端口
    if (isNaN(PORT)) {
        console.error(`❌ Invalid port value: "${portArg}" is not a number`);
        process.exit(1); // 主动退出并报错
    }

    // 验证端口有效性
    if (PORT < 1 || PORT > 65535) {
        throw new Error('PORT less then 1 or greater than 65535');
        process.exit(1);
    }

    return PORT;
}

const PORT = getPort();

console.log(`Starting server on port ${PORT}...`);

async function handle(req, server) {
    const urlObj = new URL(req.url);
    const path = urlObj.pathname;
    const queryParams = urlObj.searchParams;
    const fullPath = urlObj.pathname + urlObj.search;
    console.log(`Handling request for ${fullPath}`);
    const body = req.body ? await req.text() : null;
    //用内置的response对象,自己json,可以给response加header包括Content-Type
    return new Response(body);
}

const server = Bun.serve({
    port: PORT,
    fetch(req, server) {
        const url = new URL(req.url);

        if (url.pathname === '/vip') {
            return handle(req, server);
        }

        // 匹配 /vip/123
        if (url.pathname.startsWith('/vip/')) {
            const id = url.pathname.split('/')[2]; // 获取 ID
            return handle(req, id);
        }

        return new Response("Not found", { status: 404 });
    }
});

console.log(`Listening on ${server.url}`);
  • Setp4: 启动反向代理服务
shell 复制代码
$ bun run nginx-proxy.js
  • Setp5: 启动后台服务
shell 复制代码
$ bun run nginx-proxy-backend-server1.js -p 3000
$ bun run nginx-proxy-backend-server1.js -p 4000
  • Setp5: 测试
    • 后台服务的ip和端口要和nginx-proxy.js的upstream的配置对应。
    • 浏览器输入 http://localhost:vip 会随机返回3000或4000。

您要是觉得我的文章有帮助,请点赞,收藏。感谢!

相关推荐
孟陬5 小时前
2025-12-11 之后前端 npm 如何发包 How to Publish NPM Package in Year 2025
npm·node.js·bun
至善迎风1 天前
Bun:下一代 JavaScript 运行时与工具链
开发语言·javascript·ecmascript·bun
该用户已不存在7 天前
Anthropic 收购 Bun:当 AI 巨头决定掌控底层代码基建
claude·bun
mCell8 天前
为什么在 Agent 时代,我选择了 Bun?
javascript·agent·bun
兔子零10249 天前
从 Bun 被收购说起:AI 为啥训练离不开 Python,上线却越来越需要 JavaScript?
python·bun
用户479492835691514 天前
Bun 卖身 Anthropic!尤雨溪神吐槽:OpenAI 你需要工具链吗?
前端·openai·bun
孟陬16 天前
我的 AI 工作流 —— project_rules.md 代码规范篇,让 AI 自省自动跑起来
react.js·node.js·bun
JohnYan1 个月前
Bun技术评估 - 30 SSE支持
javascript·后端·bun
孟陬1 个月前
20251108 Bun v1.3.2 更新:特殊场景下安装速度再次提升 x6 及安装策略兼容
deno·bun