舊版套裝組合服務的 Blobstore API 總覽

Blobstore API 可讓您的應用程式提供名為 blob 的資料物件,這類物件會比 Datastore 服務允許的物件大小大上許多。Blob 不僅可用於提供影片或圖片檔等大型檔案外,還可讓使用者上傳大型資料檔案。Blob 的建立方式是透過 HTTP 要求上傳檔案。一般而言,應用程式會向使用者提供具備檔案上傳欄位的表單,藉此執行此操作。提交表單後,Blobstore 會從檔案內容建立 blob 並傳回 blob 的不透明參照,即「blob 鍵」,供您稍後用於提供該 blob。應用程式可根據使用者要求提供完整的 blob 值,或透過類似串流檔案的介面來直接讀取該值。

Blobstore 簡介

Google App Engine 包含的 Blobstore 服務可讓應用程式提供資料物件,並且只會受到單一 HTTP 連線可上傳或下載資料量的限制。這些物件稱為「Blobstore 值」或「blob」。Blobstore 值會以要求處理常式所傳回的回應提供,並且透過網路表單的上傳作業建立而成。應用程式不會直接建立 blob 資料;相反地,blob 是透過提交網路表單或其他 HTTP POST 要求的方式間接建立。Blobstore 值可提供給使用者,或由應用程式透過 Blobstore API 在類似檔案的串流中存取。

應用程式會提供具備檔案上傳欄位的網路表單,藉此提示使用者上傳 Blobstore 值。應用程式會呼叫 Blobstore API 來產生表單的動作網址。使用者的瀏覽器會透過產生的網址,將檔案直接上傳至 Blobstore。接著 Blobstore 會儲存 blob 並修改要求,讓要求內容包含 blob 金鑰,並且將它傳送至應用程式中的特定路徑。在應用程式中,位於該路徑的要求處理常式可執行額外的表單處理。

為了提供 blob,應用程式會在外送回應中設定標頭,並且由 App Engine 將該回應取代為 blob 值。

Blob 一旦建立後便無法修改,但可予以刪除。每個 blob 在資料儲存庫中都有儲存對應的「blob 資訊記錄」,用以提供有關該 blob 的建立時間與內容類型等詳細資訊。您可以使用 blob 鍵來擷取 blob 資訊記錄並查詢其屬性。

應用程式可透過 API 呼叫,一次讀取 Blobstore 值的其中一個部分。讀取的部分最大可達 API 傳回值的大小上限。這個大小略低於 32 MB,在 Python 中是以常數 google.appengine.ext.blobstore.MAX_BLOB_FETCH_SIZE 表示。應用程式僅能透過使用者上傳的檔案來建立或修改 Blobstore 值。

使用 Blobstore

應用程式可使用 Blobstore 來接受使用者上傳的大型檔案,同時還可以提供這些檔案。檔案上傳之後,就稱為 blob。應用程式不會直接存取 blob,而是透過資料儲存庫中的「blob 資訊實體」 (以 BlobInfo 類別表示) 使用 blob。

使用者提交含有一或多個檔案輸入欄位的 HTML 表單就能建立 blob。應用程式會呼叫 create_upload_url(),取得這個表單的目的地 (動作),並將應用程式中處理常式的網址路徑傳送給函式。使用者提交表單時,使用者的瀏覽器會將指定檔案直接上傳至 Blobstore。Blobstore 會修改使用者要求並儲存上傳的檔案資料,將上傳的檔案資料取代為一或多個對應的 blob 金鑰,接著將修改過的要求傳送至您提供給 create_upload_url() 的網址路徑上的處理常式。這個處理常式可根據 blob 鍵執行額外的處理工作。

應用程式可以透過類似檔案的串流介面讀取一部分的 Blobstore 值,請參閱「BlobReader 類別」。

上傳 blob

如要建立並上傳 blob,請依照下列程序執行:

1. 建立上傳網址

呼叫 blobstore.create_upload_url() 以建立使用者所填寫表單的上傳網址,在表單的 POST 完成時傳送要載入的應用程式路徑。

upload_url = blobstore.create_upload_url("/upload_photo")

非同步版本為 create_upload_url_async()。此版本可讓應用程式的程式碼在 Blobstore 產生上傳網址時繼續執行。

2. 建立上傳表單

表單必須包含檔案上傳欄位,且表單的 enctype 必須設為 multipart/form-data。使用者提交表單時,POST 將由建立 blob 的 Blobstore API 來處理。API 也會建立 blob 的資訊記錄並儲存在資料儲存庫中,然後將已經重新寫入的要求做為 blob 金鑰,傳送到指定路徑上的應用程式。

        # To upload files to the blobstore, the request method must be "POST"         # and enctype must be set to "multipart/form-data".         self.response.out.write(             """ <html><body> <form action="{0}" method="POST" enctype="multipart/form-data">   Upload File: <input type="file" name="file"><br>   <input type="submit" name="submit" value="Submit"> </form> </body></html>""".format(                 upload_url             )         )

