JavaScript >> Javascript 文檔 >  >> Tags >> API

如何使用文件讀取器 API 將文件上傳到 Amazon S3

如何在瀏覽器中使用 FileReader API 將文件作為 base64 字符串讀入內存並使用 aws-sdk 將其上傳到 Amazon S3 來自 NPM 的庫。

開始使用

對於本教程,我們將需要一個後端和一個前端。我們的後端將用於與 Amazon S3 通信,而前端將為我們提供一個用戶界面,我們可以在其中上傳文件。

為了加快速度,我們將在後端使用 CheatCode 的 Node.js Boilerplate,在前端使用 CheatCode 的 Next.js Boilerplate。要獲得這些設置,我們需要從 Github 克隆它們。

我們將從後端開始:

終端

git clone https://github.com/cheatcode/nodejs-server-boilerplate.git server

克隆後,cd 進入項目並安裝其依賴項:

終端

cd server && npm install

接下來,我們需要安裝一個額外的依賴,aws-sdk

終端

npm i aws-sdk

安裝完所有依賴項後,使用以下命令啟動服務器:

終端

npm run dev

隨著您的服務器運行,在另一個終端窗口或選項卡中,我們需要克隆前端:

終端

git clone https://github.com/cheatcode/nextjs-boilerplate.git client

克隆後,cd 進入項目並安裝其依賴項:

終端

cd client && npm install

安裝完所有依賴項後,使用以下命令啟動前端:

終端

npm run dev

有了這個,我們就可以開始了。

增加 body-parser 限制

查看我們的服務器代碼,我們需要做的第一件事是修改body-parser的上傳限制 樣板中的中間件。顧名思義,這個中間件負責解析發送到服務器(Express.js 服務器)的 HTTP 請求的原始正文數據。

/server/middleware/bodyParser.js

import bodyParser from "body-parser";

export default (req, res, next) => {
  const contentType = req.headers["content-type"];

  if (contentType && contentType === "application/x-www-form-urlencoded") {
    return bodyParser.urlencoded({ extended: true })(req, res, next);
  }

  return bodyParser.json({ limit: "50mb" })(req, res, next);
};

在 Express.js 中,中間件是指在 HTTP 請求最初到達服務器和傳遞到匹配的路徑/路由(如果存在)之間運行的代碼。

上面,我們導出的函數是一個 Express.js 中間件函數,它是 CheatCode Node.js 樣板的一部分。這個函數接收來自 Express.js 的 HTTP 請求——我們可以確定這是一個由 Express 通過 req 傳遞給我們的請求 , res , 和 next Express 傳遞給其路由回調的參數——然後將該請求從 body-parser 傳遞給適當的方法 樣板文件中包含依賴項。

這裡的想法是我們要使用來自 bodyParser 的適當“轉換器” 以確保我們從 HTTP 請求中獲得的原始正文數據在我們的應用程序中可用。

對於本教程,我們將從瀏覽器發送 JSON 格式的數據。因此,我們可以預期我們發送的任何請求(文件上傳)都會被移交給 bodyParser.json() 方法。上面,我們可以看到我們傳入了一個具有 limit 屬性的對象 設置為 50mb .這繞過了默認的 limit 100kb 在庫強加的 HTTP 請求正文上。

因為我們要上傳不同大小的文件,所以我們需要增加這個值,這樣我們就不會在上傳時收到任何錯誤。在這裡,我們使用 50 兆字節的“最佳猜測”作為我們將收到的最大正文大小。

添加 Express.js 路由

接下來,我們需要添加一個發送上傳的路由。就像我們上面暗示的那樣,我們在樣板文件中使用了 Express.js。為了保持我們的代碼井井有條,我們將不同的路由組分開,這些路由通過從主 index.js 調用的函數訪問 在 /server/index.js 中啟動 Express 服務器的文件 .

在那裡,我們調用一個函數 api() 它為樣板加載與 API 相關的路由。

/server/api/index.js

import graphql from "./graphql/server";
import s3 from "./s3";

export default (app) => {
  graphql(app);
  s3(app);
};

