使用Nextjs从GCS上传文件使用签名URL时出现403错误。

huangapple go评论58阅读模式
英文:

File uploading to GCS using Signed URL from Nextjs - error code 403

问题

以下是要翻译的内容:

"The purpose is -
-> to generate signed url in api/fileupload.js file to upload the file to GCS.
-> Obtain the signed url from Nextjs server via nextjs API - localhost://3000/api/fileupload
-> Uploading file to gcs using the generated signed url in index.jsx file

Signed URL is generated successfully. But while uploading the image body as form data to GCS, an error of 403 code is occurred.
Here is the response body.

body : (...)
bodyUsed : false
headers : 
Headers {}
ok : false
redirected : false
status : 0
statusText : ""
type : "opaque"
url : ""

Is the way to uploading file as formdata correct in index.jsx file?
or what am I missing here?

The two files are given below -

index.jsx for nextjs file -

    
import { useState } from "react";

export default function Home() {
const 
= useState(""); const [file, setFile] = useState<any>(null); const [dataloaded, setDataloaded] = useState(true); const handleSubmit = async (e: any) => { setDataloaded(false); e.preventDefault(); let formData = new FormData(); formData.append("file", file.data); formData.append("Content-Type", `${file.data.type}`); console.log(file); const response = await fetch("http://localhost:3000/api/fileupload", { method: "POST", body: formData }); const responseWithBody = await response.json(); console.log(responseWithBody); setDataloaded(true); if (response.status === 200) { setUrl(responseWithBody.url); } else { console.log("error in generating url"); } const response1 = await fetch( responseWithBody.url, { mode: "no-cors", method: "POST", body: formData, headers: { "Access-Control-Allow-Origin": "*", "content-type": "image/png" } } ); console.log(response1); };

const handleFileChange = (e: any) => {
const img = {
preview: URL.createObjectURL(e.target.files[0]),
data: e.target.files[0]
};
setFile(img);
};

return (
<>
<div className="form-container">
<form onSubmit={handleSubmit}>
<div className="image-preview-container">
{file ? (
<img src={file.preview} alt="File to upload" />
) : (
<img
src="https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg"
alt="Fallback"
/>
)}
</div>
<div className="file-name">
{file && file.data.name}
{url && (
<a href={url} target="_blank" rel="noreferrer">
FiExternalLink
</a>
)}
</div>
<input
type="file"
name="file"
onChange={handleFileChange}
className="custom-file-input"
></input>
<button
className="submit-button"
type="submit"
disabled={!file}
onClick={handleSubmit}
>
Submit
</button>
</form>
</div>
</>
);
}


fileupload.js in api/ folder -

import { Storage } from "@google-cloud/storage";
import multer from "multer";
import type { NextApiRequest, NextApiResponse } from "next";

const storage = new Storage({
keyFilename: service_account_key.json,
projectId: "my-project-id"
});
const bucketName = "my-bucket-name";

