从Next.js API路由请求带Oak中间件的Deno服务器遇连接拒绝问题
问题背景与现象
我尝试从Next.js的API路由请求Deno服务器,为简化测试,使用Oak中间件编写了如下Deno服务器:
import { Application } from 'https://deno.land/x/oak/mod.ts' import { oakCors } from 'https://deno.land/x/cors/mod.ts' const app = new Application() app.use( oakCors({ origin: 'http://localhost:3000', credentials: true }) ) app.use((ctx) => { ctx.response.body = 'Hello World!' }) await app.listen({ port: 3001 });
已配置oakCors允许来自Next.js应用(运行在http://localhost:3000)的跨域请求。接着在Next.js的/pages/api/test.js中实现对Deno服务器的请求:
export default async function handler(req, res) { try { const data = await fetch('http://localhost:3001') console.log(data) } catch (e) { console.log(e) } res.json({ done: true }) }
页面渲染两个按钮用于测试:
<button onClick={() => fetch('http://localhost:3001')}>direct fetch</button> <button onClick={() => fetch('api/test')}>fetch by api routes</button>
测试现象:
- 点击第一个按钮直接请求Deno服务器,请求正常返回
- 点击第二个按钮通过Next.js API路由间接请求,Next.js服务器控制台出现连接拒绝错误:
error - unhandledRejection: TypeError: fetch failed at Object.fetch (node:internal/deps/undici/undici:14062:11) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) { cause: Error: connect ECONNREFUSED 127.0.0.1:3001 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1487:16) { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 3001 } }
补充说明:若为http://localhost:3001开启ngrok隧道,两种请求方式均能通过生成的隧道URL正常工作。
原因分析
核心差异在于请求的执行环境:
- 直接点击按钮的请求是从浏览器客户端发起的,此时
localhost:3001指向本地运行的Deno服务器,请求能正常到达。 - 通过API路由的请求是在**Next.js服务端(Node.js进程)**执行的:
- 如果Next.js运行在容器化环境(如Docker)中,
localhost对于容器内的进程来说,指向的是容器自身而非宿主机的Deno服务器,因此会出现连接拒绝。 - 即使是本地开发,部分情况下Next.js服务端进程的网络命名空间与本地主机不共享,也会导致
localhost:3001无法解析到Deno服务器。
- 如果Next.js运行在容器化环境(如Docker)中,
- ngrok隧道是公网可访问的地址,客户端和服务端都能正确解析到Deno服务器的公网入口,因此两种方式都能正常工作。
修复方案
1. 使用本地主机IP替代localhost
将API路由中的请求地址从http://localhost:3001替换为你的本地主机IP(如192.168.x.x),确保服务端能直接定位到运行Deno服务器的机器:
export default async function handler(req, res) { try { // 替换为你的实际本地IP const data = await fetch('http://192.168.1.100:3001') console.log(data) } catch (e) { console.log(e) } res.json({ done: true }) }
2. 容器化场景使用主机别名
如果使用Docker运行Next.js,可使用Docker默认提供的host.docker.internal作为宿主机别名,替换localhost:
export default async function handler(req, res) { try { const data = await fetch('http://host.docker.internal:3001') console.log(data) } catch (e) { console.log(e) } res.json({ done: true }) }
3. 环境变量统一管理
将Deno服务器地址存入环境变量,避免硬编码,适配不同运行环境:
- 在Next.js项目根目录创建
.env.local文件:
DENO_SERVER_URL=http://192.168.1.100:3001
- 修改API路由代码读取环境变量:
export default async function handler(req, res) { try { const data = await fetch(process.env.DENO_SERVER_URL) console.log(data) } catch (e) { console.log(e) } res.json({ done: true }) }
内容的提问来源于stack exchange,提问作者olegzhermal
相关产品推荐
相关产品推荐