在該文件中,在對 graphql() 的調用下方 ,我們要添加另一個對函數 s3() 的調用 接下來我們將創建它。這裡,app 表示我們將添加路由的 Express.js 應用程序實例。讓我們創建 s3() 立即運行。

/server/api/s3/index.js

import uploadToS3 from "./uploadToS3";

export default (app) => {
  app.use("/uploads/s3", async (req, res) => {
    await uploadToS3({
      bucket: "cheatcode-tutorials",
      acl: "public-read",
      key: req.body?.key,
      data: req.body?.data,
      contentType: req.body?.contentType,
    });

    res.send("Uploaded to S3!");
  });
};

在這裡,我們採用 Express app 我們傳入並調用 .use() 的實例 方法,傳遞我們希望路由可用的路徑,/uploads/s3 .在路由的回調內部,我們調用了一個函數 uploadToS3 我們將在下一節中定義。

需要注意的是:我們打算 uploadToS3 返回一個 JavaScript Promise。這就是為什麼我們有 await 方法前面的關鍵字。當我們執行上傳時,我們希望在響應我們從客戶端發送的原始 HTTP 請求之前“等待”待解決的 Promise。為了確保這也有效,我們為關鍵字 async 添加了前綴 在我們路線的回調函數上。沒有這個,JavaScript 會拋出一個關於 await 的錯誤 是這段代碼運行時的保留關鍵字。

讓我們跳到那個 uploadToS3 立即運行,看看如何將我們的文件移交給 AWS。

將上傳連接到服務器上的 Amazon S3

現在是重要的部分。為了讓我們上傳到 Amazon S3,我們需要建立一個到 AWS 的連接和一個 .S3() 的實例 aws-sdk 中的方法 我們之前安裝的庫。

/server/api/s3/uploadToS3.js

import AWS from "aws-sdk";
import settings from "../../lib/settings";

AWS.config = new AWS.Config({
  accessKeyId: settings?.aws?.akid,
  secretAccessKey: settings?.aws?.sak,
  region: "us-east-1",
});

const s3 = new AWS.S3();

export default async (options = {}) => { ... };

在我們進入函數體之前,首先,我們需要連接一個 AWS 實例。更具體地說,我們需要傳入一個 AWS 訪問密鑰 ID 和秘密訪問密鑰。這對做兩件事:

  1. 向 AWS 驗證我們的請求。
  2. 驗證這對對我們嘗試執行的操作具有正確的權限(在本例中為 s3.putObject() )。

獲取這些密鑰超出了本教程的範圍,但請閱讀 Amazon Web Services 的此文檔以了解如何設置它們。

假設您已經獲得了您的密鑰——或者有一個可以使用的現有密鑰對——接下來,我們將利用 CheatCode Node.js 樣板中的設置實現來安全地存儲我們的密鑰。

/server/settings-development.json

{
  "authentication": {
    "token": "abcdefghijklmnopqrstuvwxyz1234567890"
  },
  "aws": {
    "akid": "Type your Access Key ID here...",
    "sak":" "Type your Secret Access Key here..."
  },
  [...]
}

/server/settings-development.json 內部 ,上面,我們添加了一個新對象aws ,將其設置為具有兩個屬性的另一個對象:

  • akid - 這將設置為您從 AWS 獲得的訪問密鑰 ID。
  • sak - 這將設置為您從 AWS 獲得的秘密訪問密鑰。

/server/lib/settings.js 內部 ,該文件在服務器啟動時自動加載到內存中。你會注意到這個文件叫做 settings-development.json . -development 部分告訴我們這個文件只會在 process.env.NODE_ENV 時被加載 (當前 Node.js 環境)等於 development .同樣,在生產中,我們會創建一個單獨的文件 settings-production.json .

這樣做的重點是安全性並避免在開發環境中使用您的生產密鑰。單獨的文件可避免不必要的洩漏和密鑰混合。

/server/api/s3/uploadToS3.js

import AWS from "aws-sdk";
import settings from "../../lib/settings";

AWS.config = new AWS.Config({
  accessKeyId: settings?.aws?.akid,
  secretAccessKey: settings?.aws?.sak,
  region: "us-east-1",
});

const s3 = new AWS.S3();

export default async (options = {}) => { ... };

