请求的处理方式

区域 ID

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

详细了解区域 ID

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

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

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

处理请求

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

当 App Engine 收到针对您应用的 Web 请求时,会调用与网址对应的 servlet,如应用的 WEB-INF/ 目录中的 web.xml 文件中所述。App Engine 支持 Java Servlet 2.5 或 3.1 API 规范,以便将请求数据提供给 servlet 并接受响应数据。

App Engine 运行应用的多个实例,每个实例都有各自用于处理请求的 Web 服务器。任何请求都可以路由到任何实例,因此来自同一用户的连续请求并不一定会发送到同一实例。系统会根据流量的变化自动调整实例数量。

默认情况下,每个 Web 服务器一次仅处理一个请求。如需并行向每个 Web 服务器分派多个请求,请通过向 appengine-web.xml 文件添加 <threadsafe>true</threadsafe> 元素,将应用标记为线程安全。

以下示例 servlet 类在用户浏览器上显示简单消息。

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(name = "requests", description = "Requests: Trivial request", urlPatterns = "/requests")
public class RequestsServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    resp.setContentType("text/plain");
    resp.getWriter().println("Hello, world");
  }
}

配额和限制

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 会中断请求处理程序。 Java 运行时环境通过抛出 com.google.apphosting.api.DeadlineExceededException 来中断 servlet。如果没有请求处理程序来捕获此异常,则运行时环境将向客户端返回 HTTP 500 服务器错误。

如果存在请求处理程序且捕获了 DeadlineExceededException,则运行时环境会为请求处理程序提供时间(少于一秒)以准备自定义响应。如果在引发异常以准备自定义响应后,请求处理程序花费的时间超过一秒,则会引发 HardDeadlineExceededError

DeadlineExceededExceptionsHardDeadlineExceededErrors 都会强行终止请求并停止实例。

如需了解距离时限还剩多少时间,应用可以导入 com.google.apphosting.api.ApiProxy 并调用 ApiProxy.getCurrentEnvironment().getRemainingMillis()。如果应用计划开始处理一些可能需要花费很长时间的工作,这样会非常有用;如果您知道处理一个工作单元需要五秒钟,但是 getRemainingMillis() 返回的时间较少,那么开始这个工作单元是没有意义的。

响应

App Engine 会使用请求对象和响应对象调用 servlet,然后等待 servlet 填充响应对象并返回。当 servlet 返回时,响应对象上的数据将发送给用户。

您生成的响应会受到一些大小限制,而且响应在返回客户端之前可以修改。

如需了解详情,请参阅请求响应参考文档

流式响应

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 不会验证缓存(检查更新的内容)。即使缓存过期,缓存只有在请求网址中的内容发生更改时才会更新。

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

  • Cache-Control 应设置为 public,以便 Google Frontend 缓存内容;除非指定了 Cache-Control privateno-store 指令,否则 Google Frontend 也可能会缓存它。如果您未在 appengine-web.xml 中设置此标头,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 分钟到期时间是合适的。

您可以通过在 appengine-web.xml 文件中指定 static-files 元素,更改所有静态文件和目录处理程序的默认到期时间。

日志记录

您的应用可以使用 java.util.logging.Logger 将信息写入应用日志。您可以使用 Cloud Logging 在 Google Cloud 控制台中查看应用的日志数据。系统会为记录的每个请求分配一个请求 ID,这是一个基于请求开始时间的全局唯一标识符。Google Cloud 控制台可以识别 Logger 类的日志级别,并以交互方式显示不同级别的消息。

App Engine 会捕获 servlet 写入标准输出流 (System.out) 和标准错误流 (System.err) 的所有内容,并记录在应用日志中。写入标准输出流的行记录在“INFO”级别,写入标准错误流的行记录在“WARNING”级别。任何记录到输出流或错误流的日志记录框架(如 log4j)都适用。但是,为了更精细地控制 Google Cloud 控制台中的日志级别显示,日志记录框架必须使用 java.util.logging 适配器。

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(
    name = "RequestLogging",
    description = "Requests: Logging example",
    urlPatterns = "/requests/log"
)
public class LoggingServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(LoggingServlet.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    log.info("An informational message.");
    log.warning("A warning message.");
    log.severe("An error message.");
    // ...
  }
}

