常见问题解答 - 服务器

  1. 为什么 GWT 不提供同步服务器连接选项?
  2. 什么是同源策略,它如何影响 GWT?
  3. 如果我不使用 GWT RPC,如何调用服务器?
  4. GWT RPC 是否支持 java.io.Serializable?
  5. 如何从其他网络域动态获取 JSON 提要?

为什么 GWT 不提供同步服务器连接选项?

GWT 的网络操作都是异步的,或者是非阻塞的。也就是说,它们在被调用后立即返回,并且要求用户使用回调方法来处理最终从服务器返回的结果。虽然在某些情况下,异步操作的使用不如同步操作方便,但 GWT 不提供同步操作。

原因是大多数浏览器的 JavaScript 引擎是单线程的。因此,阻塞对 XMLHTTPRequest 的调用也会阻塞 UI 线程,使浏览器在连接服务器的持续时间内看起来像是冻结了。一些浏览器提供了一种解决方法,但没有通用的解决方案。GWT 没有实现同步网络连接,因为这样做会引入一个在所有浏览器上都不起作用的功能,违反了 GWT 对无妥协、跨浏览器 AJAX 的承诺。它还会为开发人员带来复杂性,他们必须维护两个不同版本的通信代码来处理所有浏览器。

什么是同源策略,它如何影响 GWT?

现代浏览器实施一种称为同源策略 (SOP) 的安全模型。从概念上讲,它非常简单,但它对 JavaScript 应用程序施加的限制可能非常微妙。

简而言之,SOP 规定运行在网页上的 JavaScript 代码不得与任何非来自同一个网站的资源进行交互。此安全策略存在的理由是防止恶意网页编码人员创建窃取网页用户的信息或损害其隐私的页面。虽然非常必要,但这项策略也具有使网页开发人员生活变得困难的副作用。

需要注意的是,下面描述的 SOP 问题不是 GWT 独有的;它们是任何 AJAX 应用程序或框架的真实情况。本常见问题解答只是详细介绍了 GWT 受此普遍限制影响的具体方式。

SOP 如何影响 GWT

当您编译 GWT 应用程序时,它会生成 JavaScript 代码和 HTML 文件。由于 GWT 应用程序被分成多个文件,因此浏览器将从 HTTP 服务器发出多个请求来获取所有部分。在 GWT 1.4 之前,这意味着所有 GWT 文件都必须与启动 GWT 应用程序的页面位于同一个网页服务器上,以避免违反 SOP。在 GWT 1.4 及更高版本中,由于新的跨站点脚本包含引导过程模式,这不再是问题。

通常,引导过程将从与提供 HTML 主页的同一个网页服务器加载 GWT 应用程序文件。也就是说,假设主机 HTML 文件位于http://mydomain.com/index.html,那么 GWT 应用程序文件也将在与 index.html 文件相同的目录(或子目录)中。要加载 GWT 应用程序,<module>.nocache.js 文件将通过让 index.html 文件包含一个引用<module>.nocache.js 文件的<script> 标签来引导。这是加载 GWT 应用程序的标准引导过程。需要包含在主主机 HTML 页面中的<script> 标签如下所示

<script language="JavaScript" src="http://mydomain.com/<module>.nocache.js"></script>

但是,许多组织以这样一种方式设置其部署平台,即其主 HTML 页面是从 http://mydomain.com/ 提供的,但任何其他资源(如图像和 JavaScript 文件)都从 http://static.mydomain.com/ 下的独立静态服务器提供。在 GWT 的早期版本中,这种配置是不可能的,因为 SOP 阻止 GWT 引导过程允许来自不同服务器添加的文件的脚本访问主 HTML 页面中的 iframe。从 GWT 1.5 开始,引导模型现在通过跨站点链接器 (xs-linker) 为这种服务器配置提供支持。从 GWT 2.1 开始,您应该使用跨站点 iframe 链接器 (xsiframe-linker) 来代替,它会将 GWT 代码沙箱化到 iframe 中,就像标准链接器一样,但与跨站点链接器相反。

使用跨站点链接器或跨站点 iframe 链接器时,编译器仍将生成一个<module>.nocache.js,您需要在 index.html 中引用它。不同之处在于,跨站点链接器生成的<module>.nocache.js 将为您的每个排列链接一个 cache.js 文件,而不是一个 cache.html 文件。