您必須提供 text/html; charset=utf-8Content-Type 表單頁面,否則系統會以錯誤的方式解譯任何含有非 ASCII 字元的檔案名稱。
這是 webappwebapp2 的預設內容類型。不過,如果您是自行設定 Content-Type 或未使用 webapp,請務必記得執行這項操作。

您無法使用全域外部應用程式負載平衡器搭配無伺服器 NEG,處理傳送至 blobstore.create_upload_url 呼叫傳回的 /_ah/upload/ URL 的上傳要求。您必須改為將這些上傳要求直接轉送至 App Engine 服務。您可以透過 appspot.com 網域或直接對應至 App Engine 服務的自訂網域,執行這項操作。

3. 實作上傳處理常式

在這個處理常式中,您可以將 blob 鍵與應用程式資料模型的其他部分儲存在一起。blob 鍵本身仍可從資料儲存庫中的 blob 資訊實體存取。請注意,在使用者提交表單並呼叫處理常式後,blob 就已經儲存,而 blob 資訊也已新增至資料儲存庫。如果您不想在應用程式保留 blob,則應立即刪除 blob,以避免 blob 孤立:

class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):     def post(self):         upload = self.get_uploads()[0]         user_photo = UserPhoto(             user=users.get_current_user().user_id(), blob_key=upload.key()         )         user_photo.put()          self.redirect("/view_photo/%s" % upload.key())  

webapp 架構提供 blobstore_handlers.BlobstoreUploadHandler 上傳處理常式類別來協助您剖析表單資料。詳情請參閱 BlobstoreUploadHandler 的參考資料。

Blobstore 修改使用者要求時,系統會清空已上傳檔案中的 MIME 區塊的主體內容,然後以 MIME 區塊標頭的形式加入 blob 金鑰。系統會保留其他表單欄位與區段,並傳送至上傳處理常式。如果您未指定內容類型,Blobstore 會嘗試從副檔名推論。如果系統無法判定內容類型,則會為新建的 blob 指派內容類型 application/octet-stream

提供 blob

如要提供 blob,您必須以應用程式路徑的形式加入 blob 下載處理常式。應用程式會在連出回應中設定標頭,藉此提供 blob。下文中的範例使用的是 webapp 架構。使用 webapp 時,處理常式應將所需 blob 的 blob 金鑰傳送至 self.send_blob()。在本範例中,blob 鍵是做為網址的一部分傳送至下載處理常式。實際上,下載處理常式可透過您選擇的任何方式來取得 blob 金鑰,例如透過其他方法或是使用者動作。

class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):     def get(self, photo_key):         if not blobstore.get(photo_key):             self.error(404)         else:             self.send_blob(photo_key)  

webapp 架構提供下載處理常式類別 blobstore_handlers.BlobstoreDownloadHandler 來協助您剖析表單資料。詳情請參閱 BlobstoreDownloadHandler 的參考資料。

Blob 可透過任何應用程式網址提供。如要在應用程式中提供 blob,請在包含 blob 鍵的回應中放上特殊標頭。App Engine 會以 blob 的內容取代回應的本文。

Blob 位元組範圍

Blobstore 可根據要求,提供某個極大值的一部分,而非完整的值。如要提供部分值,請在外送回應中加入 X-AppEngine-BlobRange 標頭。此標頭的值代表標準的 HTTP 位元組範圍。位元組編號從零開始。空白的 X-AppEngine-BlobRange 會指示 API 忽略範圍標頭,並提供完整的 blob。範例範圍包括:

  • 0-499 提供值的前 500 個位元組 (從第 0 個位元組至第 499 個位元組,包含首尾)。
  • 500-999 提供從第 501 個位元組開始的 500 個位元組。
  • 500- 提供從值的第 501 個位元組開始到值結尾的所有位元組。
  • -500 提供值的最後 500 個位元組。

如果位元組範圍對 Blobstore 值有效,Blobstore 會將 206 Partial Content 狀態碼和要求的位元組範圍傳送至用戶端。如果範圍對值無效,Blobstore 會傳送 416 Requested Range Not Satisfiable

Blobstore 不支援在單一要求中提供多個位元組範圍 (例如 100-199,200-299),即便範圍重複也一樣。

webapp.blobstore_handlers.BlobstoreDownloadHandler 類別包含使用提供的位元組索引設定這個標頭的功能,以及從使用者提供的 range 標頭自動衍生位元組範圍的功能。

