英文:
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
console.log("Generated PUT signed URL:");
console.log(url);
res.status(200).json({ url: url });
}
<details>
<summary>英文:</summary>
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 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 "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
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 "@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 minutes
// 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("Generated PUT signed URL:", 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 "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 = 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("error in generating 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="File to upload"
/>
) : (
<Image
width={"400"}
height={"400"}
src="https://raw.githubusercontent.com/koehlersimon/fallback/master/Resources/Public/Images/placeholder.jpg"
alt="Fallback"
/>
)}
</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}
>
Submit
</button>
{fileUploadDone && (
<span style={{ marginTop: "20px" }}>
File upload is done successfully.{" "}
<span
onClick={() => {
setFileUploadDone(false);
setFile(null);
setDataloaded(true);
}}
>
Click to Upload Again
</span>
</span>
)}
</form>
</div>
{!dataloaded && <Loader />}
</>
);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论