2013年3月23日星期六

Java Servlet 环境

Java Servlet 环境

App Engine 在安全的“沙盒”环境中使用 Java 6 JVM 来运行 Java 网络应用程序。App Engine 将在此环境中调用应用程序的 servlet 类来处理请求并准备响应。

选择 Java API 版本

在您使用 Java SDK 中的 AppCfg 工具来上传应用程序时,App Engine 知道要为该应用程序使用 Java 运行时环境。
就本文而言,App Engine Java API 只有一个版本。此 API 由随 SDK 附带的 appengine-api-*.jar 表示(其中 * 表示 API 和 SDK 的版本)。通过将此 JAR 加入到应用程序的 WEB-INF/lib/ 目录,选择应用程序使用的 API 的版本。如果发布的新版本 Java 运行时环境引入了与现有应用程序不兼容的更改,则该环境将拥有一个新的版本号。您的应用程序将继续使用先前版本,直到您将 JAR 替换为新版本(来自新版本的 SDK)并重新上传应用程序。

请求和域

App Engine 使用传入请求的域名确定该请求是否适用于应用程序。域名为 application-id.appspot.com 的请求将传送到 ID 为 application-id 的应用程序。每个应用程序免费获得一个 appspot.com 域名。
appspot.com 域还支持 subdomain.application-id.appspot.com 形式的子域,其中,subdomain 可以是域名的某个部分中允许使用的任意字符串(不能是 .)。以这种方式发送到任何子域的请求将传送到应用程序。
您可以使用 Google Apps 设置自定义顶级域。通过使用 Google Apps,您可以将企业域的子域分配给不同应用程序,例如,Google Mail 或 Google 协作平台。也可以将 App Engine 应用程序与子域相关联。为了方便起见,您可以在注册应用程序 ID 时设置 Google Apps 域,或者以后从管理员控制台中进行设置。有关详细信息,请参阅本文
这些网址的请求将全部传送到您在管理员控制台中选择作为默认版本的应用程序版本。每个应用程序版本还具有自己的网址,因此,您可以部署新版本,并在将其作为默认版本之前对其进行测试。除了 appspot.com 域名以外,版本特定的网址还使用应用程序配置文件中的版本标识符,格式如下:version-id.latest.application-id.appspot.com。也可以将子域与版本特定的网址一起使用:subdomain.version-id.latest.application-id.appspot.com
用于请求的域名包含在传送到应用程序的请求数据中。如果希望应用程序根据用于访问它的域名以不同方式进行响应(例如,限制访问某些域或重定向到正式域),您可以从应用程序代码中检查域的请求数据(如 Host 请求标头)并相应地进行响应。

请求和 Servlet

当 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 不支持响应单个请求“流式处理”数据。
如果客户端发送的请求中的 HTTP 标头指示客户端可以接受压缩 (gzip) 内容,App Engine 将自动压缩响应数据并附加相应的响应标头。它使用 Accept-EncodingUser-Agent 请求标头确定客户端能否可靠地接收压缩响应。自定义客户端可通过将 Accept-EncodingUser-Agent 标头值指定为“gzip”,强制进行内容压缩。
如果您在使用管理员帐户登录后访问网站,App Engine 将在响应标头中包含每个请求的统计信息。X-AppEngine-Estimated-CPM-US-Dollars 标头表示与此请求类似的 1,000 个请求估计花费的金额(美元)。X-AppEngine-Resource-Usage 标头表示请求使用的资源,包括服务器端时间、应用程序服务器 CPU 时间以及 API CPU 时间(均以毫秒为单位)。

请求计时器

请求处理程序花有限的时间生成和返回请求响应,通常约为 30 秒。在到达截止时间后,将会中断请求处理程序。
Java 运行时环境通过引发 com.google.apphosting.api.DeadlineExceededException 中断 servlet。如果请求处理程序不捕获此异常,那么和所有未捕获的异常一样,运行时环境将向客户端返回 HTTP 500 服务器错误。
请求处理程序可以捕获此错误来自定义响应。运行时环境在引发异常以便准备自定义响应之后,将为请求处理程序提供更多一点的时间(少于一秒)。
尽管请求最多可以花 30 秒的时间进行响应,但 App Engine 已针对具有短暂请求的应用程序进行了优化,它们通常只需要几百毫秒。高效的应用程序可以快速对大多数请求作出响应。响应缓慢的应用程序无法根据 App Engine 基础架构进行适当扩展。

沙盒

