要求的處理方式

區域 ID

REGION_ID 是 Google 根據您在建立應用程式時選取的地區所指派的縮寫代碼。此代碼不對應至國家/地區或省份,即使部分區域 ID 可能與常用的國家/地區和省份代碼相似。如果是 2020 年 2 月後建立的應用程式,App Engine 網址會包含 REGION_ID.r。如果是這段時間前建立的現有應用程式,網址可選擇是否包含地區 ID。

進一步瞭解區域 ID

本文件說明 App Engine 應用程式如何接收要求及傳送回應。

詳情請參閱要求標頭和回應參考資料

如果您的應用程式使用服務,則可以設定要求的位址,將要求傳送到特定服務或該服務的特定版本。如要進一步瞭解服務的定址功能,請參閱要求的轉送方式一文。

處理要求

您的應用程式負責啟動網路伺服器和處理要求。 只要是適用於您開發語言的網路架構,您都可以使用。

App Engine 會執行應用程式的多個執行個體,每個執行個體都有自己專用的網路伺服器來處理要求。每個要求均有可能轉送至任何一個執行個體,因此來自同一個使用者的連續要求不一定會送到相同的執行個體。執行個體可以並行處理多個要求,執行個體的數量能夠隨流量自動調整。您也可以在 app.yaml 檔案中設定 max_concurrent_requests 元素,藉此變更執行個體可以處理的並行要求數量。

當 App Engine 收到應用程式的網路要求時,即會呼叫與該網址對應的處理常式指令碼,如應用程式的 app.yaml 設定檔所述。Python 2.7 執行階段針對回溯相容性支援 WSGI 標準CGI 標準。建議使用 WSGI,如果沒有它,Python 2.7 的某些功能就無法運作。應用程式指令碼處理常式的設定,決定要求是使用 WSGI 還是 CGI 處理。

下列 Python 指令碼會使用 HTTP 標頭和 Hello, World! 訊息回應要求。

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        self.response.write("Hello, World!")


app = webapp2.WSGIApplication(
    [
        ("/", MainPage),
    ],
    debug=True,
)

如要將多個要求並行分派至每個網路伺服器,請在 app.yaml 檔案中新增 threadsafe: true,將應用程式標記為 threadsafe。如有任何處理常式指令碼採用 CGI,則無法使用並行要求。

配額與限制

App Engine 會在流量增加時自動分配資源給您的應用程式,然而這項作業會受到下列限制:

  • 對於低延遲時間的應用程式 (回應要求的時間不到一秒),App Engine 會保留自動調整資源配置的容量。

  • 大幅受到 CPU 限制的應用程式可能也會產生一些額外的延遲時間,以便和相同伺服器上的其他應用程式有效率地共用資源。對靜態檔案的要求則不受這些延遲限制。

每個傳送至應用程式的要求均會計入「Requests」(要求) 限制。 而回應要求所傳送的資料則會計入「Outgoing Bandwidth (billable)」(連出頻寬 (可計費)) 限制。

HTTP 及 HTTPS (加密連線) 要求均會計入「Requests」(要求)、「Incoming Bandwidth (billable)」(連入頻寬 (可計費)) 及「Outgoing Bandwidth (billable)」(連出頻寬 (可計費)) 的限制。Google Cloud 控制台的「Quota Details」(配額詳細資料) 頁面也會個別回報「Secure Requests」(安全性要求)、「Secure Incoming Bandwidth」(安全連入頻寬) 和「Secure Outgoing Bandwidth」(安全連出頻寬) 的值,以供參考之用。僅有 HTTPS 要求會計入這些值。詳情請參閱配額頁面。

要求處理常式的使用配額還受到下列特別限制:

限制 大小
要求大小 32 MB
回應大小 32 MB
要求逾時 視應用程式使用的資源調度類型而定
檔案總數量的上限 (應用程式檔案和靜態檔案) 總計 10,000 個
每個目錄 1,000 個
應用程式檔案大小上限 32 MB
靜態檔案的大小上限 32 MB
所有應用程式和靜態檔案的總大小上限 前 1 GB 免費
前 1 GB 過後,每個月每 GB$ 0.026 美元
待處理要求逾時 10 秒
單一要求標頭欄位的大小上限 標準環境中的第二代執行階段為 8 KB。如果傳送至這些執行階段的要求標頭欄位超過 8 KB,系統會傳回 HTTP 400 錯誤。