要启用跨站点链接,只需将以下内容添加到您的<module>.gwt.xml 中,并在 index.html 中像往常一样包含对<module>.nocache.js 的引用。

<add-linker name="xsiframe"/>

有关 GWT 引导过程的更多详细信息,请参阅“所有缓存/非缓存内容和奇怪的文件名是怎么回事?”

SOP、GWT 和 XMLHTTPRequest 调用

SOP 最有可能影响 GWT 用户的第二个领域是 XMLHTTPRequest 的使用 - AJAX 的核心。SOP 将 XMLHTTPRequest 浏览器调用限制为与加载主机页面的同一个服务器上的 URL。这意味着无法对与加载页面的服务器不同的网站进行任何 AJAX 样式的请求。例如,您可能希望您的 GWT 应用程序从http://pages.mydomain.com/ 提供,但让它对http://data.mydomain.com/ 的数据发出请求,甚至对同一服务器上的不同端口发出请求,例如http://pages.mydomain.com:8888/。不幸的是,这两种情况都是不可能的,因为 SOP 阻止了它们。

此限制会影响对服务器的所有形式的调用,无论是 JSON、XML-RPC 还是 GWT 自己的 RPC 库。通常,您必须在与 GWT 应用程序相同的服务器上运行 CGI 环境,或者为应用程序的 CGI 调用实施更复杂的解决方案,例如循环 DNS 负载平衡或反向代理机制。

在某些情况下,有可能解决此限制。例如,请参阅“如何从其他网络域动态获取 JSON 提要?”

SOP 和 GWT 开发模式

SOP 适用于所有 GWT 应用程序,无论是在网页服务器上运行的网页(已编译)模式,还是在开发模式下运行。

如果您尝试开发一个使用开发模式不支持的服务器端技术的 GWT 应用程序,例如 EJB 或 Python 代码,这可能会导致问题。在这种情况下,您可以使用-noserver 参数启动开发模式,以便从您选择的服务器技术启动 GWT 应用程序。有关更多信息,请参阅如何在开发模式下使用自己的服务器而不是 GWT 的内置 Jetty 实例? 但是,即使您选择使用此功能,请记住 SOP 仍然适用。

如果我不使用 GWT RPC,如何调用服务器?

AJAX 的核心是从运行在浏览器中的 JavaScript 应用程序向服务器发出数据读/写调用。GWT 是“RPC 无关的”,对用于发出 RPC 请求的协议或服务器代码的编写语言没有任何特殊要求。虽然 GWT 提供了一个类库,使与 J2EE 服务器进行 RPC 通信变得非常容易,但您不需要使用它们。相反,您可以构建自定义 HTTP 请求来检索例如 JSON 或 XML 格式的数据。

要从浏览器与服务器通信而不使用 GWT RPC

  1. 使用浏览器的 XMLHTTPRequest 功能创建与服务器的连接。
  2. 根据您要使用的协议构建您的有效负载,将其转换为字符串,并通过连接将其发送到服务器。
  3. 接收服务器的响应有效负载,并根据协议对其进行解析。

您必须使用异步服务器连接。

com.google.gwt.http.client.html 包

GWT 提供了一个库,可以很大程度地(尽管不是完全地)自动化与服务器通过 HTTP 请求通信所需的步骤。 GWT 应用程序开发人员应该使用RequestBuilder 类和 com.google.gwt.http.client.html 包中的其他类,其中包含一个简洁的异步服务器请求回调实现。

注意: 新用户经常会注意到 com.google.gwt.user.client.HTTPRequest 类并尝试使用它。 但是,HTTPRequest 类已弃用,将在未来的某个时间点删除。 您应该使用 RequestBuilder 代替。

故障排除

如果您尝试使用 RequestBuilder 类并遇到问题,请首先检查您的模块 XML 文件以确保 HTTP 模块被继承,如下所示

<inherits name="com.google.gwt.http.HTTP"/>

