繁體中文

開發者工具 · Base64 編碼器和解碼器

JSON API 中的 Base64:為什麼二進位欄位要進行編碼以及它的成本

· 為什麼它很重要

base64 編碼

JSON 欄位,包含表示為傳輸而編碼的二進位資料的長 Base64 字串
原始 ToolAcre 向量圖

JSON 沒有位元組類型,因此二進位資料通常採用 Base64 編碼為字串。這篇文章解釋了為什麼存在這種約定、它的大小和 CPU 成本以及何時使用單獨的二進位端點是更好的呼叫。

主導回應的 PDF 欄位是一個具體的 API 負載,其中一個 Base64 blob 超過了其他所有內容

API 回應包含一個大對象,該物件具有一個主導有效負載大小的欄位。回應是 JSON,因此每個值都是字串或數字。大多數欄位都很小:使用者 ID、時間戳記、狀態代碼。其中一個欄位包含 imageData 或 fileContents,並是一個 40 千位元組的 Base64 字串。整個回應為 50 KB。該單一欄位佔傳輸的 80%,這似乎很浪費,因為伺服器最初將其作為位元組發送,而客戶端最終再次需要位元組。

Base64 解決了這個問題: JSON 沒有本機位元組類型,因此二進位資料必須包裝在字串中。 Base64 將任意位元組轉換為 JSON 中安全的 ASCII 字元。客戶端和伺服器都必須在發送時編碼並在接收時解碼,從而增加了 CPU 開銷。產生的有效負載大約比原始位元組大三分之一。這篇文章解釋了為什麼存在該約定、它在實踐中的成本以及何時使用單獨的二進制端點打破 JSON 約束是值得的。

為什麼 JSON 不能攜帶原始位元組 - 字串必須是有效的 Unicode 文字,因此任意位元組都需要文字包裝器

JSON 是一種文字格式,其中所有值都必須是有效的 Unicode 文字。該規範定義了字串、數字、布林值和 null。它沒有位元組數組或緩衝區類型。如果 API 需要傳回影像、加密簽章或檔案上傳等二進位資料,則它不能直接將原始位元組放入 JSON 物件中。這些位元組可能包含 JSON 解析器解釋為結構標記的字元。二進位 blob 中間的空位元組可能會提前終止字串或破壞解析器。

常見的解決方案是將二進位資料編碼為 Base64,產生 JSON 解析器將其視為純文字的 ASCII 字元字串。然後,接收客戶端將 Base64 解碼回位元組並使用它們。此編碼步驟發生在 API 級別,對大多數開發人員來說是隱藏的,但當 API 傳回許多二進位欄位時,它是累積的實際成本。 JSON 中 Base64 的成本在整個請求-回應週期中複合。第一個是大小損失:由於編碼開銷,Base64 輸出大約比輸入大 33%。

成本:多三分之一的位元組、解碼時間和記憶體副本-每項成本都出現在典型的客戶端中

經過 Base64 編碼後,30 兆位元組的影片檔案將變成 40 兆位元組。下載 40 而不是 30 兆位元組會消耗行動裝置上的頻寬和電池,以及慢速連線的使用者時間。第二個成本是 CPU 時間。伺服器必須將二進位資料編碼為 Base64,然後才能將其字串化為 JSON。客戶端必須解析 JSON ,然後將每個 Base64 欄位解碼回位元組。對於具有多個二進位欄位的回應或處理數千個回應的用戶端,CPU 時間會累積。

在手機等受限裝置上,JavaScript 字串操作和用於 Base64 解碼的 TextDecoder 會消耗電池並降低應用程式速度。第三個成本是記憶體:JSON 解析器為 Base64 欄位建立一個字串對象,然後解碼建立另一個副本作為 Uint8Array。在應用程式可以使用大欄位之前,它會在記憶體中實例化兩次。一個有效的例子闡明了成本。假設 API 端點傳回包含 100-kilobyte 頭像影像的使用者設定檔資料。伺服器以位元組形式從磁碟讀取影像,將其編碼為 Base64,並將其包含在 JSON 回應中。

工作範例:檢查 API 回應中的 Base64 欄位 - 在瀏覽器中解碼以確認伺服器實際發送的內容

JSON 响应现在约为 135 千字节(33% 开销加上其他字段)。客戶端下載 135 千位元組而不是 100。在浏览器中, JSON 解析器为 Base64 数据创建 JavaScript 字符串对象。

