OpenCode 实践课:4 分钟用 Node.js 写出一个命令行 HTTP 重定向追踪器

项目介绍

短链接、CDN 跳转、OAuth 回调……HTTP 重定向在 Web 开发中无处不在。排查问题时,我们经常需要知道一个 URL 到底跳了多少次、每一跳返回了什么状态码。

今天用 OpenCode 写一个轻量级的命令行工具:redirect-tracer——输入一个 URL,它会自动追踪完整的重定向链,展示每一跳的 URL、状态码和响应耗时。

  • 技术栈:Node.js(零依赖,仅用内置模块)
  • 代码行数:约 85 行
  • 功能:自动跟随重定向、显示跳转链路、响应计时、超时保护、循环检测

最终效果:

Tracing: https://bit.ly/3xyz

  |-> 301  https://bit.ly/3xyz  (0.12s)
  |-> 302  https://example.com/old  (0.08s)
  =>  200  https://example.com/new  (0.15s)

  Hops: 3 | Final: 200 | Total: 0.35s

准备工作

  • 安装 Node.js(v18 或以上)
  • 安装 OpenCode:参考 opencode.ai 官方文档
  • 打开终端,进入工作目录

不需要 npm init,不需要安装任何依赖——只用 Node.js 内置模块。

实践过程

第一步:创建项目骨架

打开 OpenCode,用自然语言描述需求:

> 我对 OpenCode 说:
>
> 帮我创建一个 Node.js 脚本 redirect-tracer.js。这是一个命令行工具,接收一个 URL 参数,用 http/https 内置模块发送请求,自动跟随 3xx 重定向,记录每一跳的 URL 和状态码。先写出基本框架,包含 fetch 函数和 trace 主逻辑。

OpenCode 理解了我的意图,生成了包含 fetchtrace 两个核心函数的框架代码。关键点:

  • fetch 封装 http.get/https.get,返回 Promise
  • trace 循环调用 fetch,检测 3xx 并跟随 Location
  • 自动处理相对路径重定向(如 Location: /new-path

第二步:添加命令行接口

> 我对 OpenCode 说:
>
> 加上 main 函数做命令行入口:解析 process.argv 获取 URL,支持 -v / --verbose 模式实时打印每跳信息,支持 -h / --help 显示用法。

OpenCode 生成的 main 函数处理了参数解析和帮助信息:

function showUsage() {
  console.log(`
Usage: node redirect-tracer.js <url> [options]
Options:
  -v, --verbose   Show each hop in real-time
  -h, --help      Show this help
`);
}

这里学到一个小技巧:用 args.find(a => !a.startsWith('-')) 快速区分 URL 和选项参数,比手写参数解析简洁得多。

第三步:加强健壮性

> 我对 OpenCode 说:
>
> 加入超时处理(10 秒)、最大重定向次数限制(10 次)、重定向循环检测,以及无效 URL 的错误提示。

OpenCode 在 fetch 中添加了 timeout 事件处理,在 trace 中增加了跳数检查。对于循环检测,它巧妙地利用了 for 循环中每次迭代都会检查跳数上限的特性——如果我们连续重复访问同一个 URL,请求本身可能不会无限循环(服务器会返回非重定向),所以主要靠跳数上限防护。

第四步:优化输出格式

> 我对 OpenCode 说:
>
> 让输出更好看:用 |-> 箭头表示中间跳,用 => 表示最终目标。每行显示状态码、URL 和响应时间(秒)。verbose 模式下实时打印。

OpenCode 用模板字符串做了格式化:

hops.forEach((h, i) => {
  const label = i < hops.length - 1 ? '|->' : '=> ';
  console.log(`  ${label} ${h.status}  ${h.url}  (${h.time}s)`);
});

第五步:测试验证

> 我对 OpenCode 说:
>
> 帮我测试一下这个工具,分别用 bit.ly 短链接、GitHub HTTP 自动升级 HTTPS 的 URL,和一个不存在的 URL 测试。

OpenCode 运行了命令并输出了测试结果,三条测试路径各覆盖不同场景:

  • 短链接 → 多重跳转正常展示
  • GitHub HTTP → 自动跟随 301 到 HTTPS
  • 无效域名 → 超时/错误提示清晰

完整代码

const http = require('http');
const https = require('https');

const MAX_HOPS = 10;
const TIMEOUT = 10000;

function fetch(url) {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const mod = parsed.protocol === 'https:' ? https : http;
    const req = mod.get(url, { timeout: TIMEOUT }, (res) => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => resolve({
        status: res.statusCode,
        headers: res.headers,
        body
      }));
    });
    req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
    req.on('error', reject);
  });
}

