请求的处理方式

区域 ID

REGION_ID 是 Google 根据您在创建应用时选择的区域分配的缩写代码。此代码不对应于国家/地区或省,尽管某些区域 ID 可能类似于常用国家/地区代码和省代码。对于 2020 年 2 月以后创建的应用,REGION_ID.r 包含在 App Engine 网址中。对于在此日期之前创建的现有应用,网址中的区域 ID 是可选的。

详细了解区域 ID

本文档介绍了 App Engine 应用接收请求和发送响应的方式。

如需了解详情,请参阅请求标头和响应参考

如果您的应用使用多项服务,您可以将请求路由到特定服务或服务特定版本。如需详细了解服务可寻址性,请参阅请求的路由方式

处理请求

应用负责启动 Web 服务器和处理请求。您可以使用支持您的开发语言的任何 Web 框架。

App Engine 运行应用的多个实例,每个实例都有自己的 Web 服务器来处理请求。请求都可以路由到任何实例,因此来自同一用户的连续请求并不一定会发送到同一实例。一个实例可以并行处理多个请求。系统会根据流量的变化自动调整实例数量。您还可以通过在 app.yaml 文件中设置 max_concurrent_requests 元素,更改一个实例可处理的并发请求数量。

当 App Engine 收到针对您的应用的 Web 请求时,它会按应用的 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,
)

如需向每个 Web 服务器并行调度多个请求,请通过向 app.yaml 文件添加 threadsafe: true,将应用标记为线程安全。如果有任何脚本处理程序使用 CGI,则并发请求不可用。

配额和限制

App Engine 会随着流量的增加自动为您的应用分配资源。但是,这一过程会受到下列限制:

  • App Engine 会为低延时应用(应用响应请求的时间少于 1 秒)预留自动扩缩容量。

  • 严重受 CPU 限制的应用还可能发生额外的延时,以便与同一服务器上的其他应用有效地共享资源。静态文件请求不受这些延迟限制的影响。

发送到应用的所有传入请求都会计入请求限额。为响应请求而发送的数据会计入传出带宽(计费)限额。

HTTP 和 HTTPS(安全)请求均计入请求传入带宽(计费)传出带宽(计费)限额。Google Cloud 控制台的“配额详情”页面还会分别列出安全请求次数安全传入带宽安全传出带宽的值,以供参考。仅 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 标头。为保证安全,部分标头在到达应用前由中间代理进行清理或修改。

如需了解详情,请参阅请求标头参考文档

处理请求超时

App Engine 针对那些具有短暂请求寿命(通常为几百毫秒)的应用进行了优化。高效的应用可以对大多数请求作出快速响应。响应缓慢的应用将无法配合 App Engine 的基础架构良好地进行扩缩。为了确保此性能水平,系统施加了请求超时上限,每个应用都必须在此时间内响应。

如果您的应用超过此截止时间,App Engine 会中断请求处理程序。 Python 运行时环境通过从 google.appengine.runtime 抛出 DeadlineExceededError 异常来实现这一点。如果请求处理程序不捕获此异常,那么和所有未捕获的异常一样,运行时环境将向客户端返回 HTTP 500 服务器错误。

请求处理程序可以捕获此错误以自定义响应。运行时环境在引发异常之后,会为请求处理程序提供更多一点的时间(少于一秒),以准备自定义响应。

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 Frontend 缓存来自 App Engine 静态文件和目录处理程序的响应。根据不同的因素(例如,首先缓存的响应数据类型、您在响应中指定的 Vary 标头以及请求中包含的标头),客户端可以请求压缩的数据但接收未压缩的数据,反之亦然。如需了解详情,请参阅响应缓存

响应缓存

Google Frontend 以及用户的浏览器和其他中间缓存代理服务器将按照您在响应中指定的标准缓存标头来缓存应用的响应。您可以通过框架在代码中直接指定这些响应标头,也可以通过 App Engine 静态文件和目录处理程序指定这些响应标头。

在 Google Frontend 中,缓存键是请求的完整网址。

缓存静态内容

如需确保客户端可始终收到发布的静态内容更新,我们建议您从版本化目录(例如 css/v1/styles.css)传送静态内容。在缓存过期之前,Google Frontend 不会验证缓存(检查更新的内容)。即使缓存过期,缓存只有在请求网址中的内容发生更改时才会更新。

app.yaml 中设置的以下响应标头会影响 Google Frontend 缓存内容的方式和时间:

  • Cache-Control 应设置为 public,以便 Google Frontend 缓存内容;除非指定了 Cache-Control privateno-store 指令,否则 Google Frontend 也可能会缓存它。如果您未在 app.yaml 中设置此标头,App Engine 会自动为由静态文件或目录处理程序处理的所有响应添加此标头。如需了解详情,请参阅添加或替换标头

  • Vary:如需使缓存根据请求中发送的标头返回网址的不同响应,请在 Vary 响应标头设置以下一个或多个值:AcceptAccept-EncodingOriginX-Origin

    由于可能会产生高基数,因此系统不会为其他 Vary 值缓存数据。

    例如:

    1. 您指定了以下响应标头:

      Vary: Accept-Encoding

    2. 应用收到的请求包含 Accept-Encoding: gzip 标头。App Engine 返回压缩响应,并且 Google Frontend 会缓存响应数据的 Gzip 版本。包含 Accept-Encoding: gzip 标头的网址的所有后续请求都将接收来自缓存的 Gzip 数据,直到缓存失效(由于内容在缓存过期后会发生变化)。

    3. 应用收到的请求不包含 Accept-Encoding 标头。App Engine 返回未压缩的响应,并且 Google Frontend 会缓存响应数据的未压缩版本。不包含 Accept-Encoding 标头的网址所有后续请求都会接收来自缓存的压缩数据,直到缓存失效为止。

    如果您未指定 Vary 响应标头,则 Google Frontend 将为网址创建一个缓存条目,并将其用于所有请求(无论请求中的标头如何)。例如:

    1. 您未指定 Vary: Accept-Encoding 响应标头。
    2. 请求包含 Accept-Encoding: gzip 标头,系统会缓存响应数据的 Gzip 版本。
    3. 第二个请求不包含 Accept-Encoding: gzip 标头。但是,由于缓存包含响应数据的 Gzip 版本,因此,即使客户端请求未压缩的数据,该响应也会经过 Gzip 压缩。

