My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

Python 執行階段環境

「應用服務引擎」會在安全的「沙箱」環境中,使用預先載入的 Python 直譯器來執行您的 Python 應用程式碼。您的應用程式會與這個環境互動,藉此接收網路要求、執行工作,以及傳送回應。

選取 Python 執行階段

當您搭配使用 app.yaml 設定檔以及 Python SDK 中的 appcfg.py 工具時,「應用服務引擎」會為您的應用程式提供 Python 執行階段環境。您可以透過下列設定元素選取 Python 執行階段環境:

runtime: python
api_version: 1

第一個元素 (runtime) 會選取 Python 執行階段環境。截至筆者撰稿時,「應用服務引擎」僅支援一種執行階段環境,即 python

第二個元素 (api_version) 會選取要使用的 Python 執行階段環境版本。截至筆者撰稿時,「應用服務引擎」僅支援一個版本的 Python 環境,即 1。如果「應用服務引擎」團隊需要發佈環境變更,且這些變更可能無法相容於現有程式碼時,他們會使用新的版本識別碼。除非您變更 api_version 設定並上傳您的應用程式,否則您的應用程式將繼續使用選取的版本。

如需 app.yamlappcfg.py 的詳細資訊,請參閱「Python 應用程式設定」以及「上傳應用程式」。

要求和 CGI

「應用服務引擎」接收到應用程式的網頁請求時,會呼叫與 URL 相應的處理常式指令碼,如同應用程式的 app.yaml 設定檔所述。「應用服務引擎」使用 CGI 標準來溝通要求資料與處理常式,並接收回應。

「應用服務引擎」使用多個網頁伺服器來執行您的應用程式,並可靠地自動調整用來處理要求的伺服器數量。指定的要求可能會透過傳送到任一部伺服器,而且處理它的伺服器,與來自相同使用者之前送出要求的處理伺服器,可能不是同一部。

伺服器會比較要求的 URL 和應用程式設定檔中的 URL 模式,以決定要執行的 Python 處理常式指令碼。然後,它會在填入要求資料的 CGI 環境中,執行處理常式。如同 CGI 標準所描述,伺服器會將要求放入環境變數以及標準輸入串流。指令碼針對要求執行適當的動作,然後準備回應,並將它放入標準輸出串流。

大部分應用程式會使用程式庫來剖析 CGI 要求與傳回 CGI 回應 (例如 Python 標準程式庫的 cgi 模組),或使用熟悉 CGI 通訊協定的網頁架構 (例如 webapp)。您可以參考 CGI 文件,進一步瞭解環境變數和輸入串流資料的格式。

下列範例處理常式指令碼會在使用者的瀏覽器顯示訊息。它會將 HTTP 標頭 (識別為訊息類型和訊息內容者) 列印至標準輸出串流。

print "Content-Type: text/plain"
print ""
print "Hello, world!"

回應

「應用服務引擎」會收集要求處理常式指令碼寫入標準輸出串流的所有資料,然後等待指令碼結束。指令碼結束時,會將所有輸出的資料傳送給使用者。

「應用服務引擎」不支援在結束處理常式之前,將資料傳送給使用者的瀏覽器。部分網頁伺服器在回應單一要求時會透過此技術,在一段時間內將資料「串流」至使用者的瀏覽器。「應用服務引擎」不支援這種串流技術。

