Discord REST API在代理环境下失败但WebSocket正常 - Discord REST API Fails with Proxy Despite WebSocket Success
`channels.discord.proxy` 配置仅适用于 WebSocket 网关连接,不适用于出站 REST API 调用。这导致在受限区域发送消息时出现获取失败。
🔍 症状
主要症状
Discord 机器人可以成功接收消息,但无法发送消息,并抛出 TypeError: fetch failed 错误。
技术表现
日志输出模式: log [2026-02-26 10:23:45] INFO: Discord gateway connected via proxy ws://127.0.0.1:7890/ [2026-02-26 10:23:52] INFO: Received message from user123 [2026-02-26 10:23:52] ERROR: discord final reply failed: TypeError: fetch failed [2026-02-26 10:23:52] ERROR: at fetch (node:internal/deps:node_fetch:index.js:…) at async RequestClient.request (…)
CLI 诊断命令
bash
Verify Discord connection status
openclaw debug channels
Test proxy connectivity
curl -x http://127.0.0.1:7890/ https://discord.com/api/v10/gateway
Check if REST calls bypass proxy
node -e " const fetch = require(’node-fetch’); fetch(‘https://discord.com/api/v10/gateway', { agent: new (require(‘http-proxy-agent’))(‘http://127.0.0.1:7890’) }).then(r => console.log(‘Proxy works:’, r.status)).catch(e => console.error(‘Failed:’, e.message)); "
配置看起来正确但实际失败
yaml channels: discord: enabled: true token: “Bot xxx…” proxy: “http://127.0.0.1:7890/” # WebSocket uses this # No REST proxy override available
🧠 根因分析
架构问题:双重网络路径
OpenClaw 的 Discord 频道实现使用了两种不同的网络路径:
| 操作类型 | 网络路径 | 代理支持 |
|---|---|---|
| Gateway 连接 (WebSocket) | discord.io WebSocket 客户端 | ✅ 支持 channels.discord.proxy |
| REST API 调用(发送消息、上传附件等) | carbon-request RequestClient | ❌ 不支持代理 |
代码流程分析
用户发送消息到 Discord ↓ OpenClaw 通过 WebSocket 接收(✓ 已代理) ↓ 机器人处理并需要回复 ↓ 调用 RequestClient.sendMessage() ↓ fetch() 执行时未使用代理 ↓ TypeError: fetch failed(连接被拒绝/超时)
涉及的具体文件
根本原因在于 Discord 频道处理器中调用 carbon-request / RequestClient 的方式:
javascript
// In discord-channel.js (simplified)
async function sendReply(message) {
const client = new RequestClient(); // No agent passed
return client.post({
url: https://discord.com/api/v10/channels/${channelId}/messages,
body: { content: message }
}); // Direct connection, no proxy
}
channels.discord.proxy 设置仅被 WebSocket 初始化代码路径使用,REST 请求处理器从未使用它。
为什么接收可以正常工作
WebSocket 帧处理正确传递了代理代理: javascript const ws = new WebSocket(url, { agent: new HttpsProxyAgent(proxyUrl) // ✅ Proxy applied });
为什么发送会失败
REST 调用创建了直接连接: javascript // carbon-request internal (simplified) function request(options) { return fetch(options.url, { // No agent configuration for proxy }); }
🛠️ 逐步修复
方法 1:环境变量覆盖(临时解决方案 — 立即生效)
在启动 OpenClaw 之前设置全局代理环境变量:
bash
Linux/macOS
export HTTP_PROXY=“http://127.0.0.1:7890/” export HTTPS_PROXY=“http://127.0.0.1:7890/” export http_proxy=“http://127.0.0.1:7890/” export https_proxy=“http://127.0.0.1:7890/”
Then start OpenClaw
openclaw start
修复前后对比:
| 设置 | 修复前 | 修复后 |
|---|---|---|
channels.discord.proxy | 仅 WebSocket | 仅 WebSocket |
| 环境变量 | 未设置 | 所有流量走代理 |
方法 2:代码级别修复(永久方案 — 需要补丁)
创建一个为所有 HTTP 请求应用代理的包装模块:
步骤 1: 安装所需依赖 bash npm install https-proxy-agent –save
步骤 2: 创建支持代理的 RequestClient 包装器 javascript // File: ~/.openclaw/plugins/proxy-request-wrapper.js
const { RequestClient } = require(‘carbon-request’); const { HttpsProxyAgent } = require(‘https-proxy-agent’);
class ProxyRequestClient extends RequestClient { constructor(proxyUrl) { super(); this.proxyUrl = proxyUrl; this.agent = new HttpsProxyAgent(proxyUrl); }
request(options) { return super.request({ …options, agent: this.agent, // Force HTTPS for Discord API protocol: ‘https:’ }); }
get(options) { return super.get({ …options, agent: this.agent }); }
post(options) { return super.post({ …options, agent: this.agent }); } }
module.exports = { ProxyRequestClient };
步骤 3: 修补 Discord 频道以使用包装器 javascript // In discord-channel.js, modify initialization: // Find: const client = new RequestClient(); // Replace with: const proxyUrl = config.get(‘channels.discord.proxy’) || process.env.HTTPS_PROXY; const client = proxyUrl ? new ProxyRequestClient(proxyUrl) : new RequestClient();
方法 3:用支持代理的客户端替换 carbon-request
步骤 1: 安装支持代理的 node-fetch bash npm install node-fetch@2 https-proxy-agent@5 –save
步骤 2: 在 Discord 频道中替换 RequestClient 的使用 javascript // Replace all RequestClient imports with: const fetch = require(’node-fetch’); const { HttpsProxyAgent } = require(‘https-proxy-agent’);
// In sendMessage function: async function sendMessage(channelId, content) { const proxyUrl = process.env.HTTPS_PROXY || config.channels?.discord?.proxy;
const options = {
method: ‘POST’,
headers: {
‘Authorization’: Bot ${config.channels.discord.token},
‘Content-Type’: ‘application/json’
},
body: JSON.stringify({ content })
};
if (proxyUrl) { options.agent = new HttpsProxyAgent(proxyUrl); }
const response = await fetch(
https://discord.com/api/v10/channels/${channelId}/messages,
options
);
return response.json(); }
🧪 验证
测试 1:验证 WebSocket 代理(应该已经正常工作)
bash openclaw debug –channel discord –verbose 2>&1 | grep -i proxy
预期输出: log [INFO] Discord gateway connecting via proxy: http://127.0.0.1:7890/ [INFO] Discord gateway connected (WebSocket)
测试 2:验证 REST 代理(实际修复)
javascript // Save as test-discord-rest.js const { HttpsProxyAgent } = require(‘https-proxy-agent’);
async function testProxyRequest() { const proxyUrl = ‘http://127.0.0.1:7890’;
// Test without proxy (should fail in restricted region) try { await fetch(‘https://discord.com/api/v10/gateway'); console.log(’✗ Direct connection succeeded (unexpected)’); } catch (e) { console.log(’✓ Direct connection blocked as expected:’, e.message); }
// Test with proxy (should succeed) try { const response = await fetch(‘https://discord.com/api/v10/gateway', { agent: new HttpsProxyAgent(proxyUrl) }); console.log(’✓ Proxy connection succeeded, status:’, response.status); } catch (e) { console.log(’✗ Proxy connection failed:’, e.message); } }
testProxyRequest();
运行命令: bash node test-discord-rest.js
测试 3:端到端 Discord 消息发送
bash
Start OpenClaw with proxy
HTTP_PROXY=“http://127.0.0.1:7890” HTTPS_PROXY=“http://127.0.0.1:7890” openclaw start
In another terminal, send test message via Discord
Send “!test” to your bot
Check logs
tail -f ~/.openclaw/logs/openclaw.log | grep -E “(discord|message|proxy|error)”
预期成功模式: log [INFO] Discord gateway connected via proxy [INFO] Received: !test [INFO] Sending reply via REST (proxied) [INFO] Reply sent successfully (200 OK)
测试 4:验证不再出现 fetch failed 错误
bash
After fix, this should not appear
grep -r “fetch failed” ~/.openclaw/logs/
预期结果: 无匹配(空结果)
⚠️ 常见陷阱
陷阱 1:混合使用 HTTP/HTTPS 代理协议
Discord API 要求使用 HTTPS。确保您的代理配置使用 HTTPS:
| ❌ 错误 | ✅ 正确 |
|---|---|
Gateway 使用 http://127.0.0.1:7890/ | 两者都使用(node 会处理升级) |
| SOCKS 代理未配置 agent | 明确安装 socks-proxy-agent |
javascript // For SOCKS proxies: const { SocksProxyAgent } = require(‘socks-proxy-agent’); const agent = new SocksProxyAgent(‘socks://127.0.0.1:1080’);
陷阱 2:环境变量作用域问题
进程启动后设置的环境变量不会生效。始终在启动前设置:
bash
✗ Wrong - vars not inherited
openclaw start && export HTTP_PROXY="…"
✓ Correct - vars set before fork
HTTP_PROXY="…" openclaw start
陷阱 3:Node.js 版本兼容性
https-proxy-agent@5+ 需要 Node.js 18+。对于旧版本:
bash node –version
If < 18, use: npm install https-proxy-agent@4
陷阱 4:代理身份验证未处理
如果您的代理需要用户名/密码:
javascript const proxyUrl = ‘http://user:pass@127.0.0.1:7890/’; // or const proxyUrl = ‘http://’ + encodeURIComponent(‘user’) + ‘:’ + encodeURIComponent(‘pass’) + ‘@127.0.0.1:7890/’;
陷阱 5:TLS 证书错误
在某些代理配置中,您可能需要禁用 SSL 验证以进行测试:
javascript process.env.NODE_TLS_REJECT_UNAUTHORIZED = ‘0’;
⚠️ 警告: 仅用于调试,生产环境中切勿使用。
陷阱 6:Docker 容器代理隔离
如果在 Docker 中运行 OpenClaw,代理必须从容器内部可访问:
bash
✗ Container can’t reach host loopback
HTTP_PROXY=“http://127.0.0.1:7890”
✓ Use host network or docker.for.mac.localhost
HTTP_PROXY=“http://host.docker.internal:7890”
对于 Docker,添加 --network=host 或在 docker-compose 中配置 network_mode: host。
🔗 相关错误
逻辑上相关的错误模式
| 错误 | 描述 | 相关性 |
|---|---|---|
TypeError: fetch failed | REST 调用连接失败 | 此问题 |
ECONNREFUSED | 代理服务器未运行 | 网络/代理可用性 |
ETIMEDOUT | 连接到 Discord 超时 | 网络/防火墙阻止 |
ENOTFOUND | 无法解析 Discord DNS | 受限区域的 DNS 过滤 |
Failed to connect to Discord gateway | WebSocket 初始化失败 | 代理未应用到 WS 客户端 |
Disconnected with code 1006 | WebSocket 异常关闭 | 网络不稳定/代理断开 |
Request timeout | REST 请求挂起 | 代理慢/不稳定 |
历史背景
- Issue #1423:为 Discord 网关添加了 WebSocket 代理支持
- Issue #1891:媒体/附件下载绕过代理
- Issue #2156:carbon-request 客户端缺少 agent 注入 API
其他频道中的类似模式
| 频道 | 存在 REST 代理问题 | 解决方案 |
|---|---|---|
| Discord | ✅ 是(此问题) | 环境变量 / 代码补丁 |
| Slack | ⚠️ 部分 | 使用带 agent 的 node-fetch |
| Teams | ❌ 未知 | 需要测试 |
| Mattermost | ❌ 未知 | 需要测试 |