完整的應用程式範例

在下列應用程式範例中,應用程式的主要網址會載入可用來向使用者要求上傳檔案的表單,接著上傳處理常式會立即呼叫下載處理常式來提供資料。此做法是為了簡化範例應用程式的內容。就實務而言,您可能不會使用主要網址來要求上傳資料,也不會立即提供剛剛才上傳的 blob。

from google.appengine.api import users from google.appengine.ext import blobstore from google.appengine.ext import ndb from google.appengine.ext.webapp import blobstore_handlers import webapp2   # This datastore model keeps track of which users uploaded which photos. class UserPhoto(ndb.Model):     user = ndb.StringProperty()     blob_key = ndb.BlobKeyProperty()   class PhotoUploadFormHandler(webapp2.RequestHandler):     def get(self):         upload_url = blobstore.create_upload_url("/upload_photo")         # To upload files to the blobstore, the request method must be "POST"         # and enctype must be set to "multipart/form-data".         self.response.out.write(             """ <html><body> <form action="{0}" method="POST" enctype="multipart/form-data">   Upload File: <input type="file" name="file"><br>   <input type="submit" name="submit" value="Submit"> </form> </body></html>""".format(                 upload_url             )         )   class PhotoUploadHandler(blobstore_handlers.BlobstoreUploadHandler):     def post(self):         upload = self.get_uploads()[0]         user_photo = UserPhoto(             user=users.get_current_user().user_id(), blob_key=upload.key()         )         user_photo.put()          self.redirect("/view_photo/%s" % upload.key())     class ViewPhotoHandler(blobstore_handlers.BlobstoreDownloadHandler):     def get(self, photo_key):         if not blobstore.get(photo_key):             self.error(404)         else:             self.send_blob(photo_key)     app = webapp2.WSGIApplication(     [         ("/", PhotoUploadFormHandler),         ("/upload_photo", PhotoUploadHandler),         ("/view_photo/([^/]+)?", ViewPhotoHandler),     ],     debug=True, )

使用圖片服務與 Blobstore

圖片服務可將 Blobstore 值做為轉換的來源。來源圖片的大小可達 Blobstore 值的上限大小。圖片服務仍會將轉換後的圖片傳回應用程式,因此轉換後的圖片必須小於 32 MB。這很適合用於建立由使用者上傳的大型相片縮圖。

如要瞭解如何將圖片服務與 Blobstore 值搭配使用,請參閱圖片服務說明文件

使用 Blobstore API 與 Google Cloud Storage

您可以使用 Blobstore API,將 blob 儲存至 Cloud Storage,而非儲存在 Blobstore。您必須按照 Google Cloud Storage 說明文件中所述方式設定值區,並在 blobstore.blobstore.create_upload_url gs_bucket_name 參數中指定值區和檔案名稱。在上傳處理常式中,您需要處理傳回的 FileInfo 中繼資料,並明確儲存 Google Cloud Storage 檔案名稱以便稍後擷取 blob。

您也可以使用 Blobstore API 提供 Cloud Storage 物件。 下列程式碼片段示範如何執行此操作:

# Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #      http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.  """A sample app that operates on GCS files with blobstore API."""  import cloudstorage from google.appengine.api import app_identity from google.appengine.ext import blobstore from google.appengine.ext.webapp import blobstore_handlers import webapp2   # This handler creates a file in Cloud Storage using the cloudstorage # client library and then reads the data back using the Blobstore API. class CreateAndReadFileHandler(webapp2.RequestHandler):     def get(self):         # Get the default Cloud Storage Bucket name and create a file name for         # the object in Cloud Storage.         bucket = app_identity.get_default_gcs_bucket_name()          # Cloud Storage file names are in the format /bucket/object.         filename = "/{}/blobstore_demo".format(bucket)          # Create a file in Google Cloud Storage and write something to it.         with cloudstorage.open(filename, "w") as filehandle:             filehandle.write("abcde\n")          # In order to read the contents of the file using the Blobstore API,         # you must create a blob_key from the Cloud Storage file name.         # Blobstore expects the filename to be in the format of:         # /gs/bucket/object         blobstore_filename = "/gs{}".format(filename)         blob_key = blobstore.create_gs_key(blobstore_filename)          # Read the file's contents using the Blobstore API.         # The last two parameters specify the start and end index of bytes we         # want to read.         data = blobstore.fetch_data(blob_key, 0, 6)          # Write the contents to the response.         self.response.headers["Content-Type"] = "text/plain"         self.response.write(data)          # Delete the file from Google Cloud Storage using the blob_key.         blobstore.delete(blob_key)   # This handler creates a file in Cloud Storage using the cloudstorage # client library and then serves the file back using the Blobstore API. class CreateAndServeFileHandler(blobstore_handlers.BlobstoreDownloadHandler):     def get(self):         # Get the default Cloud Storage Bucket name and create a file name for         # the object in Cloud Storage.         bucket = app_identity.get_default_gcs_bucket_name()          # Cloud Storage file names are in the format /bucket/object.         filename = "/{}/blobstore_serving_demo".format(bucket)          # Create a file in Google Cloud Storage and write something to it.         with cloudstorage.open(filename, "w") as filehandle:             filehandle.write("abcde\n")          # In order to read the contents of the file using the Blobstore API,         # you must create a blob_key from the Cloud Storage file name.         # Blobstore expects the filename to be in the format of:         # /gs/bucket/object         blobstore_filename = "/gs{}".format(filename)         blob_key = blobstore.create_gs_key(blobstore_filename)          # BlobstoreDownloadHandler serves the file from Google Cloud Storage to         # your computer using blob_key.         self.send_blob(blob_key)   app = webapp2.WSGIApplication(     [         ("/", CreateAndReadFileHandler),         ("/blobstore/read", CreateAndReadFileHandler),         ("/blobstore/serve", CreateAndServeFileHandler),     ],     debug=True, ) 

