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

de90aj5v  于 2022-11-22  发布在  Node.js
关注(0)|答案(4)|浏览(235)

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

客户端(Next.js)--〉API路由(Next.js)--〉API端点(外部)
客户端(Next.js)-通过POST使用FormData

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

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

API Route(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核心Web应用编程接口
  • 请求应为multipart/form-data
  • 请求应包含imageFile中的图像并Map到IFormFile

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

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

通过Next.js API路由是很重要的,因为它处理将访问令牌传递到外部API的过程,而不是公开该API。
我已经看过Create upload files api in next.js,但我不确定formidable是否适合这种情况?

5lhxktic

5lhxktic1#

经过大量的挖掘和经历了许多不同的方法,我终于找到了一个工作。

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

cclgggtu2#

我遇到了同样的问题,最后我找到了一个很优雅的方法来解决它。
解决方案来源: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/上载.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'
    })
}
kcugc4gi

kcugc4gi3#

我也遇到了同样的问题。我使用nextjs api作为另一个api服务器的代理,在我的例子中,我只是传递了整个请求而不是主体,指定nextjs不解析请求。我唯一需要包含的是原始请求的头部。

import Repository, { apiUrl } from "~/repositories/Repository";
import { parse } from 'cookie';

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

export default async (req, res) => {
    const { headers: { cookie, ...rest }, method } = req;
    const { auth } = parse(cookie);
    if (method === 'PUT') {
        const headers = { 
            Authorization: `Bearer ${auth}`, 
            'content-type': rest['content-type'], 
            'content-length': rest['content-length']
        }
        return Repository.put(`${apiUrl}/user/me/files`, req, { headers: headers })
            .then(response => {
                const { data } = response;
                return res.status(200).json(data);
            })
            .catch(error => {
                const { response: { status, data } } = error;
                return res.status(status).json(data);
            })
    }
    else
        return res.status(405);
}
h43kikqp

h43kikqp4#

在当前时间内此代码将工作

在/pages/api/upload.ts中找到###

import formidable from "formidable";
import fs from "fs";

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

interface IFileStream {
 filepath: string;
 originalFilename: string;
}

const ProcessFiles = (Files: any): IFileStream[] => {
 const data: IFileStream[] = [];
 let index = 0;

 while (Boolean(Files[`file${index}`])) {
  data.push(Files[`file${index}`] as IFileStream)
  index++;
 }

  return data;
 }

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) {
    const filesArray = ProcessFiles(files)

    if (filesArray.length === 0) return 
    res.sendStatus(401).json({massages: "No File Found"});
  
    // create 'uploads' folder if not exist
    fs.mkdirSync("./public/uploads", {recursive: true});

    for (let file of filesArray) {
      const data = fs.readFileSync(file.filepath);
      fs.writeFileSync(`./public/uploads/${Date.now().toString() + 
      file.originalFilename}`, data);
      fs.unlinkSync(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 (files: File[]) => {
   let formData = new FormData();

   for (let i = 0; i < files.length; i++) {
    formData.set(`file${i}`, files[i]);
   }

   await api.postForm("http://localhost:3000/api/upload", formData)
   .then(res => { console.log(res) }).catch((err) => { 
   console.log(err) }) }

   export default function Home() {
   const [files, setFiles] = useState<File[] | null>([]);

   const HandelUpload = async () => {
    if (files?.length === 0 || files === null) return alert("No 
    file selected");
   await uploadFile(files)
  }

  return (
    <div>
      <input type='file' multiple onChange={(event) => 
      setFiles((event?.target?.files || null) as File[] | null)} />
      <button onClick={HandelUpload}>Upload</button>
   </div>
   )
  }

相关问题