了解更多信息

  • 要了解有关 GWT RPC 替代方案的更多信息,请参阅开发者指南,发出 HTTP 请求
  • 有关发出 HTTP 请求以检索 JSON 格式数据的实际示例,请参阅教程,通过 HTTP 获取 JSON
  • 有关可用于构建 HTTP 请求的类和方法的文档,请参阅 API 参考,com.google.gwt.http.client 包。

GWT RPC 是否支持 java.io.Serializable?

GWT RPC 系统确实支持使用 java.io.Serializable,但仅在特定条件下支持。

在 1.4 之前,GWT RPC 机制使用“IsSerializable”标记接口来表示可以序列化的类。 许多用户表示希望重用他们已经编写的使用标准 java.io.Serializable 标记接口的 GWT 代码。 由于这两个接口都是空的标记接口,因此从技术上讲,GWT 的 RPC 机制没有理由不能使用标准 java.io.Serializable。 但是,有一些充分的理由选择不这样做

  • GWT 的序列化语义比标准 Java 序列化要简单得多,因此使用 java.io.Serializable 作为标记接口将意味着 GWT 的序列化系统比实际功能更强大。
  • 相反,GWT 的序列化机制比标准 Java 的更简单,因此使用 java.io.Serializable 将意味着用户需要担心的事情更多(例如序列化版本 ID),而实际上他们不需要担心。
  • GWT 仅实现 Java JRE 类的子集,并且特别没有实现 java.io 中的任何内容。 要使用 java.io.Serializable 作为 GWT RPC 序列化标记接口,将淡化了 java.io 在 GWT 应用程序中不可用的信息。

虽然上述每一点仍然成立,但 GWT 团队认为,社区通常了解这些问题,但更喜欢使用标准 java.io.Serializable 接口的便利性,而不是让他们的类实现 isSerializable 标记接口,尽管这两个标记接口都被 GWT 1.4 及更高版本支持。 考虑到这一点,GWT 团队对 GWT RPC 系统进行了更改,以支持使用 java.io.Serializable 作为将在网络上传输的数据传输对象(通常称为 DTO)。 但是,在新的 GWT RPC 系统中,启用对 java.io.Serializable 的支持有一个条件。

RPC 现在在 GWT 编译期间生成一个序列化策略文件。 序列化策略文件包含允许序列化的类型白名单。 它的名称是一个强哈希名称,后跟 .gwt.rpc。 为了启用对 java.io.Serializable 的支持,您的应用程序将在网络上传输的类型必须包含在序列化策略白名单中。 此外,序列化策略文件必须作为公共资源部署到您的 Web 服务器,可以通过 RemoteServiceServlet 通过 ServletContext.getResource() 访问。 如果未正确部署,RPC 将以 1.3.3 兼容模式运行,并拒绝序列化实现 java.io.Serializable 的类型。

另一个需要注意的重要事项是,在完整的 Java JRE 中实现 java.io.Serializable 的所有类都没有在 GWT 的模拟 JRE 中实现 java.io.Serializable。 这意味着在 JRE 中实现 java.io.Serializable 的类型(如 ThrowableStackTraceElement)将无法通过 GWT RPC 在网络上传输,因为客户端将无法序列化/反序列化它们。 但是,对于其他类型(如 StringNumber 等)来说这不是问题,这些类型在模拟 JRE 中没有实现 java.io.Serializable,但具有自定义字段序列化器,以便它们可以被正确序列化。

在 GWT 的未来版本中可能会解决在实际 JRE 和模拟 JRE 中实现 java.io.Serializable 的类型之间的这些差异,但在目前,如果您的应用程序使用 GWT RPC,这是一个需要牢记的重要事项。

如何动态地从其他 Web 域获取 JSON 提要?

与所有 AJAX 工具一样,GWT 的 HTTP 客户端和 RPC 库也受到限制,只能访问与加载应用程序的同一站点相同的数据,这是由于浏览器的同源策略。 如果您使用的是 JSON,则可以使用 <script> 标签(又名 JSON-P)来解决此限制。

首先,您需要一个外部 JSON 服务,它可以调用用户定义的回调函数,并将 JSON 数据作为参数。 此类服务的一个示例是 GData 的“alt=json-in-script& callback=myCallback”支持。 然后,您可以使用 JsonpRequestBuilder 发出您的调用,其方式类似于在您没有进行跨站点请求时使用 RequestBuilder