You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13 App Router用http-proxy报错:req.on is not a function

解决Next.js 13 App Router中http-proxy代理外部API的问题

问题背景

在Next.js 13 App Router中尝试用路由处理器实现外部API代理,使用http-proxy时出现错误req.on is not a function。原因是http-proxy期望接收Node.js原生的IncomingMessage类型请求,而App Router的路由处理器传入的是NextRequest,两者不兼容。同时需要在服务端为请求追加accessToken,无法使用Next.js重写方案。

用户原代码:

import { authOptions } from "@/modules/auth/utils/auth";
import httpProxy from "http-proxy";
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
const ProxyHandler = async (req: NextRequest, res: NextResponse) => {
  const session = await getServerSession(authOptions);
  return new Promise((resolve, reject) => {
    const proxy: httpProxy = httpProxy.createProxy();

    proxy.once("proxyRes", resolve).once("error", reject).web(req, res, {
      changeOrigin: true,
      target: process.env.NEXT_PUBLIC_API_URL,
    });
  });
};
export const config = {
  api: {
    bodyParser: false,
    externalResolver: true,
  },
};
export {
  ProxyHandler as GET,
  ProxyHandler as POST,
  ProxyHandler as PUT,
  ProxyHandler as DELETE,
  ProxyHandler as PATCH,
};

可行解决方案

方案一:将NextRequest转换为Node.js原生IncomingMessage适配http-proxy

通过Node.js的http模块,把NextRequest转换为IncomingMessage,同时处理响应的转换,还可在代理配置中追加accessToken到请求头:

import { authOptions } from "@/modules/auth/utils/auth";
import httpProxy from "http-proxy";
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { IncomingMessage, ServerResponse } from "node:http";
import { Readable } from "node:stream";

const ProxyHandler = async (req: NextRequest) => {
  const session = await getServerSession(authOptions);
  if (!session?.user?.accessToken) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  return new Promise<NextResponse>((resolve, reject) => {
    const proxy = httpProxy.createProxy();

    // 将NextRequest转换为IncomingMessage
    const nodeReq = new IncomingMessage(Readable.from(req.body || []));
    nodeReq.method = req.method;
    nodeReq.url = req.nextUrl.pathname + req.nextUrl.search;
    nodeReq.headers = { ...req.headers };
    // 追加accessToken到请求头
    nodeReq.headers["authorization"] = `Bearer ${session.user.accessToken}`;

    // 创建临时ServerResponse来捕获代理响应
    let responseBody = Buffer.from("");
    const nodeRes = new ServerResponse(nodeReq);
    nodeRes.write = (chunk) => {
      responseBody = Buffer.concat([responseBody, chunk]);
      return true;
    };
    nodeRes.end = () => {
      const response = new NextResponse(responseBody, {
        status: nodeRes.statusCode,
        headers: nodeRes.getHeaders() as HeadersInit,
      });
      resolve(response);
    };

    proxy.web(nodeReq, nodeRes, {
      changeOrigin: true,
      target: process.env.NEXT_PUBLIC_API_URL,
    });

    proxy.once("error", (err) => {
      reject(new NextResponse(JSON.stringify({ error: err.message }), { status: 500 }));
    });
  });
};

export { ProxyHandler as GET, ProxyHandler as POST, ProxyHandler as PUT, ProxyHandler as DELETE, ProxyHandler as PATCH };

方案二:使用Fetch直接转发请求(更适配App Router)

放弃http-proxy,直接用fetch转发请求,这种方式贴合Next.js 13 App Router的设计,无需类型转换,可轻松追加accessToken:

import { authOptions } from "@/modules/auth/utils/auth";
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";

const proxyHandler = async (req: NextRequest) => {
  const session = await getServerSession(authOptions);
  if (!session?.user?.accessToken) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const targetUrl = new URL(req.nextUrl.pathname + req.nextUrl.search, process.env.NEXT_PUBLIC_API_URL);
  
  // 构建请求选项,复制原请求的方法、body、headers,追加accessToken
  const requestOptions: RequestInit = {
    method: req.method,
    headers: new Headers(req.headers),
    body: req.method !== "GET" && req.method !== "HEAD" ? await req.text() : undefined,
    redirect: "manual",
  };
  // 替换或添加Authorization头
  requestOptions.headers.set("authorization", `Bearer ${session.user.accessToken}`);
  // 移除Next.js特定的头,避免干扰外部API
  requestOptions.headers.delete("host");

  try {
    const response = await fetch(targetUrl.toString(), requestOptions);
    return new NextResponse(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
    });
  } catch (error) {
    return NextResponse.json({ error: "Proxy request failed" }, { status: 500 });
  }
};

export { proxyHandler as GET, proxyHandler as POST, proxyHandler as PUT, proxyHandler as DELETE, proxyHandler as PATCH };

方案说明

  • 方案二更推荐,完全适配App Router的路由处理器模型,代码简洁,无需复杂类型转换,调试维护更方便。
  • 两种方案均实现了服务端追加accessToken的需求,确保令牌不会暴露给客户端。

内容的提问来源于stack exchange,提问作者MajdNaji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 10:22:25