如果用戶端傳送 HTTP 標頭的要求指出用戶端可接受壓縮的 (gzip'd) 內容,則「應用服務引擎」會自動壓縮回應資料,並附加適當的回應標頭。它會使用 Accept-EncodingUser-Agent 的要求標頭來判斷用戶端是否能夠可靠地接收壓縮的回應。自訂用戶端可以強制壓縮內容,只要將 Accept-EncodingUser-Agent 標頭指定為「gzip」值即可。

要求計時器

要求處理常式必須在限制時間內 (一般是在 30 秒左右),根據要求產生並傳回回應。一旦超過限制時間,要求處理常式將遭到中斷。

Python 執行階段會從 google.appengine.runtime 套件引發 DeadlineExceededError,藉此中斷要求處理常式。如果要求處理常式未能捕捉此例外狀況,則與所有未捕捉到的例外狀況一樣,執行階段環境將傳回 HTTP 500 伺服器錯誤給用戶端。

要求處理常式可以捕捉此錯誤,並自訂回應。執行階段環境會在引發例外狀況後,給予要求處理常式較多時間 (一秒以內) 準備自訂回應。

from google.appengine.runtime import DeadlineExceededError

class MainPage(webapp.RequestHandler):
  def get(self):
    try:
      # Do stuff...

    except DeadlineExceededError:
      self.response.clear()
      self.response.set_status(500)
      self.response.out.write("This operation could not be completed in time...")     

如果處理常式無法在第二個期限內傳回回應或引發例外狀況,處理常式將終止並傳回預設的錯誤回應。

回應一個要求的時間可能長達 30 秒,不過如果應用程式的要求都是短期要求,尤其是只需花費幾百毫秒的要求,則「應用服務引擎」將能展現最佳效能。有效率的應用程式可以快速回應絕大多數的要求。無法快速回應的應用程式,將無法順利地透過「應用服務引擎」的基礎結構擴充。

沙箱

為了讓「應用服務引擎」將應用程式的要求分散到多個網頁伺服器,並防止應用程式彼此干擾,應用程式會在限制的「沙箱」環境中執行。在此環境中,應用程式可以執行程式碼、儲存和查詢「應用服務引擎」資料存放區的資料、使用「應用服務引擎」郵件、URL 擷取和使用者服務以及檢查使用者的網路要求和準備回應。

「應用服務引擎」無法執行下列動作:

  • 寫入檔案系統。應用程式必須使用「應用服務引擎」資料存放區來儲存持續性的資料。「應用服務引擎」可以讀取檔案系統,也可以使用所有與應用程式一起上傳的應用程式檔案。
  • 開啟通訊端或直接存取其他主機。應用程式可以使用「應用服務引擎」URL 擷取服務,讓 HTTP 和 HTTPS 要求分別轉向其他主機的連接埠 80 和 443。
  • 產生子處理程序或執行緒。應用程式的網頁要求必須在單一處理程序中的幾秒內進行處理。如果處理程序回應時間過長,便會終止,以避免網頁伺服器負載過重。
  • 發出其他種類的系統呼叫。

純 Python 環境

Python 執行階段環境使用 Python 2.5.2。

Python 執行階段環境的所有程式碼必須都是 Python,且不包括任何 C 延伸或其他必須編譯的程式碼。

環境包括 Python 標準程式庫。部分模組因為「應用服務引擎」不支援,而被停用,例如網路功能或寫入檔案系統功能。此外,可以使用 os 模組,但是會停用不支援的功能。如果嘗試匯入不支援的模組或使用不支援的功能,便會引發例外狀況。

標準程式庫的幾個模組已被取代或自訂,以便與「應用服務引擎」搭配。例如:

  • cPickle 改名為 pickle。不支援 cPickle 特定的功能。
  • marshal 是空白的。匯入會成功,但是無法使用。
  • 以下模組也是空的:impftplibselectsocket
  • tempfile 已停用,而 TemporaryFile 的別名為 StringIO
  • logging 可以使用,並強烈建議您使用!請參閱以下資訊。

除了 Python 標準程式庫和應用程式庫之外,執行階段環境包括下列第三方程式庫:

您可以在應用程式目錄中放置相關的程式碼,為應用程式加入其他純 Python 程式庫。若在應用程式目錄中製作模組目錄的符號連結,appcfg.py 會根據該連結,將模組包括在您的應用程式中。

Python 模組包括路徑包含應用程式的根目錄 (該目錄包含 app.yaml 檔案)。使用根路徑,則可以使用您在應用程式根目錄中建立的模組。請不要忘記在子目錄中建立 __init__.py 檔案,這樣 Python 會將子目錄視為套件。

應用程式快取

在單一網頁伺服器上,Python 執行階段環境會快取要求之間的匯入模組;就像是獨立的 Python 應用程式僅載入模組一次 (即使該模組被多個檔案匯入)。若處理常式指令碼提供 main() 常式,則執行階段也會快取指令碼。否則,每次要求都會載入該處理常式指令碼一次。

應用程式快取可大幅縮短回應時間。我們建議您針對所有應用程式使用 main() 常式,如下所描述。

匯入會被快取

為了提升效能,網頁伺服器會將匯入的模組保持在記憶體,在相同伺服器之相同應用程式的後續要求中,都不用重新載入或重新評估它們。大部分模組在匯入時,都不會初始化任何全域資料或有任何副作用,因此快取它們不會改變應用程式的行為。

若您的應用程式匯入的模組,在每次要求時都要進行評估,則應用程式必須使用此快取行為。

下列範例示範快取匯入模組的方式。因為單一網頁伺服器只會匯入一次 mymodule,全域 mymodule.counter 只會在伺服器第一次服務要求時初始化為 0。後續的要求會使用之前要求的值。

### mymodule.py
counter = 0
def increment():
  global counter
  counter += 1
  return counter


### myhandler.py
import mymodule

print "Content-Type: text/plain"
print ""
print "My number: " + str(mymodule.increment())

此輸出 My number: #:其中的 # 是此常式被處理要求的網頁伺服器呼叫的次數。

也可以快取常式指令碼

您除了可以讓「應用服務引擎」快取匯入的模組,也可以快取常式指令碼本身。若常式指令碼定義名稱為 main() 的函式,然後指令碼及其全域環境都會被快取,就像匯入的模組一樣。指定網頁伺服器第一次要求該指令碼時,會正常地評估指令碼。針對後續的要求,「應用服務引擎」會壓快取的環境中呼叫 main() 函式。

要快取常式指令碼,「應用服務引擎」必須能夠呼叫沒有引數的 main()。若處理常式指令碼沒有定義 main() 函式,或者 main() 函式需要引數 (預設不需要),則「應用服務引擎」會在每次要求都載入和評估整個指令碼。

將剖析的 Python 程式碼保留在記憶體中,可以節省時間並獲得更快速的回應。快取全域環境還有其他潛在的用途:

  • 已編譯的規則運算式。所有的規則運算式都會被剖析,並以編譯過的格式儲存。您可以將編譯的規則運算式儲存在全域變數,然後使用應用程式快取在每次要求時,重複使用已編譯的物件。
  • GqlQuery 物件。建立 GqlQuery 物件時,會剖析 GQL 查詢字串。與每次重新建構物件相比,以參數繫結重複使用 GqlQuery 物件以及 bind() 方法會更快速。您可以將 GqlQuery 物件與值的參數繫結儲存在全域變數中,然後為每個要求繫結新的參數值來重複使用它。
  • 設定和資料檔案。若您的應用程式會從檔案載入並剖析設定資料,它可以將已剖析的資料保留在記憶體,就不用每次要求時都重新載入。

處理常式指令碼在匯入時應呼叫 main()。「應用服務引擎」認為該指令碼的匯入會呼叫 main(),因此「應用服務引擎」在伺服器上首次載入要求處理常式時,不會呼叫它。

下列範例與之前的範例的目的都相同,使用常式指令碼的全域環境:

### myhandler.py

# A global variable, cached between requests on this web server.
counter = 0

def main():
  global counter
  counter += 1
  print "Content-Type: text/plain"
  print ""
  print "My number: " + str(counter)

if __name__ == "__main__":
  main()

注意:小心不要在要求之間「洩露」使用者的資訊。使用全域變數時,請使用快取功能,並於 main() 常式中初始化要求特定的資料。

快取 main() 的應用程式可大幅縮短應用程式的回應時間。我們建議所有應用程式都使用此方式。

記錄

「應用服務引擎」網頁伺服器會擷取處理常式指令碼寫入標準輸出串流的所有資料,以便回應給網頁要求。它也會擷取處理常式指令碼寫入標準錯誤串流的所有資料,並將它儲存為記錄資料。應用程式的記錄資料可以透過「管理控制台」檢視與分析,也可以透過 appcfg.py request_logs 下載。

「應用服務引擎」Python 執行階段環境,對 Python 標準程式庫的 logging 模組有特殊的支援,以瞭解記錄概念例如記錄等級 (debug - 偵錯、info - 資訊、warning - 警告、error - 錯誤、critical - 重大)。

import logging

from google.appengine.api import users
from google.appengine.ext import db

user = users.get_current_user()
if user:
  q = db.GqlQuery("SELECT * FROM UserPrefs WHERE user = :1", user)
  results = q.fetch(2)
  if len(results) > 1:
    logging.error("more than one UserPrefs object for user %s", str(user))
  if len(results) == 0:
    logging.debug("creating UserPrefs object for user %s", str(user))
    userprefs = UserPrefs(user=user)
    userprefs.put()
  else:
    userprefs = results[0]
else:
  logging.debug("creating dummy UserPrefs for anonymous user")

環境

執行環境包括幾個對應用程式有用的環境變數。部分環境變數是「應用服務引擎」特有的,而其他則為 CGI 標準的一部分。Python 程式碼可以使用 os.environ 字典來存取這些變數。

下列環境變數是「應用服務引擎」特有的:

  • APPLICATION_ID:目前正在執行之應用程式的 ID。
  • CURRENT_VERSION_ID:目前正在執行之應用程式的主要和次要版本,顯示為「X.Y」。應用程式的 app.yaml 檔案會指定主要版本號碼 (「X」)。而應用程式的各個版本上傳到「應用服務引擎」時,會自動設定次要版本號碼 (「Y」)。在開發網頁伺服器上,次要版本是「1」。
  • AUTH_DOMAIN:使用此網域來驗證有 Users API (使用者 API) 的使用者。於 appspot.com 主控的應用服務具有 gmail.comAUTH_DOMAIN,並接受任何 Google 帳戶。使用「Google 應用服務」、於自訂網域主控的應用服務,具有等於自訂網域的 AUTH_DOMAIN

下列環境變數是 CGI 標準的一部分,在「應用服務引擎」中有特殊行為:

  • SERVER_SOFTWARE:在開發網頁伺服器中,此值是 「Development/X.Y」,其中的 「X.Y」是執行階段的版本。

其他環境變數會根據 CGI 標準來設定。如需關於這些變數的詳細資訊,請參閱 CGI 標準

提示:下列 webapp 要求處理常式會在瀏覽器中顯示應用程式可見的所有環境變數:

from google.appengine.ext import webapp
import os

class PrintEnvironmentHandler(webapp.RequestHandler):
  def get(self):
    for name in os.environ.keys():
      self.response.out.write("%s = %s<br />\n" % (name, os.environ[name]))

配額和限制

應用程式的每個連入要求會算入「要求」的配額中。

接收要求而收到的資料會算入「連入頻寬 (可計費)」的配額中。回應要求而傳送的資料會算入「連出頻寬 (可計費)」的配額中。

HTTP 和 HTTPS (安全) 要求會算入「要求」、「連入頻寬 (可計費)」以及「連出頻寬 (可計費)」的配額中。「管理控制台」的 [Quota Details] (配額詳細資訊) 頁面也會另外提供「安全要求」、「安全連入頻寬」以及「安全連出頻寬」的數據,以供參考。只有 HTTPS 要求才會算入這些數據。

執行要求處理常式所花費的 CPU 處理時間會算入「CPU 時間 (可計費)」的配額中。

如需瞭解配額的詳細資訊,請參閱「配額」,或參閱「管理控制台」的「配額詳細資訊」一節。

除了配額之外,使用要求處理常式時也需遵循下列限制:

限制 大小
要求大小 10 MB
回應大小 10 MB
要求持續時間 30 秒
同時動態要求 30 *
應用程式檔案數量上限 1,000
靜態檔案數量上限 1,000
應用程式檔案大小上限 10 MB
靜態檔案大小上限 10 MB
所有應用程式和靜態檔案的總計大小上限 150 MB

* 應用程式可以同時處理大約 30 個作用中的動態要求。這表示如果一個應用程式處理伺服器端要求的平均時間為 75 毫秒,則在沒有發生任何額外延遲的狀況下,每一秒最多可以處理 400 個要求 (1000 毫秒/秒 / 75 毫秒/要求 * 30 = 400)。為挪出空間給其他共用相同伺服器的應用程式,受到 CPU 嚴重限制的應用程式在處理長期執行的要求時,可能會發生額外的延遲。靜態檔案的要求則不會受到此限制的影響。