要求限制

所有 HTTP/2 要求在轉送到應用程式伺服器時,都會轉譯成 HTTP/1.1 要求。

回應限制

  • 動態回應大小上限為 32 MB。如果指令碼處理常式產生大於此上限的回應,伺服器會傳回空白回應並顯示「500 Internal Server Error」(內部伺服器錯誤) 狀態碼。這項限制不適用於從舊版 Blobstore 和 Cloud Storage 提供資料的回應。

  • 第二代執行階段的回應標頭大小上限為 8 KB。 如果回應標頭超出這項限制,系統會傳回 HTTP 502 錯誤,且記錄檔會顯示 upstream sent too big header while reading response header from upstream

要求標頭

傳入 HTTP 要求包含用戶端傳送的 HTTP 標頭。基於安全性考量,部分標頭在送達應用程式之前會由中繼 Proxy 進行處理或修改。

詳情請參閱要求標頭參考資料

處理要求逾時

App Engine 已針對要求持續時間短暫的應用程式進行最佳化,尤其是回應要求只需耗費數百毫秒的應用程式。效率良好的應用程式能快速回應大部分的要求,至於回應速度不夠快的應用程式,則無法隨著 App Engine 的基礎架構妥善調度資源。為確保達到這個效能水準,系統會強制設定要求逾時上限,所有應用程式都必須在時限內回應。

如果應用程式超過這個期限,App Engine 會中斷要求處理常式。Python 執行階段環境會從 google.appengine.runtime 引發 DeadlineExceededError 例外狀況,藉此達成這項目的。如果要求處理常式沒有接收此例外狀況,則執行階段環境會將 HTTP 500 伺服器錯誤傳回至用戶端,就像處理所有未接收的例外狀況一樣。

要求處理常式可以接收此錯誤以自訂回應。執行階段環境會在引發例外狀況之後,多給要求處理常式一點時間 (少於 1 秒) 準備自訂的回應。

class TimerHandler(webapp2.RequestHandler):
    def get(self):
        from google.appengine.runtime import DeadlineExceededError

        try:
            time.sleep(70)
            self.response.write("Completed.")
        except DeadlineExceededError:
            self.response.clear()
            self.response.set_status(500)
            self.response.out.write("The request did not complete in time.")

如果處理常式未在第二個期限前傳回回應或引發例外狀況,系統會停止處理常式,並傳回預設錯誤回應。

回應

App Engine 會使用 Request 呼叫處理常式指令碼,然後等候指令碼傳回結果;所有寫入標準輸出串流的資料會以 HTTP 回應格式送出。

您產生的回覆會受到大小限制,而且在傳回用戶端前可能已經過修改。

詳情請參閱要求回應參考資料

串流回應

若資料在要求處理過程中,以增量區塊的形式傳送至用戶端,那麼 App Engine 並不支援此類資料的串流回應。系統會依上述方式收集所有來自程式碼的資料,並做為單一 HTTP 回應傳送。

回應壓縮

App Engine 會盡量向支援壓縮 (gzip) 的用戶端提供壓縮內容。為判斷是否應壓縮內容,App Engine 會在收到要求時執行下列操作:

  1. 查看要求中的 Accept-EncodingUser-Agent 標頭,確認用戶端是否能穩定接收壓縮過的回應。這種方法可避免熱門瀏覽器中一些常在 gzip 格式的壓縮內容上發生的問題。

  2. 查看您為回應處理常式設定的 Content-Type 標頭,確認是否適合壓縮內容。一般來說,壓縮適用於文字內容類型,但不適用於二進位內容類型。