當應用程式需要圖片時,它會呼叫 Base64 解碼器,該解碼器會建立原始 100 千位元組的 Uint8Array。在解碼的幾毫秒內,兩個物件都存在於記憶體中。如果頁面顯示十個帶有頭像的個人資料,則成本會倍增。另一種方法是 API 傳回 JSON 回應,並為每個頭像資源提供單獨的 URL,讓瀏覽器透過其本機快取、漸進式渲染和記憶體管理來處理圖片下載。

替代方案:多部分、單獨的下載 URL 和原始二進位端點 - 每個的權衡

在 JSON 中包含二進位檔案和單獨取得它之間的權衡取決於 API 目的和使用模式。對於傳回數百個微小縮圖的搜尋結果頁面,將每個縮圖作為單獨的請求獲取會破壞 HTTP 連線池和快取。在 JSON 回應中將它們內聯為 Base64 可能會更快。對於需要一張或兩張高解析度圖片的詳細個人資料頁面,單獨下載顯然更好。 API 檔案應說明 Base64 欄位的最大大小以及用戶端何時應期望單獨的端點。

如果欄位經常超過一兩千位元組,則內嵌 Base64 策略表示 API 設計需要重新考慮。 JSON 中存在 Base64 的替代方案,但每個方案都有其優缺點。多部分 MIME 回應將二進位和文字分開,因此二進位部分會作為原始位元組發送,只有文字部分是 JSON。這需要客戶端解析多部分訊息,而不是只呼叫 JSON.parse,增加了複雜性。 JSON 回應中的單獨下載 URL 指示客戶端單獨取得二進位資源。

值得在 API 檔案中說明的約定 — 標準字母表與 base64url 字母表、填充和最大大小

當二進位資源較大或存取頻率低於元資料時,此方法效果很好。僅傳回位元組並完全放棄 JSON 的原始二進位端點是最簡單的方法,但刪除了 JSON 提供的結構。一些 API 會傳回壓縮的二進位資料並對其進行 Base64 編碼,從而減少了大小損失,但增加了解開銷。選擇取決於預期的用途:小欄位可以很好地內聯,大欄位屬於單獨的資源,即使有 Base64 成本,結構化資料也值得保留在 JSON 中。

約定對於互通性很重要。對二進位資料進行 Base64 編碼的 API 應清楚地記錄它並說明字母表是標準的還是 URL 安全的。標準 Base64 使用 + 和 /, ,它們在 JSON 字串中是安全的,但在 URL 中不安全。 URL 安全的 Base64 將它們替換為 - 和 _,這適用於 data: URI,但在 JSON 中進行不必要的轉義。檔案應指定是否包含或省略填充,因為兩者都是有效的 Base64,但期望填充並接收未填充資料的用戶端將默默失敗或產生垃圾。

這不包括什麼 - protobuf、CBOR 和其他二進位序列化格式

對於非常大或經常更新的欄位,記錄單獨的二進制端點至關重要,這樣客戶端就不會嘗試獲取數千位元組的不必要資料。使用正確的工具,使用 Base64 欄位來偵錯 API 非常簡單。 Base64 編碼器和解碼器可讓您在瀏覽器中本機解碼任何欄位,而無需將其儲存或傳送到任何地方。從 JSON 回應複製 Base64 欄位,將其貼上到解碼器中並點擊解碼。對於類似文字的資料(例如 Base64 內的 JSON),解碼後的輸出會立即出現。

對於影像等二進位資料,十六進位視圖顯示位元組。這有助於確認伺服器發送了您所期望的內容,並您的客戶端解碼器工作正常。如果欄位解碼為意外資料,則問題出在伺服器編碼或複製欄位的方式。如果它解碼為部分 blob,則該欄位可能已被截斷或 Base64 長度可能錯誤。與將欄位寫入檔案並開啟外部工具相比,本機解碼可加快偵錯速度。

重點:JSON 中的 Base64 是一種折衷方案,因此請記錄下來 — Base64 編碼器和解碼器如何幫助您在本地檢查和驗證編碼欄位

API 中 Base64 的實用方法是認知而不是迴避。 Base64 是在 JSON 中傳輸二進位資料的標準方式,而且它可以運作。了解每個 Base64 欄位的大小要多花費三分之一,並每個請求-回應週期要花費幾毫秒的 CPU 時間。對於像身分驗證令牌這樣的小型關鍵元資料(其中 JWT 本身是 Base64 編碼的),成本可以忽略不計。對於大型附件,請詢問二進位檔案是否應在相同回應中傳輸或作為單獨的資源傳輸。

在 API 規格中記錄編碼方案和最大大小。檢查回應時,使用 Base64 編碼器和解碼器來驗證欄位解碼正確並了解伺服器實際發送的內容。這種紀律使權衡顯而易見,決策是經過深思熟慮的而不是偶然的。