App Engine 在安全的“沙盒”环境中使用 Java 6 JVM 来运行 Java 网络应用程序。App Engine 将在此环境中调用应用程序的 servlet 类来处理请求并准备响应。
在您使用 Java SDK 中的 AppCfg 工具来上传应用程序时,App Engine 知道为该应用程序使用 Java 运行时环境。
就本文而言,App Engine Java API 只有一个版本。此 API 由附带 SDK 的 appengine-api-*.jar 表示(其中 * 表示 API 和 SDK 的版本)。通过将此 JAR 加入到应用程序的 WEB-INF/lib/ 目录,选择应用程序使用的 API 的版本。如果发布的新版本 Java 运行时环境引入了与现有应用程序不兼容的更改,则该环境将拥有一个新的版本号。在用新的版本(来自于最新的 SDK)替换 JAR 并重新上传应用程序之前,您的应用程序将继续使用先前的版本。
当 App Engine 收到应用程序的网络请求时,将调用与网址相对应的 servlet,如应用程序的部署描述符(WEB-INF/ 目录中的 web.xml 文件)中所述。它使用 Java Servlet API 向 servlet 提供请求数据,并接受响应数据。
App Engine 使用多个网络服务器运行您的应用程序,并自动调整它所使用的服务器数量以便可靠地处理请求。指定的请求可能会传送到任何服务器,而且可能不是处理先前来自同一用户的请求的服务器。
以下示例 servlet 类在用户浏览器上显示简单消息。
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.getWriter().println("Hello, world");
}
}
App Engine 调用带有请求对象和响应对象的 servlet,然后等待其将填充响应对象并返回。servlet 返回时,响应对象上的数据将发送给用户。
App Engine 不支持向客户端发送数据,在应用程序中执行更多计算,然后发送更多数据。换句话说,App Engine 不支持响应单个请求“流式处理”数据。
如果客户端发送带有指明该客户端可接受压缩(通过 gzip)内容的请求的 HTTP 标头,则 App Engine 会自动压缩该响应数据并附加相应的响应标头。它同时使用 Accept-Encoding 和 User-Agent 请求标头来确定客户端是否可以可靠地接收压缩响应。自定义客户端可通过指定 Accept-Encoding 和 User-Agent 标头(带有“gzip”值)强行压缩内容。
请求处理程序对请求生成和返回响应的时间是有限的,通常约为 30 秒。达到限制时间后,请求处理程序将中断。
Java 运行时环境通过引发 com.google.apphosting.api.DeadlineExceededException 中断 servlet。如果请求处理程序不捕获此异常,那么和所有未捕获的异常一样,运行时环境将向客户端返回 HTTP 500 服务器错误。
请求处理程序可以捕获此错误来自定义响应。运行时环境在引发异常以便准备自定义响应之后,将为请求处理程序提供更多一点的时间(少于一秒)。
虽然请求有 30 秒的时间来响应,但 App Engine 针对应用程序进行过优化,请求时间很短,通常只需要几百毫秒。高效的应用程序对大多数的请求都能够很快作出响应。响应不快的应用程序将不能根据 App Engine 基础结构而进行灵活的调整。
为了使得 App Engine 能够跨多个网络服务器分配对于应用程序的请求,并且防止应用程序彼此干扰,请在受限制的“沙盒”环境中运行应用程序。在这种环境中,该应用程序可执行代码;可存储和查询 App Engine 数据存储区中的数据;可使用 App Engine 邮件、网址抓取和用户服务;可检查用户的网络请求以及准备响应。
App Engine 应用程序无法:
Java 应用程序无法新建 java.lang.ThreadGroup 或 java.lang.Thread。这些限制也适用于利用线程的 JRE 类。例如,应用程序无法新建 java.util.concurrent.ThreadPoolExecutor 或 java.util.Timer。应用程序可以对当前线程执行操作,如 Thread.currentThread().dumpStack()。
Java 应用程序无法使用任何用来写入文件系统的类,如 java.io.FileWriter。应用程序可以使用诸如 java.io.FileReader 的类从文件系统中读取自己的文件。应用程序也可以通过例如 Class.getResource() 或 ServletContext.getResource() 来访问作为“资源”的自身文件。
只有视为“资源文件”的文件才可以由应用程序通过文件系统访问。默认情况下,WAR 中的所有文件都是“资源文件”。您可以使用 appengine-web.xml 文件将文件从该组中排除出去。
禁用不适用于 App Engine 的 java.lang.System 类的功能。
以下 System 方法在 App Engine 中不起作用:exit()、gc()、runFinalization()、runFinalizersOnExit()
以下 System 方法返回 null:inheritedChannel()、console()
应用程序无法提供或直接调用任何本机 JNI 代码。以下 System 方法引发 java.lang.SecurityException:load()、loadLibrary()、setSecurityManager()
允许应用程序对自己的类进行完全、无限制的反射访问。它可以查询任何私有成员,使用 java.lang.reflect.AccessibleObject.setAccessible(),以及读取/设置私有成员。
应用程序还可以对 JRE 和 API 类(如 java.lang.String 和 javax.servlet.http.HttpServletRequest)进行反射。但是,它只可以访问这些类的公共成员,而不可以访问受保护成员或私有成员。
应用程序无法对不属于自己的任何其他类进行反射,也无法使用 setAccessible() 方法来避开这些限制。
App Engine 完全支持自定义类载入。但是请注意,App Engine 将覆盖所有的 ClassLoader,以将相同的权限分配给所有由应用程序载入的类。如果执行自定义类载入,在载入不信任的第三方代码时要小心。
对 Java 标准库(Java 运行时环境或 JRE)中类的访问仅限于 App Engine JRE 白名单中的类。
应用程序可以使用 java.util.logging.Logger 将信息写入应用程序日志。您可以使用管理控制台查看和分析您的应用程序的日志数据,或使用 appcfg.sh request_logs 下载日志数据。管理控制台可以识别 Logger 类的日志级别,并且以交互方式显示不同级别的消息。
servlet 写入标准输出流 (System.out) 和标准错误流 (System.err) 中的所有内容都由 App Engine 捕捉,并记录在应用程序日志中。写入标准输出流的行将以“INFO”级别记录,写入标准错误流的行将以“WARNING”级别记录。任何记录到输出或错误流的日志记录框架(如 log4j)都适用。但为了细化对管理控制台日志级别显示的控制,日志记录框架必须使用 java.util.logging 适配器。
import java.util.logging.Logger;
// ...
public class MyServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(MyServlet.class.getName());
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/classes/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/classes/logging.properties" />
</system-properties>
</appengine-web-app>
Eclipse Google 插件新项目向导将为您创建这些日志记录配置伟建,并自动将它们复制到 WEB-INF/classes/。对于 java.util.logging,必须设置系统属性才能使用此文件。
所有系统属性和环境变量都是应用程序专有的。设置系统属性只会影响该属性的应用程序视图,不会影响到 JVM 的视图。
您可以在部署描述符中为应用程序设置系统属性和环境变量。
App Engine 在应用程序服务器上初始化 JVM 时设置以下系统属性:
file.separatorpath.separatorline.separatorjava.versionjava.vendorjava.vendor.urljava.class.versionjava.specification.versionjava.specification.vendorjava.specification.namejava.vm.vendorjava.vm.namejava.vm.specification.versionjava.vm.specification.vendorjava.vm.specification.nameuser.dir对应用程序的每个传入请求都会计算在请求配额内。
作为请求的一部分所接收的数据会计算在传入带宽(计费)配额内。作为对请求的响应所发送的数据会计算在传出带宽(计费)配额内。
HTTP 和 HTTPS(安全)请求都会计算在请求、传入带宽(计费)和传出带宽(计费)配额内。管理控制台的“配额详细信息”页也出于信息方面的考虑,将安全请求、安全传入带宽和安全传出带宽报告为单独的值。只有 HTTPS 请求计算在这些值内。
执行请求处理程序所花费的 CPU 处理时间会计算在 CPU 时间(计费)配额内。
有关配额的详细信息,请参阅配额,以及管理控制台的“配额详细信息”部分。
除了配额以外,请求处理程序还遵循以下限制i:
| 限制 | 值 |
|---|---|
| 请求大小 | 10 兆字节 |
| 响应大小 | 10 兆字节 |
| 请求持续时间 | 30 秒 |
| 同时动态请求 | 30 |
| 应用程序文件的最大数目 | 1,000 |
| 静态文件的最大数目 | 1,000 |
| 应用程序文件的最大大小 | 10 兆字节 |
| 静态文件的最大大小 | 10 兆字节 |
| 所有应用程序和静态文件的最大总大小 | 150 兆字节 |
* 应用程序可以同时处理约 30 个活动的动态请求。这表示平均服务器端请求处理时间为 75 毫秒的应用程序可以达到最高为(1000 毫秒/秒 / 75 毫秒/请求)* 30 = 400 请求/秒的效率,且没有任何额外滞后时间。很大程度上受 CPU 制约的应用程序在长时间请求中可能会出现一些额外滞后时间,以便为分享相同服务器的其他应用程序腾出空间。对于静态文件的请求不受此限制的影响。