注意事項:

  • 用戶端可以將 Accept-EncodingUser-Agent 要求標頭都設為 gzip,強制壓縮文字型內容類型。

  • 如果要求未在 Accept-Encoding 標頭中指定 gzip,App Engine 就不會壓縮回應資料。

  • Google 前端會快取 App Engine 靜態檔案和目錄處理常式的回應。視多項因素而定 (例如最先快取的回應資料類型、您在回應中指定的 Vary 標頭,以及要求中包含的標頭),用戶端可能會要求壓縮資料,但收到未壓縮的資料,反之亦然。詳情請參閱回應快取

回應快取

Google 前端,以及可能的使用者瀏覽器和其他中繼快取 Proxy 伺服器,會根據您在回應中指定的標準快取標頭,快取應用程式的回應。您可以透過架構、直接在程式碼中,或透過 App Engine 靜態檔案和目錄處理常式,指定這些回應標頭。

在 Google 前端,快取金鑰是要求的完整網址。

快取靜態內容

為確保用戶端在發布靜態內容後,能立即收到更新,建議您從版本目錄 (例如 css/v1/styles.css) 提供靜態內容。快取到期前,Google 前端不會驗證快取 (檢查更新內容)。即使快取過期,只要要求網址的內容未變更,快取就不會更新。

您可以在 app.yaml 中設定下列回應標頭,影響 Google 前端快取內容的方式和時間:

  • 應設為 public,Google 前端才能快取內容;除非您指定 Cache-Control privateno-store 指令,否則 Google 前端也可能會快取內容。Cache-Control如果您未在 app.yaml 中設定這個標頭,App Engine 會自動為靜態檔案或目錄處理常式處理的所有回應新增這個標頭。詳情請參閱「新增或取代的標頭」。

  • Vary:如要讓快取根據要求中傳送的標頭,針對網址傳回不同的回應,請在 Vary 回應標頭中設定一或多個下列值:AcceptAccept-EncodingOriginX-Origin

    由於其他 Vary 值可能具有高基數,因此系統不會快取這些值的資料。

    例如:

    1. 您指定下列回應標頭:

      Vary: Accept-Encoding

    2. 應用程式會收到含有 Accept-Encoding: gzip 標頭的要求。App Engine 會傳回壓縮回應,而 Google 前端會快取回應資料的 gzip 版本。後續所有包含 Accept-Encoding: gzip 標頭的這類網址要求,都會從快取收到經過 gzip 壓縮的資料,直到快取失效為止 (快取過期後內容變更)。

    3. 您的應用程式收到不含 Accept-Encoding 標頭的要求。App Engine 會傳回未壓縮的回應,而 Google Frontend 會快取未壓縮的回應資料版本。後續對這個網址的所有要求,只要不含 Accept-Encoding 標頭,都會從快取接收壓縮資料,直到快取失效為止。

    如未指定 Vary 回應標頭,Google 前端會為網址建立單一快取項目,並用於所有要求,不論要求中的標頭為何。例如:

    1. 您未指定 Vary: Accept-Encoding 回應標頭。
    2. 要求包含 Accept-Encoding: gzip 標頭,且回應資料的 gzip 壓縮版本會快取。
    3. 第二個要求不含 Accept-Encoding: gzip 標頭。 不過,由於快取包含回應資料的 gzip 壓縮版本,即使用戶端要求未壓縮的資料,回應仍會經過 gzip 壓縮。

要求中的標頭也會影響快取:

  • 如果要求包含 Authorization 標頭,Google 前端不會快取內容。

快取到期

根據預設,App Engine 靜態檔案和目錄處理常式新增至回應的快取標頭,會指示用戶端和 Google 前端等網路 Proxy 在 10 分鐘後使快取失效。

擁有指定到期時間的檔案在傳輸之後,通常就無法從網路 Proxy 快取中清除,就算使用者清除了自己的瀏覽器快取也沒辦法。重新部署新版應用程式時,並不會重設任何快取。因此,如果您打算修改靜態檔案,請為其設定較短的效期 (不超過一小時)。在大多數情況下,預設的 10 分鐘效期即為適當的設定。

如要變更所有靜態檔案和目錄處理常式的預設到期日,請在 app.yaml 檔案中指定 default_expiration 元素。如要為個別處理常式設定特定到期時間,請在 app.yaml 檔案的處理常式元素中指定 expiration 元素。