为了使 App Engine 在多个网络服务器中分配应用程序请求以及防止应用程序之间彼此干扰,应用程序将在受限制的“沙盒”环境中运行。在这种环境中,应用程序可以执行代码,在 App Engine 数据存储区中存储和查询数据,使用 App Engine 邮件、网址提取和用户服务以及检查用户的网络请求和准备响应。
App Engine 应用程序无法执行以下操作:
  • 写入到文件系统。应用程序必须使用 App Engine 数据存储区来存储永久性数据。允许从文件系统中读取,并且可以使用随应用程序上传的所有应用程序文件。
  • 打开套接字或直接访问其他主机。应用程序可以使用 App Engine 网址提取服务分别向端口 80 和 443 上的其他主机发出 HTTP 和 HTTPS 请求。
  • 生成子进程或线程。应用程序的网络请求必须在单个进程中处理,并且必须在几秒钟内完成。将终止响应时间很长的进程以免网络服务器过载。
  • 进行其他类型的系统调用。

线程

Java 应用程序无法新建 java.lang.ThreadGroupjava.lang.Thread。这些限制也适用于利用线程的 JRE 类。例如,应用程序无法新建 java.util.concurrent.ThreadPoolExecutorjava.util.Timer。应用程序可以对当前线程执行操作,如 Thread.currentThread().dumpStack()

文件系统

Java 应用程序无法使用任何用于向文件系统写入内容的类,如 java.io.FileWriter。应用程序可以使用诸如 java.io.FileReader 的类从文件系统中读取自己的文件。应用程序也可以通过例如 Class.getResource()ServletContext.getResource() 来访问作为“资源”的自身文件。
只有视为“资源文件”的文件才可以由应用程序通过文件系统访问。默认情况下,WAR 中的所有文件都是“资源文件”。您可以使用 appengine-web.xml 文件将文件从该组中排除出去。

java.lang.System

停用不适用于 App Engine 的 java.lang.System 类的功能。
以下 System 方法在 App Engine 中不起作用:exit()gc()runFinalization()runFinalizersOnExit()
以下 System 方法返回 nullinheritedChannel()console()
应用程序无法提供或直接调用任何本机 JNI 代码。以下 System 方法引发 java.lang.SecurityExceptionload()loadLibrary()setSecurityManager()

反射

允许应用程序对自己的类进行完全、无限制的反射访问。它可以查询任何私有成员,使用 java.lang.reflect.AccessibleObject.setAccessible(),以及读取/设置私有成员。
应用程序还可以对 JRE 和 API 类(如 java.lang.Stringjavax.servlet.http.HttpServletRequest)进行反射。但是,它只可以访问这些类的公共成员,而不可以访问受保护成员或私有成员。
应用程序无法对不属于自己的任何其他类进行反射,也无法使用 setAccessible() 方法来避开这些限制。

自定义类载入

App Engine 完全支持自定义类载入。但请注意,App Engine 将覆盖所有的 ClassLoader,以将相同的权限分配给所有由应用程序载入的类。如果执行自定义类载入,在载入不信任的第三方代码时要小心。

JRE 白名单

对 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 时设置以下系统属性:
  • com.google.appengine.runtime.environment 在 App Engine 中运行时是 "Production",在开发服务器中运行时是 "Development"
  • com.google.appengine.runtime.version 是该运行时环境的版本 ID,如 "1.3.0"
除了使用 System.getProperty() 以外,您还可以使用类型安全 API 访问系统属性。例如:
if (SystemProperty.environment.value() ==
    SystemProperty.Environment.Value.Production) {
    // The app is running on App Engine...
}
当 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

配额和限制

在通信量增加时,Google App Engine 自动为应用程序分配资源以支持很多并发请求。不过,App Engine 为低延迟应用程序(应用程序的请求响应时间不到 1 秒)保留了自动扩展功能。系统限制高延迟应用程序(很多请求的响应时间均超过 1 秒),这些应用程序需要请求取消限制才能处理大量并发动态请求。如果应用程序强烈要求提供较高的吞吐量以处理长时间运行的请求,您可以请求取消并发动态请求限制。绝大多数应用程序不需要请求取消限制。
受到 CPU 严重限制的应用程序还可能产生额外的延迟,以便与同一服务器上的其他应用程序之间有效地共享资源。静态文件请求不受这些延迟限制的影响。
应用程序的每个传入请求将计入请求配额。
作为请求一部分收到的数据将计入传入带宽(可计费)配额。在请求响应中发送的数据将计入传出带宽(可计费)配额。
HTTP 和 HTTPS(安全)请求将计入请求传入带宽(可计费)传出带宽(可计费)配额。管理控制台的“配额详细信息”页还将安全请求安全传入带宽安全传出带宽作为单独的值进行报告以提供相应的信息。仅 HTTPS 请求计入这些值。
执行请求处理程序所花的 CPU 处理时间将计入 CPU 时间(可计费)配额。
有关配额的详细信息,请参阅配额以及管理控制台的“配额详细信息”部分。
除了配额以外,以下限制也适用于请求处理程序:
限制 数量
请求大小 10 MB
响应大小 10 MB
请求持续时间 30 秒
最大文件总数(应用程序文件和静态文件) 3,000 个
应用程序文件的最大大小 10 MB
静态文件的最大大小 10 MB
所有应用程序和静态文件的最大总大小 150 MB

没有评论:

发表评论