App Engine 使用预先载入的 Python 解释器在安全的“沙盒”环境中执行 Python 应用程序代码。应用程序收到网络请求、执行操作并通过与此环境交互来发送响应。
使用 Python SDK 中名为 appcfg.py 的工具(其配置文件名为 app.yaml)时,App Engine 知道为应用程序代码使用 Python 运行时环境。使用以下配置元素来选择 Python 运行时环境:
runtime: python api_version: 1
第一个元素 runtime 选择 Python 运行时环境。就本文而言,App Engine 仅支持 python 这一种运行时环境。
第二个元素 api_version 选择要使用的 Python 运行时环境的版本。就本文而言,App Engine 仅有 1 这一个 Python 环境的版本。如果 App Engine 团队需要发布对可能与现有代码不兼容的环境的更改,他们将使用新版本的标识符来发布。应用程序将继续使用所选择的版本,直到您更改 api_version 设置并上传应用程序。
有关 app.yaml 和 appcfg.py 的详细信息,请参阅 Python 应用程序配置和上传应用程序。
App Engine 在收到您的应用程序的网络请求时,会调用与此网址相对应的处理程序脚本(如应用程序的 app.yaml 配置文件中所述)。App Engine 会使用 CGI 标准将请求数据传递到处理程序,并接收响应。
App Engine 使用多个网络服务器运行您的应用程序,并自动调整它所使用的服务器数量以便可靠地处理请求。指定的请求可能会传送到任何服务器,而且可能不是处理先前来自同一用户的请求的服务器。
服务器通过将请求网址与应用程序配置文件中的网址模式进行比较来确定要运行的 Python 处理程序脚本。然后,它将在填充了请求数据的 CGI 环境中运行处理程序。正如 CGI 标准中所述,服务器会把请求数据置于环境变量和标准输入流中。脚本会执行请求所需的操作,然后准备响应并将响应置于标准输出流上。
大多数应用程序会使用一个库来解析 CGI 请求以及返回 CGI 响应,例如,来自 Python 标准库的 cgi 模块或了解 CGI 协议的网络框架(例如 webapp)。要了解有关环境变量和输入流数据格式的详细信息,可以参考 CGI 文档。
以下示例处理程序脚本显示了用户浏览器上的消息。它会把标识消息类型和消息内容的 HTTP 标头打印到标准输出流中。
print "Content-Type: text/plain" print "" print "Hello, world!"
App Engine 会收集请求处理程序脚本写入标准输出流的所有数据,然后等待该脚本退出。脚本退出后,所有输出数据均会发送至用户。
App Engine 不支持在退出处理程序前将数据发送至用户的浏览器。有些网络服务器将在一段时间内使用该技术将数据“流式处理”到用户的浏览器,以对单个请求做出响应。App Engine 不支持这种流式处理技术。
如果客户端发送带有指明该客户端可接受压缩(通过 gzip)内容的请求的 HTTP 标头,则 App Engine 会自动压缩该响应数据并附加相应的响应标头。它同时使用 Accept-Encoding 和 User-Agent 请求标头来确定客户端是否可以可靠地接收压缩响应。自定义客户端可通过指定 Accept-Encoding 和 User-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 秒的时间来响应,但 App Engine 针对应用程序进行过优化,请求时间很短,通常只需要几百毫秒。高效的应用程序对大多数的请求都能够很快作出响应。响应不快的应用程序将不能根据 App Engine 基础结构而进行灵活的调整。
为了使得 App Engine 能够跨多个网络服务器分配对于应用程序的请求,并且防止应用程序彼此干扰,请在受限制的“沙盒”环境中运行应用程序。在这种环境中,该应用程序可执行代码;可存储和查询 App Engine 数据存储区中的数据;可使用 App Engine 邮件、网址抓取和用户服务;可检查用户的网络请求以及准备响应。
App Engine 应用程序无法:
Python 运行时环境使用 Python 2.5.2。
适用于 Python 运行时环境的所有代码必须是纯 Python,且不包括任何 C 扩展程序或其他必须编译的代码。
该环境包括 Python 标准库。有些模块已被禁用,因为 App Engine 不支持其核心函数(例如,联网或写入到文件系统)。此外,os 模块可用,但其不支持的功能被禁用。尝试导入不支持的模块或使用不支持的功能会引发异常。
标准库中的几个模块已替换掉,或已经过自定义,可以与 App Engine 配合使用。例如:
TemporaryFile(又名 StringIO)除外。除了 Python 标准库和 App Engine 库之外,运行时环境还包括以下第三方库:
您可以通过将代码置于您的应用程序目录中以将其他纯 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: #,其中 # 是处理请求的网络服务器调用该处理程序的次数。
您可以让 App Engine 除缓存导入模块之外,还对处理程序脚本本身进行缓存。如果处理程序脚本定义了一个名为 main() 的函数,则会缓存脚本及其全局环境,就像缓存导入的模块一样。指定网络服务器上脚本的第一个请求会正常评估脚本。对于后续请求,App Engine 则会调用缓存的环境中的 main() 函数。
要缓存处理程序脚本,App Engine 必须能够调用不带参数的 main()。如果处理程序脚本没有定义 main() 函数,或 main() 函数需要参数(没有默认值),则 App Engine 将针对每个请求加载和评估整个脚本。
将解析的 Python 代码保留在内存中可节省时间并加快响应的速度。缓存全局环境也有其他潜在作用:
处理程序脚本应在导入时调用 main()。App Engine 希望导入脚本将调用 main(),所以 App Engine 首次在服务器上载入请求处理程序时不调用它。
以下示例使用处理程序脚本的全局环境的缓存,可实现与前面的示例相同的操作:
### 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() 的应用程序缓存在应用程序的响应时间方面有明显改善。我们建议将其用于所有应用程序。
App Engine 网络服务器会捕捉处理程序脚本写入标准输出流,以响应网络请求的所有内容。它还会捕捉处理程序脚本写入标准错误流的所有内容,并将其存储为日志数据。您可以使用管理控制台查看和分析您的应用程序的日志数据,或使用 appcfg.py request_logs 下载日志数据。
App Engine Python 运行时环境包括对日志模块的特殊支持,请从 Python 标准库了解日志概念,例如日志级别(“调试”、“信息”、“警告”、“错误”、“严重”)。
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")
执行环境包含多个对应用程序有用的环境变量。这些环境变量中有一些是 App Engine 特有的,而其他的则是 CGI 标准的一部分。Python 代码可使用 os.environ 参照表访问这些变量。
以下环境变量是 App Engine 特有的:
APPLICATION_ID:当前运行的应用程序的 ID。CURRENT_VERSION_ID:当前运行的应用程序的主要版本和次要版本,表示为“X.Y”。在应用程序的 app.yaml 文件中指定了主要版本号(“X”)。将应用程序的每个版本上传到 App Engine 时,都会自动设置次要版本号(“Y”)。在开发网络服务器上,次要版本号一律为“1”。 AUTH_DOMAIN:用于通过用户 API 验证用户的域。在 appspot.com 上托管的应用程序具有 gmail.com 的 AUTH_DOMAIN,并且可以接受任何 Google 帐户。通过使用 Google 企业应用套件在自定义域上托管的应用程序具有 AUTH_DOMAIN(相当于该自定义域)。以下环境变量属于 CGI 标准的一部分,且在 App Engine 中具有特殊的行为:
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(安全)请求都会计算在请求、传入带宽(计费)和传出带宽(计费)配额内。管理控制台的“配额详细信息”页也出于信息方面的考虑,将安全请求、安全传入带宽和安全传出带宽报告为单独的值。只有 HTTPS 请求计算在这些值内。
执行请求处理程序所花费的 CPU 处理时间会计算在 CPU 时间(计费)配额内。
有关配额的详细信息,请参阅配额,以及管理控制台的“配额详细信息”部分。
除了配额以外,请求处理程序还遵循以下限制i:
| 限制 | 值 |
|---|---|
| 请求大小 | 10 兆字节 |
| 响应大小 | 10 兆字节 |
| 请求持续时间 | 30 秒 |
| 同时动态请求 | 30 |
| 应用程序文件的最大数目 | 1,000 |
| 静态文件的最大数目 | 1,000 |
| 应用程序文件的最大大小 | 10 兆字节 |
| 静态文件的最大大小 | 10 兆字节 |
| 所有应用程序和静态文件的最大总大小 | 150 兆字节 |
* 应用程序可以同时处理约 30 个活动的动态请求。这表示平均服务器端请求处理时间为 75 毫秒的应用程序可以达到最高为(1000 毫秒/秒 / 75 毫秒/请求)* 30 = 400 请求/秒的效率,且没有任何额外滞后时间。很大程度上受 CPU 制约的应用程序在长时间请求中可能会出现一些额外滞后时间,以便为分享相同服务器的其他应用程序腾出空间。对于静态文件的请求不受此限制的影响。