async function parseFormData(
req: NextApiRequest & { files?: any },
res: NextApiResponse
) {
const storage = multer.memoryStorage();
const multerUpload = multer({ storage });
const multerFiles = multerUpload.any();
await new Promise((resolve, reject) => {
multerFiles(req as any, res as any, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
return {
fields: req.body,
files: req.files
};
}

export default async function handler(
req: NextApiRequest & { files?: any },
res: NextApiResponse<any>
) {
const options = {
version: "v4",
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: "application/octet-stream"
} as any;

const result = await parseFormData(req, res);
// console.log(result);
const file = storage
.bucket(bucketName)
.file(result?.files[0]?.originalname || "new-file.png");
const

: any = await file.getSignedUrl(options);

console.log("Generated PUT signed URL:");
console.log(url);

res.status(200).json({ url: url });
}


<details>
<summary>英文:</summary>

The purpose is - 
-&gt; to generate signed url in api/fileupload.js file to upload the file to GCS.
-&gt; Obtain the signed url from Nextjs server via nextjs API - localhost://3000/api/fileupload
-&gt; Uploading file to gcs using the generated signed url in index.jsx file

Signed URL is generated successfully. But while uploading the image body as form data to GCS, an error of 403 code is occured.
Here is the response body.

body : (...)
bodyUsed : false
headers :
Headers {}
ok : false
redirected : false
status : 0
statusText : ""
type : "opaque"
url : ""

Is the way to uploading file as formdata correct in index.jsx file? 
or what am I missing here?

The two files are given below - 

index.jsx for nextjs file - 
import { useState } from &quot;react&quot;;

export default function Home() {
const 
= useState(&quot;&quot;); const [file, setFile] = useState&lt;any&gt;(null); const [dataloaded, setDataloaded] = useState(true); const handleSubmit = async (e: any) =&gt; { setDataloaded(false); e.preventDefault(); let formData = new FormData(); formData.append(&quot;file&quot;, file.data); formData.append(&quot;Content-Type&quot;, `${file.data.type}`); console.log(file); const response = await fetch(&quot;http://localhost:3000/api/fileupload&quot;, { method: &quot;POST&quot;, body: formData }); const responseWithBody = await response.json(); console.log(responseWithBody); setDataloaded(true); if (response.status === 200) { setUrl(responseWithBody.url); } else { console.log(&quot;error in generating url&quot;); } const response1 = await fetch( responseWithBody.url, { mode: &quot;no-cors&quot;, method: &quot;POST&quot;, body: formData, headers: { &quot;Access-Control-Allow-Origin&quot;: &quot;*&quot;, &quot;content-type&quot;: &quot;image/png&quot; } } ); console.log(response1);

};

const handleFileChange = (e: any) => {
const img = {
preview: URL.createObjectURL(e.target.files[0]),
data: e.target.files[0]
};
setFile(img);
};

 return (
    &lt;&gt;
     &lt;div className=&quot;form-container&quot;&gt;
       &lt;form onSubmit={handleSubmit}&gt;
         &lt;div className=&quot;image-preview-container&quot;&gt;
           {file ? (
             &lt;img src={file.preview} alt=&quot;File to upload&quot; /&gt;
           ) : (
             &lt;img
           src=&quot;https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg&quot;
            alt=&quot;Fallback&quot;
          /&gt;
        )}
      &lt;/div&gt;
      &lt;div className=&quot;file-name&quot;&gt;
        {file &amp;&amp; file.data.name}
        {url &amp;&amp; (
          &lt;a href={url} target=&quot;_blank&quot; rel=&quot;noreferrer&quot;&gt;
            FiExternalLink
          &lt;/a&gt;
        )}
      &lt;/div&gt;
      &lt;input
        type=&quot;file&quot;
        name=&quot;file&quot;
        onChange={handleFileChange}
        className=&quot;custom-file-input&quot;
      &gt;&lt;/input&gt;
      &lt;button
        className=&quot;submit-button&quot;
        type=&quot;submit&quot;
        disabled={!file}
        onClick={handleSubmit}
      &gt;
        Submit
      &lt;/button&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/&gt;
);
}

fileupload.js in api/ folder - 

import { Storage } from "@google-cloud/storage";
import multer from "multer";
import type { NextApiRequest, NextApiResponse } from "next";

const storage = new Storage({
keyFilename: service_account_key.json,
projectId: "my-project-id"
});
const bucketName = "my-bucket-name";

async function parseFormData(
req: NextApiRequest & { files?: any },
res: NextApiResponse
) {
const storage = multer.memoryStorage();
const multerUpload = multer({ storage });
const multerFiles = multerUpload.any();
await new Promise((resolve, reject) => {
multerFiles(req as any, res as any, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
return {
fields: req.body,
files: req.files
};
}

export default async function handler(
req: NextApiRequest & { files?: any },
res: NextApiResponse<any>
) {
const options = {
version: "v4",
action: "write",
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: "application/octet-stream"
} as any;

const result = await parseFormData(req, res);
// console.log(result);
const file = storage
.bucket(bucketName)
.file(result?.files[0]?.originalname || "new-file.png");
const

: any = await file.getSignedUrl(options);

console.log("Generated PUT signed URL:");
console.log(url);

res.status(200).json({ url: url });
}


</details>


# 答案1
**得分**: 1

以下是代码的中文翻译:

在使用 Next.js GET API 生成签名 URL 之前,我想要发送原始文件的名称,并通过 Next.js GET API 接收该签名 URL。

以下是解决方案代码 -

在 api/fileupload.ts 文件中
```typescript
import { Storage } from "@google-cloud/storage";
import type { NextApiRequest, NextApiResponse } from "next";
const storage = new Storage({
  keyFilename: `service_account_key.json`,
  projectId: "my-project-id"
});
const bucketName = "bucket-name";

export default async function handler(
  req: NextApiRequest & { files?: any },
  res: NextApiResponse<any>
) {
  const options = {
    version: "v4",
    action: "write",
    expires: Date.now() + 15 * 60 * 1000 // 15 分钟
    // contentType: "application/octet-stream"
  } as any;

  const newFileName = req.query.name as string;
  const file = storage.bucket(bucketName).file(newFileName);
  const 
: any = await file.getSignedUrl(options); console.log("生成的 PUT 签名 URL:", url); res.status(200).json({ url: url }); }

通过 Next.js GET API 获得签名 URL,并在标头中包含来自事件目标对象的数据和实际内容类型后,调用 PUT API。

index.jsx 文件 -

import { useState } from "react";
import axios from "axios";
import Image from "next/image";
import Link from "next/link";
import { FiExternalLink } from "react-icons/fi";
import Loader from "./Loader";
export default function Home() {
  const [url, setUrl] = useState("");
  const [file, setFile] = useState<any>(null);
  const [dataloaded, setDataloaded] = useState(true);
  const [fileUploadDone, setFileUploadDone] = useState(false);
  
  const handleSubmit = async (e: any) => {
    setDataloaded(false);
    e.preventDefault();
    const response = await fetch(`/api/fileupload?name=${file.data.name}`, {
      method: "GET"
    });
    const responseWithBody = await response.json();
    console.log(responseWithBody.url);

    if (response.status === 200) {
      setUrl(responseWithBody.url);
    } else {
      console.log("生成 URL 时出错");
    }
    const response1 = await axios.put(responseWithBody.url, file.data, {
      headers: {
        "Content-Type": `${file.data.type}`
      }
    });
    if (response1.status === 200) {
      setFileUploadDone(true);
    } else {
    }
    setDataloaded(true);
    console.log(response1, file.data.type);
  };
  const handleFileChange = (e: any) => {
    const img = {
      preview: URL.createObjectURL(e.target.files[0]),
      data: e.target.files[0]
    };
    setFile(img);
  };

  return (
    <>
      <div className="form-container">
        <form onSubmit={handleSubmit}>
          <div className="image-preview-container">
            {file ? (
              <Image
                width={"400"}
                height={"400"}
                src={file.preview}
                alt="要上传的文件"
              />
            ) : (
              <Image
                width={"400"}
                height={"400"}
                src="https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg"
                alt="替代图片"
              />
            )}
          </div>
          <div className="file-name">
            {file && file.data.name}
          </div>
          <input
            type="file"
            name="file"
            onChange={handleFileChange}
            className="custom-file-input"
          ></input>
          <button
            className="submit-button"
            type="submit"
            disabled={!file}
            onClick={handleSubmit}
          >
            提交
          </button>
          {fileUploadDone && (
            <span style={{ marginTop: "20px" }}>
              文件上传成功
              <span
                onClick={() => {
                  setFileUploadDone(false);
                  setFile(null);
                  setDataloaded(true);
                }}
              >
                点击重新上传
              </span>
            </span>
          )}
        </form>
      </div>
      {!dataloaded && <Loader />}
    </>
  );
}

希望这可以帮助您理解代码。

英文:

I wanted to send the name of the original file on which a signed URL will be generated and receive that signed URL using Nextjs GET API.

Here is the solution code -
in api/fileupload.ts

import { Storage } from &quot;@google-cloud/storage&quot;;
import type { NextApiRequest, NextApiResponse } from &quot;next&quot;;
const storage = new Storage({
keyFilename: `service_account_key.json`,
projectId: &quot;my-project-id&quot;
});
const bucketName = &quot;bucket-name&quot;;
export default async function handler(
req: NextApiRequest &amp; { files?: any },
res: NextApiResponse&lt;any&gt;
) {
const options = {
version: &quot;v4&quot;,
action: &quot;write&quot;,
expires: Date.now() + 15 * 60 * 1000 // 15 minutes
// contentType: &quot;application/octet-stream&quot;
} as any;
const newFileName = req.query.name as string;
const file = storage.bucket(bucketName).file(newFileName);
const 
: any = await file.getSignedUrl(options); console.log(&quot;Generated PUT signed URL:&quot;, url); res.status(200).json({ url: url }); }

Through Nextjs GET API signed URL is obtained and a PUT API is called with a signed URL, data saved from the target object of the event, and actual content type in the header.

index.jsx file -

import { useState } from &quot;react&quot;;
import axios from &quot;axios&quot;;
import Image from &quot;next/image&quot;;
import Link from &quot;next/link&quot;;
import { FiExternalLink } from &quot;react-icons/fi&quot;;
import Loader from &quot;./Loader&quot;;
export default function Home() {
const 
= useState(&quot;&quot;); const [file, setFile] = useState&lt;any&gt;(null); const [dataloaded, setDataloaded] = useState(true); const [fileUploadDone, setFileUploadDone] = useState(false); const handleSubmit = async (e: any) =&gt; { setDataloaded(false); e.preventDefault(); const response = await fetch(`/api/fileupload?name=${file.data.name}`, { method: &quot;GET&quot; }); const responseWithBody = await response.json(); console.log(responseWithBody.url); if (response.status === 200) { setUrl(responseWithBody.url); } else { console.log(&quot;error in generating url&quot;); } const response1 = await axios.put(responseWithBody.url, file.data, { headers: { &quot;Content-Type&quot;: `${file.data.type}` } }); if (response1.status === 200) { setFileUploadDone(true); } else { } setDataloaded(true); console.log(response1, file.data.type); }; const handleFileChange = (e: any) =&gt; { const img = { preview: URL.createObjectURL(e.target.files[0]), data: e.target.files[0] }; setFile(img); }; return ( &lt;&gt; &lt;div className=&quot;form-container&quot;&gt; &lt;form onSubmit={handleSubmit}&gt; &lt;div className=&quot;image-preview-container&quot;&gt; {file ? ( &lt;Image width={&quot;400&quot;} height={&quot;400&quot;} src={file.preview} alt=&quot;File to upload&quot; /&gt; ) : ( &lt;Image width={&quot;400&quot;} height={&quot;400&quot;} src=&quot;https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg&quot; alt=&quot;Fallback&quot; /&gt; )} &lt;/div&gt; &lt;div className=&quot;file-name&quot;&gt; {file &amp;&amp; file.data.name} &lt;/div&gt; &lt;input type=&quot;file&quot; name=&quot;file&quot; onChange={handleFileChange} className=&quot;custom-file-input&quot; &gt;&lt;/input&gt; &lt;button className=&quot;submit-button&quot; type=&quot;submit&quot; disabled={!file} onClick={handleSubmit} &gt; Submit &lt;/button&gt; {fileUploadDone &amp;&amp; ( &lt;span style={{ marginTop: &quot;20px&quot; }}&gt; File upload is done successfully.{&quot; &quot;} &lt;span onClick={() =&gt; { setFileUploadDone(false); setFile(null); setDataloaded(true); }} &gt; Click to Upload Again &lt;/span&gt; &lt;/span&gt; )} &lt;/form&gt; &lt;/div&gt; {!dataloaded &amp;&amp; &lt;Loader /&gt;} &lt;/&gt; ); }

huangapple
  • 本文由 发表于 2023年5月29日 14:14:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76355040.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定