App Engine Java SDK 的 appengine-java-sdk/config/user/ 目录中包含模板 logging.properties 文件。如需使用该文件,请将其复制到您的 WEB-INF/classes 目录(或 WAR 中的其他位置),然后将系统属性 java.util.logging.config.file 复制到 "WEB-INF/logging.properties"(或您选择的其他任何一个相对于应用根目录的路径)。您可以在 appengine-web.xml 文件中设置系统属性,如下所示:

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> ... <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties" /> </system-properties> </appengine-web-app>

servlet 会采用 INFO 日志级别(使用 log.info())记录消息。默认日志级别是 WARNING,它的输出中去除了 INFO 消息。 如需更改日志级别,请修改 logging.properties 文件。

环境

所有系统属性和环境变量都是应用专有的。设置系统属性只会影响该属性的应用视图,不会影响到 JVM 的视图。

您可以在部署描述符中为应用设置系统属性和环境变量。

App Engine 会设置多个用于识别运行时环境的系统属性:

  • com.google.appengine.runtime.environment 在 App Engine 中运行时是 "Production",在开发服务器中运行时是 "Development"

    除了使用 System.getProperty() 之外,您还可以使用我们的 type-safe API 访问系统属性。例如:

    if (SystemProperty.environment.value() ==
        SystemProperty.Environment.Value.Production) {
        // The app is running on App Engine...
    }
    
  • com.google.appengine.runtime.version 是该运行时环境的版本 ID,如 "1.3.0"。调用以下方法可获得版本:String version = SystemProperty.version.get();

  • com.google.appengine.application.id 是应用的 ID。调用以下方法可获得 ID:String ID = SystemProperty.applicationId.get();

  • com.google.appengine.application.version 是当前运行的应用服务的主要版本和次要版本,例如“X.Y”。主要版本号(“X”)是在服务的 appengine-web.xml 文件中指定的。次要版本号(“Y”)是在应用的每个版本上传到 App Engine 时自动设置的。调用以下方法可获得 ID:String ID = SystemProperty.applicationVersion.get();

    在开发 Web 服务器上,返回的主要版本始终是默认服务的版本,次要版本则始终是“1”。

当 App Engine 对应用服务器上的 JVM 进行初始化时,还会设置以下系统属性:

  • file.separator
  • path.separator
  • line.separator
  • java.version
  • java.vendor
  • java.vendor.url
  • java.class.version
  • java.specification.version
  • java.specification.vendor
  • java.specification.name
  • java.vm.vendor
  • java.vm.name
  • java.vm.specification.version
  • java.vm.specification.vendor
  • java.vm.specification.name
  • user.dir

实例 ID

您可以使用以下代码检索处理请求的实例的 ID:

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.instance.id")

在生产环境中,已登录的管理员可以在网址中使用 ID:https://INSTANCE_ID-dot-VERSION_ID-dot-SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com。请求将路由到该特定实例。如果实例无法处理请求,会立即返回一个 503 错误。

请求 ID

在请求发出时,您可以保存请求 ID。请求 ID 对于请求是唯一的,以后可用于将请求与该请求的日志相关联。

以下代码展示了如何在请求的上下文中获取请求 ID:

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.runtime.request_log_id")

强制 HTTPS 连接

出于安全考虑,所有应用都应建议客户端使用 https 连接。如需指示浏览器为给定页面或整个网域使用 https 而不是 http,请在响应中设置 Strict-Transport-Security。例如:

Strict-Transport-Security: max-age=31536000; includeSubDomains
如需为应用传送的任何静态内容设置此标头,请将该标头添加到应用的静态文件和目录处理程序

大多数应用框架和 Web 服务器均支持为代码生成的响应设置此标头。如需了解 Spring Boot 中的 Strict-Transport-Security 标头,请参阅 HTTP 严格传输安全协议 (HSTS)

处理异步后台工作

后台工作是指您的应用在传送 HTTP 响应后为请求执行的任何工作。避免在应用中执行后台工作,并审核代码,以确保所有异步操作都会在传送响应之前完成。

对于长时间运行的作业,我们建议使用 Cloud Tasks。使用 Cloud Tasks 时,HTTP 请求会长期有效,并且仅在任何异步工作结束后返回响应。