回到我們的 uploadToS3.js 文件,接下來,我們導入 settings 我們上面提到的 /server/lib/settings.js 文件 然後,我們獲取 aws.akidaws.sak 我們剛剛設置的值。

最後,在深入研究函數定義之前,我們創建一個 S3 的新實例 類,將其存儲在 s3 new AWS.S3() 的變量 .有了這個,讓我們進入函數的核心:

/server/api/s3/uploadToS3.js

import AWS from "aws-sdk";

[...]

const s3 = new AWS.S3();

export default async (options = {}) => {
  await s3
    .putObject({
      Bucket: options.bucket,
      ACL: options.acl || "public-read",
      Key: options.key,
      Body: Buffer.from(options.data, "base64"),
      ContentType: options.contentType,
    })
    .promise();

  return {
    url: `https://${options.bucket}.s3.amazonaws.com/${options.key}`,
    name: options.key,
    type: options.contentType || "application/",
  };
};

它沒有太多內容,所以我們在這裡記錄了所有內容。我們將在 s3 上調用的核心函數 實例是 .putObject() .到 .putObject() ,我們傳遞一個帶有一些設置的選項對象:

  • Bucket - 您要在其中存儲您上傳的對象(文件的 S3 術語)的 Amazon S3 存儲桶。
  • ACL - 您想用於文件權限的“訪問控制列表”。這告訴 AWS 誰可以訪問該文件。您可以在此處傳遞亞馬遜提供的任何罐頭 ACL(我們使用的是 public-read 授予開放訪問權限)。
  • Key - 將存在於 Amazon S3 存儲桶中的文件的名稱。
  • Body - 您正在上傳的文件的內容。
  • ContentType - 您上傳的文件的 MIME 類型。

專注於Body ,我們可以看到一些獨特的事情正在發生。在這裡,我們調用 Buffer.from() Node.js 內置的方法。稍後我們會看到,當我們從瀏覽器中的 FileReader 取回文件時,它將被格式化為 base64 字符串。

為了確保 AWS 可以解釋我們發送的數據,我們需要將從客戶端傳遞過來的字符串轉換為 Buffer。在這裡,我們傳遞了我們的 options.data —base64 字符串 — 作為第一個參數,然後是 base64 作為 let Buffer.from() 的第二個參數 知道轉換字符串所需的編碼。

有了這個,我們就有了發送到亞馬遜所需的東西。為了使我們的代碼更具可讀性,在這裡,我們鏈接 .promise() 方法添加到我們對 s3.putObject() 的調用的末尾 .這告訴 aws-sdk 我們希望它返回一個 JavaScript Promise。

就像我們在路由回調中看到的一樣,我們需要添加 async 為我們的函數添加關鍵字,以便我們可以利用 await 關鍵字來“等待”來自 Amazon S3 的響應。從技術上講,我們不需要 等待 S3 響應(我們可以省略 async/await 在這裡)但是在本教程中這樣做將幫助我們驗證上傳是否完成(當我們前往客戶端時會詳細介紹)。

上傳完成後,我們從函數中返回一個描述 url 的對象 , name , 和 type 我們剛剛上傳的文件。在這裡,請注意 url 格式化為文件的 URL,因為它存在於您的 Amazon S3 存儲桶中。

這樣,我們就完成了服務器。讓我們跳到客戶端連接我們的上傳接口並讓它工作。

在客戶端連接 FileReader API

因為我們在客戶端使用 Next.js,我們將創建一個新的 upload /pages 中的頁面 將包含我們上傳代碼的示例組件的目錄:

/client/pages/upload/index.js

import React, { useState } from "react";
import pong from "../../lib/pong";

const Upload = () => {
  const [uploading, setUploading] = useState(false);

  const handleUpload = (uploadEvent) => { ... };

  return (
    <div>
      <header className="page-header">
        <h4>Upload a File</h4>
      </header>
      <form className="mb-3">
        <label className="form-label">File to Upload</label>
        <input
          disabled={uploading}
          type="file"
          className="form-control"
          onChange={handleUpload}
        />
      </form>
      {uploading && <p>Uploading your file to S3...</p>}
    </div>
  );
};