您在到期元素時間中指定的值,將用於設定 Cache-ControlExpires HTTP 回應標頭。

應用程式快取

Python 執行階段環境會在要求之間將匯入的模組快取於單一的網路伺服器,這種做法類似於在獨立的 Python 應用程式中,即使某個模組會由多個檔案匯入,該應用程式也只載入該模組一次。由於 WSGI 處理常式是模組,因此可以在要求之間進行快取。CGI 處理常式指令碼只有在提供 main() 日常程序時可供快取;否則,CGI 處理常式指令碼會針對每個要求載入。

應用程式快取可大幅縮短回應時間。我們建議所有 CGI 處理常式指令碼使用如上述的 main() 日常程序。

快取匯入

為了提高效率,網路伺服器會將匯入的模組存放在記憶體中,而且不會在相同的伺服器上,為針對相同應用程式所提出的後續要求重新載入或重新評估這些模組。大多數的模組在匯入時不會初始化任何全域資料,或是產生其他的副作用,因此快取這些模組不會變更應用程式的行為。

如果應用程式是根據每次要求所評估的模組來匯入模組,則應用程式必須配合此快取行為。

快取 CGI 處理常式

除了匯入的模組,您可要求 App Engine 對 CGI 處理常式指令碼本身進行快取。如果處理常式指令碼定義名為 main() 的函式,系統會像匯入的模組一樣,快取指令碼及其全域環境。在指定的網路伺服器上第一次要求指令碼時,App Engine 會正常地評估該指令碼,對於後續要求,App Engine 會在快取環境中呼叫 main() 函式。

如要快取處理常式指令碼,App Engine 必須能夠呼叫 main(),且不得有任何引數。如果處理常式指令碼未定義 main() 函式,或 main() 函式需要引數 (沒有預設值),則 App Engine 會為每個要求載入並評估整個指令碼。

將剖析的 Python 程式碼儲存在記憶體可節省時間,並且獲得更快速的回應。您也可將全域環境存入快取,以提供其他用途:

  • 編譯的規則運算式:所有的規則運算式都會被剖析,並以編譯過的格式儲存。您可以將編譯的規則運算式儲存在全域變數中,然後在要求之間使用應用程式快取,以重複使用編譯過的物件。

  • GqlQuery 物件。建立 GqlQuery 物件時,系統會剖析 GQL 查詢字串。重新使用包含參數繫結的 GqlQuery 物件與 bind() 方法,會比每次都重新構造物件更快。您可以將 GqlQuery 物件與值的參數繫結儲存在全域變數中,然後為每個要求繫結新的參數值來重複使用。

  • 設定和資料檔案:如果您的應用程式從檔案載入並剖析設定資料,程式可以將剖析的資料保留在記憶體中,以避免在每次要求時重新載入檔案。

處理常式指令碼必須在匯入時呼叫 main()。App Engine 預期匯入指令碼會呼叫 main(),因此在伺服器上首次載入要求處理常式時,App Engine 不會呼叫該指令碼。

使用 main() 的應用程式快取可大幅縮短 CGI 處理常式的回應時間。我們建議所有使用 CGI 的應用程式採用這個方法。

記錄

App Engine 網路伺服器會擷取處理常式指令碼寫入標準輸出串流的所有內容,以回應網路要求。還會擷取處理常式指令碼寫入標準錯誤串流的所有內容,並儲存為記錄資料。每項要求都會經由指派獲得 request_id;這是根據要求開始時間所指派的全域不重複 ID。您可以在 Google Cloud 控制台中使用 Cloud Logging 查看應用程式的記錄資料。

App Engine Python 執行階段特別支援包含來自 Python 標準程式庫的登入模組,以瞭解等登入層級 (「偵錯」、「資訊」、「警告」、「錯誤」、「重要」) 等登入概念。

import logging

import webapp2