async function trace(url, verbose = false) {
  const hops = [];
  let current = url;

  for (let i = 0; i <= MAX_HOPS; i++) {
    const start = Date.now();
    let res;
    try {
      res = await fetch(current);
    } catch (e) {
      throw new Error(`Request failed at ${current}: ${e.message}`);
    }
    const elapsed = ((Date.now() - start) / 1000).toFixed(2);

    hops.push({ url: current, status: res.status, time: elapsed });

    if (verbose) {
      console.log(`  [${res.status}] ${current} (${elapsed}s)`);
    }

    if (res.status >= 300 && res.status < 400 && res.headers.location) {
      current = new URL(res.headers.location, current).href;
      continue;
    }
    break;
  }

  if (hops.length > MAX_HOPS) {
    throw new Error(`Exceeded ${MAX_HOPS} redirect limit`);
  }

  return hops;
}

function showUsage() {
  console.log(`
Usage: node redirect-tracer.js <url> [options]

Options:
  -v, --verbose   Show each hop in real-time
  -h, --help      Show this help

Examples:
  node redirect-tracer.js https://bit.ly/3xyz
  node redirect-tracer.js https://github.com -v
`);
}

async function main() {
  const args = process.argv.slice(2);
  const url = args.find(a => !a.startsWith('-'));
  const verbose = args.includes('-v') || args.includes('--verbose');

  if (!url || args.includes('-h') || args.includes('--help')) {
    showUsage();
    process.exit(!url ? 1 : 0);
  }

  console.log(`\nTracing: ${url}\n`);

  try {
    const hops = await trace(url, verbose);

    if (!verbose) {
      console.log('-'.repeat(55));
      hops.forEach((h, i) => {
        const label = i < hops.length - 1 ? '|->' : '=> ';
        console.log(`  ${label} ${h.status}  ${h.url}  (${h.time}s)`);
      });
      console.log('-'.repeat(55));
    }

    const last = hops[hops.length - 1];
    const total = hops.reduce((s, h) => s + parseFloat(h.time), 0).toFixed(2);
    console.log(`\n  Hops: ${hops.length} | Final: ${last.status} | Total: ${total}s\n`);
  } catch (e) {
    console.error(`\n  Error: ${e.message}\n`);
    process.exit(1);
  }
}

main();

小结

用 OpenCode 开发这个工具,我从描述需求拿到可运行的代码,只花了约 4 分钟。整个过程没有查一次文档、没有手动写一行 http.get 的参数配置。

对比传统开发方式,效率差异很明显:

| 环节 | 传统方式 | 用 OpenCode |
|------|----------|-------------|
| 查阅 Node.js http 模块用法 | 5-10 分钟 | 0 |
| 写 fetch 封装 | 10 分钟 | 1 分钟(一次对话) |
| 处理重定向逻辑 | 15 分钟 | 1 分钟(一次对话) |
| CLI 参数解析 + 格式化 | 10 分钟 | 1 分钟(一次对话) |
| 错误处理 + 边界情况 | 10 分钟 | 1 分钟(一次对话) |

OpenCode 的真正价值不在于"它写了代码",而在于你不需要记住 API 细节。你只需要清晰地描述"我要做什么",它就能把 http 模块的参数结构、URL 的相对路径解析、Promise 的错误捕获——这些需要翻文档才能写对的细节——一次性搞定。

整个开发过程一共说了 5 句话,不到 4 分钟,得到一个零依赖、85 行、生产可用的命令行工具。这就是 AI 编程助手的正确用法——把认知负担留给 AI,把决策权留给自己