Upload.propTypes = {};

export default Upload;

首先,我們設置了一個帶有足夠標記的 React 組件來為我們提供一個基本的用戶界面。對於樣式,我們依賴於在樣板文件中自動為我們設置的 Bootstrap。

這裡重要的部分是 <input type="file" /> 這是我們將附加 FileReader 的文件輸入 實例到。當我們使用這個選擇一個文件時,onChange 函數將被調用,傳遞包含我們選擇的文件的 DOM 事件。在這裡,我們定義了一個新函數 handleUpload 我們將用於此活動。

/client/pages/upload/index.js

import React, { useState } from "react";
import pong from "../../lib/pong";

const Upload = () => {
  const [uploading, setUploading] = useState(false);

  const handleUpload = (uploadEvent) => {
    uploadEvent.persist();
    setUploading(true);

    const [file] = uploadEvent.target.files;
    const reader = new FileReader();

    reader.onloadend = (onLoadEndEvent) => {
      fetch("http://localhost:5001/uploads/s3", {
        method: "POST",
        mode: "cors",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          key: file.name,
          data: onLoadEndEvent.target.result.split(",")[1],
          contentType: file.type,
        }),
      })
        .then(() => {
          setUploading(false);
          pong.success("File uploaded!");
          uploadEvent.target.value = "";
        })
        .catch((error) => {
          setUploading(false);
          pong.danger(error.message || error.reason || error);
          uploadEvent.target.value = "";
        });
    };

    reader.readAsDataURL(file);
  };

  return (
    <div>
      <header className="page-header">
        <h4>Upload a File</h4>
      </header>
      <form className="mb-3">
        <label className="form-label">File to Upload</label>
        <input
          disabled={uploading}
          type="file"
          className="form-control"
          onChange={handleUpload}
        />
      </form>
      {uploading && <p>Uploading your file to S3...</p>}
    </div>
  );
};

Upload.propTypes = {};

export default Upload;

填寫handleUpload 函數,我們有幾件事要做。首先,在函數體內部,我們添加對 React 的 .persist() 的調用 uploadEvent 上的方法 (這是通過 onChange 傳入的 DOM 事件 <input /> 上的方法 )。我們需要這樣做,因為 React 創建了一種稱為合成事件的東西,它是 not 在主執行線程之外的函數內部可用(稍後會詳細介紹)。

在此之後,我們使用 useState() 從 React 掛鉤以創建狀態變量 uploading 並將其切換為 true .如果您向下看我們的標記,您會看到我們在上傳過程中使用它來禁用文件輸入,並顯示一條反饋消息以確認該過程正在進行中。

在此之後,我們深入研究核心功能。首先,我們需要從瀏覽器中獲取我們選擇的文件。為此,我們調用 uploadEvent.target.files 並使用 JavaScript 數組解構來“提取”文件數組中的第一個文件並將其分配給變量 file .

接下來,我們創建 FileReader() 的實例 在瀏覽器中。這是現代瀏覽器內置的,因此無需導入任何內容。

作為響應,我們返回一個 reader 實例。跳過 reader.onloadend 片刻,在我們的 handleUpload 底部 函數,我們調用了 reader.readAsDataURL() , 傳入 file 我們剛剛從 uploadEvent.target.files 解構 大批。這一行負責告訴文件閱讀器我們希望將文件讀入內存的格式。在這裡,一個數據 URL 讓我們返回如下內容:

示例 Base64 字符串

data:text/plain;base64,4oCcVGhlcmXigJlzIG5vIHJvb20gZm9yIHN1YnRsZXR5IG9uIHRoZSBpbnRlcm5ldC7igJ0g4oCUIEdlb3JnZSBIb3R6

雖然它可能看起來不像,但這個字符串能夠表示文件的全部內容。當我們的 reader 已將我們的文件完全加載到內存中,reader.onloadend 函數 event 被調用,傳入 onloadevent 對像作為參數。通過這個事件對象,我們可以訪問代表我們文件內容的數據 URL。

