【问题标题】:How do I pass through a file upload request from a Next.js API to another API?如何将文件上传请求从 Next.js API 传递到另一个 API?
【发布时间】:2022-11-15 01:49:42
【问题描述】:

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

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

客户端(Next.js)- fetch 使用 FormData 通过 POST

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
    }
}

注释掉的 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()
}

API 端点(外部)

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

一旦请求通过 API 路由并发送到外部 API,图像流就不再有效。我可以看到 IFormFile 对象已经拾取了 imageFile OK 并获得了相关数据。

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

通过 Next.js API 路由很重要,因为它处理将访问令牌传递给外部 API 并且不打算公开该 API。

我看过 Create upload files api in next.js,但我不确定 formidable 如何/是否适合这种情况?

【问题讨论】:

    标签: javascript node.js asp.net-core asp.net-web-api next.js


    【解决方案1】:

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

    • 我没有找到关于为什么图像数据损坏的解释,这意味着我不能使用原始的 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()
    }
    

    【讨论】:

    • 值得注意的是,Vercel 对 API 调用有 5mb 的负载限制。最后最终没有使用这个!
    • 我遇到了类似的问题:stackoverflow.com/q/72262003/1385183。但是谢谢你的解决方案。我让它工作了。仍然很高兴听到有人说为什么图像数据会损坏。是不是因为内容类型在转到 API 路由时发生了变化。或者这是一个 Next.js 问题?
    【解决方案2】:

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

    解决方案来源: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);
        })
    }
    

    /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'
        })
    }
    

    【讨论】:

      【解决方案3】:

      目前这段代码可以工作

      在 /pages/api/hello.ts

      import formidable from "formidable";
      import fs from "fs";
      
      export const config = {
       api: {
        bodyParser: false
       }
      };
      
      export default async function handler(req: any, res: any) {
        if (req.method === "POST") {
         const form = new formidable.IncomingForm();
      
            form.parse(req, async function (err, fields, files: any) {
             const data = fs.readFileSync(files.file.filepath);
             fs.writeFileSync(`./public/uploads/${Date.now().toString() + 
             files.file.originalFilename}`, data);
             fs.unlinkSync(files.file.filepath);
            });
      
          return res.status(200).json({massage: "Success"});    
         }
        };
      



      在 /pages/index.tsx

      import axios from 'axios';
      import { useState } from 'react';
      
      const api = axios.create();
      
      const uploadFile = async (file: File) => {
       let formData = new FormData();
       formData.set("file", file);
      
       await api.postForm("http://localhost:3000/api/hello", formData)
       .then(res => { console.log(res) }).catch((err) => { console.log(err) });
      }
      
      export default function Home() {
      const [file, setFile] = useState<File | null>(null);
      
      const HandelUpload = async () => {
        if (file === null) return alert("No file selected");
        await uploadFile(file)
      }
      
      return (
         <div>
          {/* @ts-ignore */}
          <input type='file' onChange={(event) => setFile(event?.target?.files[0] 
          || null)} />
          <button onClick={HandelUpload}>Upload</button>
        </div>
       )
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-22
        • 2022-07-28
        • 2016-05-12
        • 1970-01-01
        • 2020-12-14
        • 1970-01-01
        • 2018-05-02
        • 1970-01-01
        相关资源
        最近更新 更多