Google Cloud Platform:移动端点





0/5 (0投票)
Google Cloud Platform - 第4部分:移动端点
- Google Cloud Platform:Google App Engine 入门(第一部分)
- Google Cloud Platform:使用 Google App Engine 进行部署(第二部分)
- Google Cloud Platform:用户管理(第三部分)
- Google Cloud Platform:移动端点(第四部分)
- Google Cloud Platform - Google Cloud SQL(第五部分)
- Google Cloud Platform - Google Cloud Datastore(第六部分)
- Google Cloud Platform - Google Cloud Storage(第七部分)
第 4 部分:移动端点
在 90 年代末,当 Web 刚刚兴起时,重点放在构建应用程序上——用户界面是 HTML,数据库通过 Web 服务器访问,等等。支持“多个客户端”意味着支持 IE、Firefox、Opera 和(后来)Chrome。“客户端执行”意味着在浏览器中运行 Javascript,“客户端数据存储”意味着 cookie。近十年时间里,这几乎是所有人遵循的模型。
然后,移动技术达到了临界质量,智能手机、平板电脑以及现在的其他移动设备(Google Glass、“智能手表”以及更多即将出现的设备)开始主导开发人员领域。支持“多个客户端”现在意味着(至少)Android、iOS 以及运行在笔记本电脑、平板电脑甚至台式机上的任何桌面操作系统。“客户端”执行意味着在这些设备上运行的原生应用程序,“客户端数据存储”可以意味着从在设备上运行的 SQLite 到 NoSQL 选项,甚至……有时甚至是功能齐全的 RDBMS。所有这些,同时仍然支持基于浏览器的前端(顺便说一下,它们可能运行在上述设备上)。
支持各种不同前端的需求导致了架构的改变——不再总是浏览器-Web服务器-数据库;相反,开发人员需要(并希望)构建 REST 端点,为数据和逻辑提供单一访问点。现在,HTTP 服务器不再返回 HTML,而是发送 XML 或(越来越多地)JSON 数据,而无需关心哪个客户端正在接收数据,因为这两种都是平台中立的格式。事实上,用 2000 年代初期的说法,Web 服务器已经从客户端层的工具转变为整个应用程序服务器。
然而,构建将 Java 对象转换为 XML 或 JSON 的 Servlet 可能有点痛苦。诚然,互联网上存在大量开源库和工具包来简化这一点,但 Google 认为,如果他们能够增强服务器端基础设施,让开发人员不必不断获取(和更新,等等)这些模块,那将使所有人的生活更轻松。请记住:没有什么能阻止开发人员直接在 Google App Engine 中进行 Servlet 发送 JSON,但在做任何决定之前,请尝试一下 Cloud Endpoints。
云端点
事实上,Google Cloud Endpoints 的大部分价值在于它能够生成客户端库,这些库“隐藏”了 HTTP 的细节,为传输准备数据(将其编组为 JSON 并再次返回)、实际的 HTTP URL 等。诚然,这可能看起来不是一项艰巨的任务——毕竟,只需获取一个开源 JSON 库,也许再获取 Apache HttpClient 库,就可以开始了——但 Google 的 Cloud Endpoints 不仅会为 Java(假设是 Android 前端)生成客户端库,还会为 JavaScript(用于富客户端 Web 应用)和 iOS(适用于所有人最喜欢的水果移动设备)生成客户端库,而且是基于服务器端类的当前源代码来完成的。如果这有点令人困惑,请不要担心:一旦我们看到实际操作过程,它就会变得更有意义。
让我们从头开始。
从头开始
到目前为止,我们的演示一直都是创建一个“问候”服务来打招呼;当我们构建网页时,它生成的是 HTML,但既然我们现在更倾向于“API”路线,我们希望生成 JSON 响应。与其尝试调整早期的基于 Servlet 的代码,不如直接开始一个新项目。最简单的方法是从 Google App Engine SDK 中获取“new_project_template”(它隐藏在“demos”下),并将其完整复制到一个工作目录中进行一些修改。我们称这个项目为“helloapi”,由于它将像第 2 部分的演示一样生成一个 WAR 文件,所以我们暂时将其全部保留在本地,以使事情简单一些。项目模板有一个示例 Servlet 和一个我们不需要的 index.html 文件,所以为了简单起见,把它们删掉(或者不删,如果你想在本地或部署到云端时将它们用作快速验证测试——无论哪种方式,我们都不会再使用它们)。
项目模板还需要一个更改才能使其全部工作:项目根目录中包含的 Ant 脚本需要知道 Google App Engine SDK 的位置
<!-- When you copy this template to your working directories, change the value of appengine.sdk accordingly. You may also want to use Ant's property file feature, allowing each developer to have their own local property file with the path to the SDK from that workspace. --> <property name="appengine.sdk" location="C:/Prg/appengine-java-sdk-1.8.7"/>
此外,还有一个更改是官方可选的,但我个人更喜欢的是更改生成 WAR 内容的输出目录的名称;项目模板将其命名为“www”,但我足够老派,认为 WAR 应该在“war”目录中组装。
<!-- Change if you like e.g. "war" better than "www" for the output --> <property name="war.dir" location="war"/>
就叫我老古董吧。
代码
首先是创建端点本身:一个标准的 Java 类,它公开一个或多个公共方法,这些方法接受零个或多个参数,并返回一个对象或一个对象集合。换句话说,一个非常普通的 Java 对象。
package com.tedneward.appenginedemo; import com.google.api.server.spi.config.Api; import javax.inject.Named; class Message { public String message; public Message(String m) { message = m; } public String getMessage() { return message; } public void setMessage(String value) { message = value; } } public class Greetings { public Message greet(String target) { return new Message( "Hello, " + target + ", from Google Cloud Endpoints!"); } }
“greet”返回一个带有公共字段的对象,而不是一个简单的 String,这可能看起来很奇怪;尽管在这个例子中一个简单的 String 可能会奏效,但一般来说,REST API 会希望返回一个包含多个数据字段的完整数据传输对象(DTO);在某些情况下,这些结构将完全不简单。然而,请注意,这些 DTO 不应该是实际的“领域对象”,而是它们的扁平化版本,旨在方便客户端(请记住,客户端不总是 Java)消费。
端点还没有完成——在 Cloud Endpoints 工具将其识别为端点之前,还需要添加一些东西——但这在很大程度上是练习的“编码”部分的结束。事实上,这就是重点:Cloud Endpoints 功能允许 Java 开发人员专注于编码,而不是 HTTP 或 JSON 的机制。
注解
代码还需要一些东西才能符合 Cloud Endpoint 的世界观。首先,它需要一个“Api”注解(来自 com.google.api.server.spi.config 包)来指示此端点的“名称”(将用作 HTTP URL 路径的一部分)和一个“版本”(出于类似原因)。
package com.tedneward.appenginedemo; import com.google.api.server.spi.config.Api; import javax.inject.Named; class Message { public String message; public Message(String m) { message = m; } public String getMessage() { return message; } public void setMessage(String value) { message = value; } } @Api(name = "helloworld", version = "v1") public class Greetings { public Message greet(String target) { return new Message( "Hello, " + target + ", from Google Cloud Endpoints!"); } }
请注意,“greet”方法需要一个参数,这对于 REST API 来说并不罕见——事实上,这是更常见的情况。然而,REST API 是基于 HTTP 的,这意味着它们要么作为 URL 的一部分,要么作为 JSON 命名参数提交。同样,Cloud Endpoints 的目标是抛开这些区别,所以我们在这里使用 Java 标准的“Named”注解(来自 javax.inject 包)来指示传递的参数来自 API,并且预期与方法参数本身具有相同的名称。
package com.tedneward.appenginedemo; import com.google.api.server.spi.config.Api; import javax.inject.Named; class Message { public String message; public Message(String m) { message = m; } public String getMessage() { return message; } public void setMessage(String value) { message = value; } } @Api(name = "helloworld", version = "v1") public class Greetings { public Message greet(@Named("target") String target) { return new Message( "Hello, " + target + ", from Google Cloud Endpoints!"); } }
"Named
" 注解可以暴露任何名称,但为了保持理智,通常方便将暴露的名称与方法参数保持一致。
这两个包都来自默认项目 Ant 编译类路径中默认包含的库列表之外,因此需要对 Ant 脚本中的“compile”目标进行快速编辑,以包含 <appenginesdk>/lib/opt/user/appengine-endpoints/v1 中的 JAR 文件。
<fileset dir="${appengine.sdk}/lib/opt/user/appengine-endpoints/v1"> <include name="*.jar"/> </fileset>
确保这同时出现在 <javac> 任务和 <copy> 任务中(后者将 JAR 文件复制到组装目标的 WEB-INF/lib 目录中)。目前,那里只有一个 JAR 文件,但 App Engine SDK 在按功能/特性分离 JAR 文件方面做得很好,因此这样 Ant 脚本在 Google 在其中添加更多 JAR 文件或将一个 JAR 文件拆分为组成部分时具有前瞻性。
管理事务
为了使项目成为一个功能齐全的 Google Endpoint,还需要一些管理元素:WEB-INF 目录中需要有关于端点 API 的描述(元数据),为此,我们需要在 WAR 的 web.xml 文件中注册一个 Servlet,该 Servlet 了解我们的端点并能对其进行反射和描述。
web.xml 修复非常简单。
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>HelloAPI</display-name>
<servlet>
<servlet-name>SystemServiceServlet</servlet-name>
<servlet-class>
com.google.api.server.spi.SystemServiceServlet
</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.tedneward.appenginedemo.Greetings</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SystemServiceServlet</servlet-name>
<url-pattern>/_ah/spi/*</url-pattern>
</servlet-mapping>
<!-- the rest as-is -->
</web-app>
当这个 Servlet 被调用时,它将负责生成描述端点所需的元数据;它是下一步的服务器端,即运行一个命令行工具“endpoints”(对于 UNIX 用户是 shell 脚本,对于 Windows 用户是批处理文件),要求它生成一个必须存储在“WEB-INF”目录中的“API”文件。
$ endpoints gen-api-config -o src\WEB-INF com.tedneward.appenginedemo.Greetings Nov 17, 2013 5:51:21 PM com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml INFO: Successfully processed ./war\WEB-INF/appengine-web.xml API configuration written to src\WEB-INF/helloworld-v1.api
如果 WEB-INF 目录中没有这个 API 文件,Endpoints 主机将抛出 500 错误,并在堆栈跟踪中声称 404 错误。并且每次 Greetings 类或其依赖项(如 Message)的公共 API 发生更改时都需要重新生成,所以实际上,这一步应该作为 Ant 脚本的一部分;鉴于这不是一篇关于 Ant 的文章,我将把它留给读者作为练习。
请注意,运行“endpoints”脚本会发现一个有趣的 bug:gen-api-config 未列为其可接受的“动词”之一,但没有其他方法可以生成这些 API 文件。
测试
此时,启动 App Engine 开发 Web 服务器,可以使用“ant runserver”,或者直接使用“dev_appserver”启动。测试这可以通过几种形式进行:一,使用 cURL 访问 URL,但这意味着我们知道端点的 URL 是什么,这在实际操作之前并不明显;或者二,使用 Google Endpoint 内置的 API explorer,通过启动浏览器并将其指向“https://:8080/_ah/api/explorer”,并使用 UI 导航到相关的 API 端点(“helloworld API v1”>“helloworld.greetings.greet”)。填写表单中红色标签的字段(这些是必填参数),点击大的蓝色“Execute”按钮,我们不仅会看到问候响应,还会显示生成它的 HTTP 请求。在这种情况下,URL 是 https://:8888/_ah/api/helloworld/v1/greet/fred,这在分解之前很难直观理解:“/_ah/api”是所有 Endpoint API 的前缀,“helloworld
”和“v1
”都来自 @Api
注解,“greet”是与包含 @Api
注解的类(Greeting
)关联的方法名称,而“fred”是我们的参数。
顺便说一句,当查看 API 浏览器视图时,看到右上角的“OAuth”开关了吗?这是因为通过巧妙地使用注解(在 Javadocs 中查看 Api
类和 ApiAuth
类),我们可以将某些方法标记为需要 OAuth 身份验证才能使用,而将其他方法保留为公共。事实上,Api
注解上有大约二十个不同的字段,允许对端点本身进行一些非常深入的自定义,包括配置 API 配额(对未注册 API 用户施加的限制)。
客户端
同样,所有这些都可以使用 Servlet 和手动转换为 JSON 并再次转换来完成,但真正的优势在于编写我们刚刚构建的 REST API 的前端;“endpoints”命令可以生成一个完整的库,隐藏调用相关 API 的细节。因此,在这种特殊情况下,要生成一个 Android 兼容的库,它是
$ endpoints get-discovery-doc com.tedneward.appenginedemo.Greetings
...生成一个“发现”文档,然后将其第二次馈送给端点,如下所示
$ endpoints get-client-lib ./helloworld-v1-rest.discovery
... 假设一切成功,它将生成一个包含客户端库代码的 .ZIP 文件。这个文件会比预期的要大一些——它旨在成为一个“包含所有组件”的客户端库,这意味着它不仅包含一个带有已编译客户端代码的 JAR 文件,还包含所有该客户端依赖项的 JAR 文件,以及许可证文件和文档。简而言之,它是一个完全可再分发的捆绑包,包含任何客户端可能需要的一切。
摘要
使用 Google Cloud Endpoints 工具包并不能“自动”构建对客户端友好的 REST API——仍然需要从基于 REST 的角度思考,识别资源以及如何通过核心 HTTP 动词对其进行操作——但 Cloud Endpoints 的好处在于开发人员可以专注于 REST 部分,而不是使 REST 在 Java 应用程序中实现所需的所有周边基础设施。而且,如果需要构建的客户端不是 Android、iOS 或 JavaScript 驱动的(Windows8,我正看着你),API 仍然可以通过 HTTP 连接和对 JSON 格式的基本了解来访问。
然而,有时,除了返回问候语之外,API 还希望知道给定客户端调用了它多少次,包括他们调用时的时间戳,这需要数据存储,我们将在下次深入探讨。但与此同时,祝您享受被问候的乐趣,编码愉快!