在此之前,我們設置了對 fetch() 的調用 ,傳入我們在服務器上上傳路由的假定 URL(當你運行 npm run dev 在樣板文件中,它在端口 5001 上運行服務器 )。在 fetch() 的選項對像中 我們確保設置 HTTP methodPOST 這樣我們就可以連同我們的請求一起發送一個正文。

我們還要確保設置模式 cors 為 true 以便我們的請求使其通過服務器上的 CORS 中間件(這限制了哪些 URL 可以訪問服務器 - 這是預先配置為在 Next.js 樣板和 Node.js 樣板之間為您工作)。在此之後,我們還設置了 Content-Type header 是一個標準的 HTTP 標頭,它告訴我們的服務器我們的 POST 採用什麼格式 身體在裡面。記住,這是不是 和我們的文件類型一樣。

body 字段,我們調用 JSON.stringify()fetch() 要求我們將 body 作為字符串而不是對像傳遞 - 為此,傳遞一個對象,其中包含我們在服務器上將文件上傳到 S3 所需的數據。

這裡,key 設置為 file.name 以確保我們放入 S3 存儲桶中的文件與從我們的計算機中選擇的文件的名稱相同。 contentType 設置為瀏覽器文件對像中自動提供給我們的 MIME 類型(例如,如果我們打開了 .png 文件這將被設置為 image/png )。

這裡重要的部分是 data .請注意,我們正在使用 onLoadEndEvent 就像我們在上面暗示的那樣。這包含我們文件的內容作為其 target.result 中的 base64 字符串 場地。這裡,調用 .split(',') 最後是說“把它分成兩塊,第一塊是關於base64字符串的元數據,第二塊是實際的base64字符串。”

我們需要這樣做,因為只有數據 URL 中逗號後面的部分(參見上面的示例)是實際的 base64 字符串。如果我們 把它拿出來,Amazon S3 將存儲我們的文件,但是當我們打開它時,它將無法讀取。為了完成這一行,我們使用數組括號表示法來表示“給我們數組中的第二項(位置 1 在一個從零開始的 JavaScript 數組中)。”

有了這個,我們的請求被發送到服務器。最後,我們添加一個 .then() 回調—fetch 向我們返回一個 JavaScript Promise——它確認上傳成功並“重置”我們的 UI。我們setUploading()false ,清除<input /> ,然後使用 pong Next.js 樣板中內置的警報庫可在屏幕上顯示消息。

如果出現故障,我們會做同樣的事情,但是會提供錯誤消息(如果可用)而不是成功消息。

如果一切都按計劃進行,我們應該會看到如下內容:

總結

在本教程中,我們學習瞭如何在瀏覽器中使用 FileReader API 將文件上傳到 Amazon S3。我們學習瞭如何通過 aws-sdk 建立與 Amazon S3 的連接 ,以及如何創建可以從客戶端調用的 HTTP 路由。

在瀏覽器中,我們學習瞭如何使用FileReader API 將我們的文件轉換為 Base64 字符串,然後使用 fetch() 將我們的文件傳遞給我們創建的 HTTP 路由。


Tutorial JavaScript 教程
  1. 在 Chrome 中工作,但在 Safari 中中斷:無效的正則表達式:無效的組說明符名稱 /(?<=/)([^#]+)(?=#*)/

  2. 用 Javascript 構建康威生命遊戲

  3. 什麼是 JavaScript 循環?

  4. CSS 概念 - 您需要的唯一指南

  5. ES2022 JavaScript 特性

  6. 純 HTTP 的力量——屏幕共享、實時消息傳遞、SSH 和 VNC

  7. 編程範式

  1. Axios 中的 `create()` 函數

  2. 通過數據的異步內聯腳本:URI

  3. getConnectedNodes 方向參數

  4. 使用 Url-Tab 可共享打開時的活動選項卡

  5. Next.js 設置 | ESLint、Jest、React 測試庫和絕對導入

  6. Flatiron 第四個項目:Vanilla JS 和 Ruby on Rails API

  7. 編寫/運行服務器是什麼意思?回答我自己關於 NodeJS 的問題

  1. 第 91 天:登陸頁面設計和實施。

  2. 了不起的蓋茨比😎

  3. CSS 模塊和反應

  4. Angular 中的反應狀態:介紹 Angular 效果