如何将文件上传请求从 Next.js API 传递到另一个 API?

Jas*_*ert 15 javascript node.js asp.net-web-api asp.net-core next.js

我正在尝试在 Next.js 应用程序中裁剪图像,将其发送到应用程序中的 API 路由,最后发送到应用程序外部的 API 端点。如果我绕过 API 路由,它可以正常工作,但在通过它时就不行了。图像数据不再正确且无法处理。

客户端 (Next.js) --> API 路由 (Next.js) --> API 端点(外部)

客户端 (Next.js) -fetch使用FormDataviaPOST

async function handleSave(image: Blob) {
    const file = new File([image], 'avatar.png', { type: 'image/png' })

    const data  = new FormData()
    data.append('imageFile', file)

    const response = await fetch(`/api/users/${userId}/media/avatar`,
        {
            body: data,
            method: 'POST',
        }
    )
    
    // const response = await fetch (`https://localhost:1337/user/${userId}/media/avatar`, {
    //     method: 'POST',
    //     headers: {
    //         "Authorization": `Bearer <JWT token>`,
    //     },
    //     body: data
    // })

    if (response.ok) {
        // handle
    }
}
Run Code Online (Sandbox Code Playgroud)

注释掉的 fetch 是我测试直接调用外部 API 端点的地方,这工作正常。

API 路由 (Next.js) - 从客户端获取请求并将其转发到外部 API 端点。

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    await cors(req, res)

    const { userId } = req.query;
    const { accessToken } = await getToken({ req, secret });

    const response = await fetch(`${process.env.API_HOST}/user/${userId}/media/avatar`, {
        method: 'POST',
        headers: {
            "Authorization": `Bearer ${accessToken}`,
            "Content-Type": req.headers["content-type"]
        },
        body: req.body
    })

    try {
        const json = await response.json();

        console.log(json)
    }
    finally { }

    res.end()
}
Run Code Online (Sandbox Code Playgroud)

API 端点(外部)

  • ASP.Net Core Web API
  • 请求应该是multipart/form-data
  • 请求应包含 中的图像imageFile并映射到IFormFile

一旦请求通过 API 路由传递并发送到外部 API,图像流就不再有效。我可以看到该IFormFile对象已选择imageFile“确定”并获取了相关数据。

在此输入图像描述

当我绕过 Next.js API 路由时,上传工作正常,并且我注意到IFormFile对象长度要小得多。

在此输入图像描述

通过 Next.js API 路由非常重要,因为它负责将访问令牌传递给外部 API,并且无意公开该 API。

我已经查看了在 next.js 中创建上传文件 api,但我不确定如何/是否formidable适合这种情况?

Jas*_*ert 9

经过大量挖掘并尝试了许多不同的方法后,我终于找到了一种有效的方法。

  • 我没有找到关于图像数据损坏的原因的解释,这意味着我无法使用原始multipart/form-data文件。
  • 用于formidable读取文件,首先保存到磁盘,然后fs读取该文件,最后在FormData.
  • 我不知道这的性能如何,而且我发现先将图像保存到磁盘而不是仅保存在内存中会很不舒服。
  • 需要再次检查一遍并确保其免受攻击向量的影响,例如文件大小
import Cors from 'cors'
import initMiddleware from '../../../../../lib/initMiddleware'
import { NextApiRequest, NextApiResponse } from 'next'
import { getToken } from 'next-auth/jwt'
import fetch from "node-fetch";
import FormData from 'form-data'
import { IncomingForm } from 'formidable'
import { promises as fs } from 'fs';

export const config = {
    api: {
        bodyParser: false,
    },
}

const cors = initMiddleware(
    Cors({
        methods: ['POST']
    })
)

const secret = process.env.NEXTAUTH_SECRET;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    await cors(req, res)

    const { userId } = req.query;
    const { accessToken } = await getToken({ req, secret });

    const fData = await new Promise<{ fields: any, files: any }>((resolve, reject) => {
        const form = new IncomingForm({
            multiples: false
        })
        form.parse(req, (err, fields, files) => {
            if (err) return reject(err)
            resolve({ fields, files })
        })
    });

    const imageFile = fData.files.imageFile
    const tempImagePath = imageFile?.filepath

    try {
        const image = await fs.readFile(tempImagePath)

        const data = new FormData()
        data.append('imageFile', image, { filename: 'avatar.png' })

        const response = await fetch(`${process.env.API_HOST}/user/${userId}/media/avatar`, {
            method: 'POST',
            headers: {
                "Authorization": `Bearer ${accessToken}`,
                "Content-Type": `multipart/form-data; boundary=${data.getBoundary()}`
            },
            body: data
        })

        if (!response.ok) {
            
        }

        res.status(response.status);
    }
    catch (error) {

    }
    finally {
        if (tempImagePath) {
            await fs.rm(tempImagePath)
        }
    }

    res.end()
}
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,Vercel 对 API 调用的负载限制为 5mb。最后还是没用这个! (2认同)

小智 5

我遇到了同样的问题,最后我找到了非常优雅的方法来解决它。

解决方案来源:https://github.com/vercel/next.js/discussions/15727#discussioncomment-1094126

以防万一,我从源代码复制代码:

文件上传组件

const uploadFile = (file : File) => {
    let url = "http://localhost:3000/api/upload";

    let formData = new FormData();
    formData.set("testFile", file)

    fetch(url, {
        method: "POST",
        body: formData,
    }).then(r => {
        console.log(r);
    })
}
Run Code Online (Sandbox Code Playgroud)

/api/upload.ts:

import {NextApiRequest, NextApiResponse} from "next";
import httpProxyMiddleware from "next-http-proxy-middleware";

// For preventing header corruption, specifically Content-Length header
export const config = {
    api: {
        bodyParser: false,
    },
}

export default (req: NextApiRequest, res: NextApiResponse) => {
    httpProxyMiddleware(req, res, {
        target: 'http://localhost:21942'
    })
}
Run Code Online (Sandbox Code Playgroud)