要求的處理方式

區域 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 的某些功能就無法運作。應用程式的 script 處理常式設定會決定要求是使用 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,
)

如要並行將多個要求分派給每個網路伺服器,請將 threadsafe: true 新增至 app.yaml 檔案,並將應用程式標示為 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 千位元組。針對這些執行階段發出的標頭欄位超過 8 千位元組的要求,會傳回 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 前端快取內容的方式和時間:

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

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

    由於基數可能很高,因此系統不會為其他 Vary 值快取資料。

    例如:

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

      Vary: Accept-Encoding

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

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

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

    1. 您未指定 Vary: Accept-Encoding 回應標頭。
    2. 要求包含 Accept-Encoding: gzip 標頭,回應資料的 GZIP 版本會快取。
    3. 第二個要求不含 Accept-Encoding: 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 控制台 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 上託管的應用程式具有 gmail.comAUTH_DOMAIN,並接受任何 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 要求會保留一段時間,且只會在任何非同步工作結束後傳回回應。