class MainPage(webapp2.RequestHandler):
    def get(self):
        logging.debug("This is a debug message")
        logging.info("This is an info message")
        logging.warning("This is a warning message")
        logging.error("This is an error message")
        logging.critical("This is a critical message")

        try:
            raise ValueError("This is a sample value error.")
        except ValueError:
            logging.exception("A example exception log.")

        self.response.out.write("Logging example.")


app = webapp2.WSGIApplication([("/", MainPage)], debug=True)

環境

執行環境會自動設定數個環境變數,您可以在 app.yaml 中設定更多變數。在自動設定的變數中,有些是 App Engine 的特殊變數,而其他變數則是 WSGI 或 CGI 標準的一部分。Python 程式碼可以使用 os.environ 字典存取這些變數。

下列環境變數專屬於 App Engine:

  • CURRENT_VERSION_ID:目前執行應用程式的主要和次要版本,例如「X.Y」。主要版本號碼 (「X」) 會在應用程式的 app.yaml 檔案中指定。當每個版本的應用程式上傳到 App Engine 時,則會自動設定次要版本編號 (「Y」)。在開發網路伺服器上,次要版本一律是「1」。

  • AUTH_DOMAIN:用於透過 Users API 驗證使用者的網域。託管在 appspot.com 的應用程式具有 AUTH_DOMAINgmail.com,且接受任何 Google 帳戶。在自訂網域上託管的應用程式,其 AUTH_DOMAIN 等於自訂網域。

  • INSTANCE_ID:包含處理要求的前端執行個體 ID。這個 ID 是十六進位字串 (例如 00c61b117c7f7fd0ce9e1325a04b8f0df30deaaf)。登入的管理員可在下列網址中使用此 ID:https://INSTANCE_ID-dot-VERSION_ID-dot-SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com。要求將轉送到特定前端執行個體。假如執行個體無法處理要求,則立即傳回 503。

下列環境變數是 WSGI 和 CGI 標準的一部分,在 App Engine 中會有特殊行為:

  • SERVER_SOFTWARE:在開發網路伺服器中,此值為「Development/X.Y」,其中「X.Y」是執行階段的版本。在 App Engine 上運行時,這個值為「Google App Engine/X.Y.Z」。

系統會根據 WSGI 或 CGI 標準設定其他環境變數。如要進一步瞭解這些變數,請視需要參閱 WSGI 標準CGI 標準

您也可以在 app.yaml 檔案中設定環境變數:

env_variables:
  DJANGO_SETTINGS_MODULE: 'myapp.settings'

下列 webapp2 要求處理常式會顯示瀏覽器中應用程式可見的每個環境變數:

class PrintEnvironmentHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        for key, value in os.environ.iteritems():
            self.response.out.write("{} = {}\n".format(key, value))

要求 ID

提出要求時,您可以儲存不重複的要求 ID,之後可以在 Cloud Logging 中將要求 ID 用來查詢該要求的記錄。

以下範例程式碼顯示如何在要求的內文中取得要求 ID:

class RequestIdHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers["Content-Type"] = "text/plain"
        request_id = os.environ.get("REQUEST_LOG_ID")
        self.response.write("REQUEST_LOG_ID={}".format(request_id))

強制 HTTPS 連線

基於安全性考量,所有應用程式皆應鼓勵用戶端透過 https 連線。如要指示瀏覽器針對指定網頁或整個網域採用 https 而非 http,請在回應中設定 Strict-Transport-Security 標頭。例如:

Strict-Transport-Security: max-age=31536000; includeSubDomains
如要為應用程式提供的任何靜態內容設定這個標頭,請將標頭新增至應用程式的靜態檔案和目錄處理常式

如要為程式碼產生的回覆設定這個標頭,請使用 flask-talisman 程式庫

處理非同步背景工作

背景工作是指應用程式在傳送 HTTP 回應後,為要求執行的任何工作。請避免在應用程式中執行背景工作,並檢查程式碼,確認在您傳送回應之前,所有非同步作業皆已完成。

如果是長時間執行的工作,建議使用 Cloud Tasks。使用 Cloud Tasks 時,HTTP 要求會長時間存在,且只有在任何非同步工作結束後才會傳回回應。