请求中的标头也会影响缓存:

  • 如果请求包含 Authorization 标头,则 Google Frontend 不会缓存该内容。

缓存到期

默认情况下,App Engine 静态文件和目录处理程序添加到响应的缓存标头会指示客户端和 Web 代理(如 Google Frontend)使缓存在 10 分钟后过期。

在传输指定了到期时间的文件后,即使用户清除了自己的浏览器缓存,通常也无法将文件从 Web 代理缓存中清除。重新部署应用的新版本将不会重置任何缓存。因此,如果您打算修改某个静态文件,则应该设置较短的到期时间(不超过一个小时)。在大多数情况下,默认的 10 分钟到期时间是合适的。

您可以通过在 app.yaml 文件中指定 default_expiration 元素,更改所有静态文件和目录处理程序的默认到期时间。如需为个别处理程序设置特定的到期时间,请在 app.yaml 文件中的处理程序元素内指定 expiration 元素。

您在到期元素时间中指定的值将用于设置 Cache-ControlExpires HTTP 响应标头。

应用缓存

Python 运行时环境会在单个 Web 服务器上的请求之间对导入的模块进行缓存,类似于独立的 Python 应用仅加载一次模块的方式(即使模块由多个文件导入)。由于 WSGI 处理程序是模块,因此它们会在请求之间进行缓存。CGI 处理程序脚本只有在提供了 main() 例程时才会被缓存;否则,系统会为每个请求加载 CGI 处理程序脚本。

应用缓存在响应时间方面有明显的优势。我们建议所有 CGI 处理程序脚本都使用 main() 例程,如下所述。

缓存导入的内容

为了提高效率,网络服务器会将导入的模块保存在内存中,并且对于同一服务器上的相同应用的后续请求,就不再重新加载或重新评估这些模块。大多数模块在导入时不会初始化任何全局数据或产生其他副作用,因此缓存它们不会改变应用的行为。

如果应用导入的模块依赖于针对每个请求进行评估的模块,则应用必须调整该缓存行为。

缓存 CGI 处理程序

除了导入的模块之外,您还可以让 App Engine 缓存 CGI 处理程序脚本本身。如果处理程序脚本定义了一个名为 main() 的函数,则脚本及其全局环境将像导入的模块一样进行缓存。给定 Web 服务器上的脚本的第一个请求通常会评估脚本。对于后续请求,App Engine 会在缓存环境中调用 main() 函数。

如需缓存处理程序脚本,App Engine 必须能够调用不带参数的 main()。如果处理程序脚本没有定义 main() 函数,或 main() 函数需要参数(没有默认值),则 App Engine 将针对每个请求加载和评估整个脚本。

将解析的 Python 代码保存在内存中可节省时间并加快响应的速度。缓存全局环境也有其他潜在作用:

  • 编译的正则表达式。所有正则表达式都以编译的形式进行解析和存储。您可以在全局变量中存储已编译的正则表达式,然后使用应用缓存在请求之间重复使用已编译的对象。

  • GqlQuery 对象。创建 GqlQuery 对象时,系统会解析 GQL 查询字符串。通过参数绑定和 bind() 方法重复使用 GqlQuery 对象比每次重新构造对象速度要快。您可为全局变量中的值存储具有参数绑定的 GqlQuery 对象,然后通过对每个请求绑定新参数值来重复使用该对象。

  • 配置和数据文件。如果应用加载和解析来自文件的配置数据,则可以将已解析的数据保留在内存中,以免每次请求都重新加载文件。

处理程序脚本应在导入时调用 main()。App Engine 希望导入脚本将调用 main(),因此 App Engine 首次在服务器上加载请求处理程序时不会调用它。

使用 main() 进行应用缓存可显著缩短 CGI 处理程序的响应时间。建议将其用于所有使用 CGI 的应用。

日志记录

App Engine Web 服务器会捕获处理程序脚本为响应 Web 请求而写入标准输出流的所有内容。它还会捕获处理程序脚本写入标准错误流的所有内容,并将其存储为日志数据。系统会为每个请求分配一个 request_id,这是一个基于请求开始时间的全局唯一标识符。您可以使用 Cloud Logging 在 Google Cloud 控制台中查看应用的日志数据。

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 文件中指定的。次要版本号(“Y”)是在应用的每个版本上传到 App Engine 时自动设置的。在开发 Web 服务器上,次要版本始终为“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。请求 ID 对于请求是唯一的。稍后可以使用请求 ID 在 Cloud Logging 中查找相应请求的日志。

以下示例代码展示了如何在请求的上下文中获取请求 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 请求会长期有效,并且仅在任何异步工作结束后返回响应。