使用 BlobReader

應用程式可以透過類似於 Python file 物件的介面讀取 Blobstore 值的資料。這個介面可讀取任何位元組位置開始的值,並可使用多重服務呼叫與緩衝作業,因此即使受到單次服務呼叫回應的大小限制,應用程式還是可以存取該值的完整大小。

BlobReader 類別可以將下列三個值當中的其中一個當做其建構函式的引數:

物件會採用常見的檔案方法來讀取該值。應用程式無法修改 Blobstore 值;寫入的檔案方法不會受到執行。

# Instantiate a BlobReader for a given Blobstore blob_key. blob_reader = blobstore.BlobReader(blob_key)  # Instantiate a BlobReader for a given Blobstore blob_key, setting the # buffer size to 1 MB. blob_reader = blobstore.BlobReader(blob_key, buffer_size=1048576)  # Instantiate a BlobReader for a given Blobstore blob_key, setting the # initial read position. blob_reader = blobstore.BlobReader(blob_key, position=0)  # Read the entire value into memory. This may take a while depending # on the size of the value and the size of the read buffer, and is not # recommended for large values. blob_reader_data = blob_reader.read()  # Write the contents to the response. self.response.headers["Content-Type"] = "text/plain" self.response.write(blob_reader_data)  # Set the read position back to 0, then read and write 3 bytes. blob_reader.seek(0) blob_reader_data = blob_reader.read(3) self.response.write(blob_reader_data) self.response.write("\n")  # Set the read position back to 0, then read and write one line (up to # and including a '\n' character) at a time. blob_reader.seek(0) for line in blob_reader:     self.response.write(line)

提出非同步要求

應用程式可呼叫一些能在背景運作的 Blobstore 函式。Blobstore 執行要求時,應用程式能夠執行其他工作。若要提出要求,應用程式必須呼叫非同步函式。函式會立即傳回遠端程序呼叫 (RPC) 物件,這個物件代表了該項要求。應用程式必須取得該項要求的結果時,就會呼叫遠端程序呼叫 (RPC) 物件的 get_result() 方法。

如果應用程式呼叫 get_result() 時,服務尚未完成要求,方法會等到要求完成 (或達到期限,或發生錯誤) 為止。這個方法會傳回結果物件,或在執行要求時發生錯誤時發出例外狀況。舉例來說,這個程式碼片段

upload_url = blobstore.create_upload_url('/upload') slow_operation() self.response.out.write("""<form action="%s" method="POST"                            enctype="multipart/form-data">""" % upload_url) 

會變成:

upload_url_rpc = blobstore.create_upload_url_async('/upload') slow_operation() upload_url = upload_url_rpc.get_result() self.response.out.write("""<form action="%s" method="POST"                            enctype="multipart/form-data">""" % upload_url) 

在本範例中,應用程式會執行 slow_operation() 程式碼,同時 Blobstore 會產生上傳網址。

配額與限制

Blobstore 值所使用的空間會佔用「儲存資料 (可計費)」配額。資料儲存庫中的 blob 資訊實體會計入資料儲存庫相關的限額中。請注意,Google Cloud Storage 屬付費使用服務,您需按照 Cloud Storage 價目表支付費用。

如要進一步瞭解整個系統的安全配額,請參閱配額

除了整個系統的安全配額外,Blobstore 的使用配額還受到下列特別限制:

  • 應用程式透過單一 API 呼叫可讀取的 Blobstore 資料量上限為 32 MB。
  • 單一表單 POST 可上傳的檔案數量上限為 500 個。