本文假设您已下载了 Project Zero ,并且完成了 初级教程 或自己已经编写了一个简单的应用程序。您还应该熟悉 developerWorks 教程 Create a WSDM interface for an HTTP server using Apache Muse,此教程讲述如何使用 Apache Muse 和 WS-* 规范创建 HTTP 服务器的管理界面。本文不要求您是一位 Muse 或 WS-* 技术的专家用户,只要求您理解此教程正尝试解决的问题以及代码所提供的功能。
本文并不是要列举 WS-* 技术与面向 REST 技术的优缺点,也不是为了在两者之间选择一个 “胜利者”。本文的目标是阐明 REST 和 Web 2.0 开发技术是否能为系统管理项目提供一种高生产力的选择,并希望为开发人员提供一些附加选择。WS-* 用户和 REST 用户正在就哪种技术最适合哪个问题集进行争论,而 WS-* 用户总是声称比较复杂的企业级问题不能用 REST 的方式解决。
我们创建的用于管理 HTTP 服务器的原始 Web 服务界面使用 WSDL 1.1 文档来公开一组属性 和操作。属性是名称-值对,可以使用由 WS-ResourceProperties (WS-RP) 规范定义的操作来访问。一些实际属性由 WS-* 规范(比如 WS-DistributedManagement 或 WSDM)定义,而其他属性是根据 HTTP 服务器资源自定义的。我们使用的完整属性清单如下:
-
ResourceId(WSDM) -
ManageabilityCapability(WSDM) -
Description(WSDM) -
Caption(WSDM) -
Version(WSDM) -
OperationalStatus(WSDM) -
Name(custom) -
Port(custom) -
ThreadsPerChild(custom)
界面中包括的操作来自 WS-RP,主要用于读取并写入一个或多个属性值。我们还有两个名为 start 和 Stop 的自定义操作,用于启动和停止 HTTP 服务器。所有的 WS-RP 操作由 Apache Muse 框架提供,而自定义操作通过 Java™ 代码实现并使用 Apache Muse 扩展点来包括。我们使用的完整操作清单如下:
-
GetResourceProperty(WS-RP) -
GetResourcePropertyDocument(WS-RP) -
GetMultipleResourceProperties(WS-RP) -
Start(custom) -
Stop(custom)
您可能想到了,我们没有包括来自 WS-RP 的任何 “写” 操作,因为我们没有任何想直接更改的属性。一些属性是可变的,但仅作为副作用(例如,当服务器启动或停止时,OperationalStatus 属性可能会更改)。
将 WS-ResourceProperties 映射到 REST
RESTful 编程依靠网络传输层(通常是 HTTP)的规则和惯例以及一些众所周知的数据格式(通常是 XML 或 JSON)。要以 RESTful 方式公开资源属性中的信息,我们将使用 HTTP GET 方法检索 JSON 对象,此对象具有与资源属性相似的字段(名称-值对)。换句话说,我们正使用 JSON 对象替代资源属性文档,并使用 HTTP 方法替代 WS-RP 方法。清单 1 展示 HTTP 服务器资源的 JSON 表示的可能形式:
清单 1. HTTP 服务器的 JSON 表示
{
"name": "server1.ibm.com",
"port": 8080,
"threads": 250,
"admin": "admin@us.ibm.com"
}
|
您可能注意到,在我们的 JSON 表示中不存在 WSDM 属性。一些 WS-* 概念没有在 RESTful 设计中转换,或者出现在其他应用程序领域中。例如,不需要 WSDM ResourceId 属性和隐式资源模式;不使用端点引用 和 ResourceId 值代替定义资源,而是使用 URI 模式并强制所有资源具有惟一的 URI。通过为一部分资源界面重复使用 HTTP,我们消除了对一些属性和操作的使用(WS-* 用户可能指出我们将自己限制到 HTTP, 但是争论其好坏已经超出了本文的范围)。我们还将使用描述性 URI 并提供易于阅读的服务文档,这与 WSDL 文档中嵌入的一些描述性属性形成对比。假设我们现在有一个面向 HTTP 的解决方案,那么基本要求就是我们的 JSON 表示要尽量完整。
要使我们的 HTTP 服务器资源易于发现和操作,我们将使用
/httpd/{serverName} 的 URI 模式来区别它们。/httpd URI 将引用此应用程序托管的所有 HTTP 服务器的集合,/httpd/{serverName} URI 将引用单个 HTTP 服务器。发送到集合 URI 的请求将针对整个集合进行评估(如果可能的话),而发送到单个服务器 URI 的请求将被限制到它所表示的服务器中。表 1 显示将描述 HTTP 服务器资源的 REST API:
表 1. HTTP 服务器资源的 REST API
| 方法 | URI | 格式 | 结果 |
|---|---|---|---|
| GET | /httpd | text/json | 表示由此应用程序托管的服务器的名称列表。 |
| GET | /httpd/{serverName} | text/json | 包含特定服务器的配置数据的 JSON 对象。 |
如果想扩展我们的界面以允许用户直接修改这些属性,我们将使用 HTTP 的 PUT 而不是 WS-RP 的 SetResourceProperties。PUT 请求的 URI 模式将与 GET 的 URI 模式相同,主要的差别在于 PUT 请求发送 JSON 数据,而 GET 请求接受数据。
自定义操作 Start 和 Stop 为设计提出了很大的挑战。我们只有四种 HTTP 方法可供使用(GET、PUT、POST 和 DELETE),而且 REST Land 认为将操作名称嵌入到 PUT 或 POST 请求中不是一种好的方法。要解决此问题,需要更改考虑启动和停止服务器行为的方式;不是将其当作针对 HTTP 服务器安装的一个操作,而是将其当作 HTTP 服务器进程 的创建或销毁。这并不是一种延伸,因为这正是操作系统处理启动和停止请求的方式:它创建可以监控和(最终)终止的进程。我们将使用它自己的一套 URI 和 HTTP 语义来创建 HTTP 服务器进程资源。表 2 显示这个新资源类型的 REST API:
表 2. HTTP 进程资源的 REST API
| 方法 | URI | 格式 | 结果 |
|---|---|---|---|
| GET | /httpd/{serverName}/process | 无 | 如果服务器正在运行,是 200 OK ,否则是 404 Not Found。 |
| POST | /httpd/{serverName}/process | 无 | 启动服务器并返回 201 Created。 |
| DELETE | /httpd/{serverName}/process | 无 | 停止服务器并返回 204 No Content。 |
正如您所看到的,HTTP 进程资源没有 JSON 表示 — 它只是一个 URI。将 HTTP POST 发送到 URI 将创建一个进程(“启动服务器”),而 HTTP DELETE 将销毁进程(“停止服务器”),HTTP GET 将提供状态(类似于我们不再需要的 WSDM 属性 OperationalStatus)。将我们的界面划分为两种不同的资源类型允许我们在无需求助 RPC 或 WS-* 类技术情况下实现相同的功能。在下一节中,我们将会看到如何将此设计决策转化为真正工作的代码。
编写代码来实现我们在上一节创建的 RESTful 界面将十分简单,尤其是如果您在前一教程中了解了基于 Muse 的实现。对于此项目,我们将使用 Groovy 脚本而非 Java 类,RESTdoc 注释而非 WSDL 文件,JSON 类型而非 XML 类型。
开始编码前,应该确保 Apache HTTP 服务器 (httpd) 已安装并正常工作。如果您没有学完前一教程且没有安装 httpd,参考资料 一节中具有到 Apache 网站的链接,您可以从此处下载此服务器及其安装说明。确保您已经安装此服务器作为服务(或后台守护程序);如果不确定是否有服务,只需在命令行键入 httpd -k install,如果服务尚不存在,则将创建一个服务。安装 httpd 作为服务将允许我们从代码中异步启动和停止服务器。
安装并准备好 httpd 之后,需要创建一个 Zero 项目来存放代码。使用清单 2 中的命令行创建名为 zero.httpd 的项目;默认情况下,项目将包含对 Zero Core 和 RESTdoc 工具的依赖,这就是我在本文中要编写的代码所需的一切。如果您想跳过此过程而查看已完成的项目,您可以从 下载 一节中下载。
清单 2. 创建示例项目
$ zero create zero.httpd
|
在编写任何代码之前,需要决定代码将如何找到 httpd 安装,以便可以读取其中的文件并对其执行命令。在前一教程中,我们使用 muse.xml 文件创建包含 httpd 安装路径的初始化参数。然后,我们可以创建到 httpd 文件的路径,而无需将安装数据硬编码到 Java 源文件中。我们将使用 Zero 执行相似的方法,此时使用它的 zero.config 文件创建服务器到安装目录的映射;服务器名称可以是任何字母数字 ID,并将用于为 HTTP 服务器资源创建具体的 URI。清单 3 显示可用作起点的示例 zero.config 文件:
清单 3. 配置 HTTP 服务器安装
[/config/httpd/servers[]]
server1=/httpd/server1
buildServer=/dev/builds/httpd
intranetSite=/public/prod/apache
[/config/http]
port=9080
|
在清单 3 中,服务器名称以黑体突出显示。我们将能够使用 Zero 全局上下文来访问 Groovy 代码中的这组名称-值对。为了简单起见,我们将为所有的测试使用 server1。您应该将 server1 的安装路径值修改为您的本地 httpd 安装使用的安装路径值。您不需要另外两个服务器条目 — 它们仅用于阐明我们的配置格式。
在保存和关闭 zero.config 文件之前,应该为清单 3 所示的 /config/http/port 添加相同的条目。此条目将使用值 9080 覆盖 Zero 的默认端口 8080;httpd 也具有默认端口 8080,而我们不希望在 Zero 应用程序和 httpd 进程之间发生任何冲突 。
现在应该创建三个文件:httpd.groovy、process.groovy 和 process.bnd,并将其添加到项目的 /app/resources 目录中。前两个文件是 Groovy 脚本,它们将表示上一节中的两个 RESTful 资源。第三个文件将用于配置资源允许的 RESTful URI 模式。
让我们从最容易的任务开始:process.bnd 文件。您仅需要将一行添加到此文件中,如清单 4 所示。此行告诉 Zero 运行时应该允许我们在 REST 表中归档的 /httpd/{id}/process URI 模式。如果没有这一行,Zero 将允许 /httpd/{id} 和 /process/{id} 的请求,但绝不允许 /httpd/{id}/process。
清单 4. 创建 BND 文件
httpd/process
|
httpd.groovy 文件表示我们的 httpd 安装 并需要两种方法:onList() 和 onRetrieve()。前者将返回 Zero 应用程序托管的 httpd 安装清单,后者将返回单个 httpd 安装的 JSON 表示。我们的界面目前不支持 httpd 安装的远程修改,因此我不需要任何其他的 RESTful 方法(onCreate() 等)。
清单 5 显示 onList() 和 onRetrieve() 的代码。onRetrieve() 的实现使用名为 readConfig() 的方法解析 httpd.conf 文件;此方法中的解析代码是从前一教程复制过来的,并稍微进行了一些修改,以获得更类似 Groovy 的语法。通过下载已完成的示例项目并查看 /app/resources/httpd.groovy,可以看到 readConfig() 的实现。
清单 5. 创建 httpd.groovy
def onList()
{
request.view = "JSON";
request.json.output = config.httpd.servers[0].keySet();
render();
}
def onRetrieve()
{
def serverConfig = readConfig();
if (serverConfig.isEmpty())
{
request.status = 404;
return;
}
request.view = "JSON";
request.json.output = [
name: serverConfig.ServerName,
port: serverConfig.Listen,
threads: serverConfig.ThreadsPerChild,
admin: serverConfig.ServerAdmin
];
render();
}
|
清单 5 显示我们挑选的 httpd 配置属性,这些属性是在创建 JSON 响应数据时要公开的属性。正如在 WS-* 版本中,我们有更通用的属性名称(因此界面并不特定于 Apache),而且我们忽略了在安全性方面比较敏感的内容。还要注意,我们使用简单的 HTTP 状态代码 (404) 来指明服务器何时不存在 — 没有要发送的错误类型或附加信息。
onRetrieve() 方法在概念上与 WS-RP 的 GetResourcePropertyDocument 类似,但没有可用于验证结果的方案,而且 “错误” 是由传输层 (HTTP) 而非应用层定义的。
定义 httpd 安装资源之后,继续查看 httpd 进程资源。清单 6 显示 process.groovy 的实现;它非常简单,因为此资源类型仅用于启动服务器、停止服务器并检查 httpd 进程的存在。对常见 REST 设计原则稍微调整后,我们使用 HTTP POST 来创建 httpd 进程(启动服务器),使用 HTTP DELETE 破坏 httpd 进程(停止服务器),使用 HTTP GET 确定服务器是否正在运行。这三个操作分别对应于 onCreate()、onDeleteCollection() 和 onList() 方法。
清单 6. 创建 process.groovy
def onCreate()
{
Runtime.getRuntime().exec("${getServerHome()}/bin/httpd -k start");
request.status = 201;
}
def onDeleteCollection()
{
Runtime.getRuntime().exec("${getServerHome()}/bin/httpd -k stop");
request.status = 204;
}
def onList()
{
def pidFile = new File(getServerHome(), "logs/httpd.pid");
if (!pidFile.exists())
request.status = 404;
}
|
onCreate() 和 onDeleteCollection() 方法使用与前一教程中相同的代码来启动和停止服务器;具体来说,它们使用 JDK 的 Runtime.exec() API 加载 httpd 可执行文件并执行适当的命令。这正像 WS-* 界面中的自定义操作 Start 和 Stop。onList() 方法利用了一个事实:httpd 在每次启动时创建进程 ID(或 PID)文件,并测试它的存在以确定服务器是否正在运行;如果 PID 文件不存在,此方法返回 404 Not Found 而非正常的 200 OK。同样,通过下载示例项目,您可以获得完全的 process.groovy 文件。
此时,我们完全实现了 httpd 的 RESTful API,但是我们没有简单的方法来测试它。在下一节中,我们将为 Groovy 方法创建一些 RESTful 文档,并在没有编写任何代码的情况下使用 RESTdoc 工具来测试 API。实际上,无需离开浏览器,您就能够验证代码对 httpd 执行的操作!
在上一节中,我们编写了许多代码,但是我们没有将其归档。对于 Zero 的 RESTful 资源脚本,好的文档不仅能够帮助您记住代码如何工作,还能帮助客户端程序员创建并测试其客户端代码。可以为 Groovy 和 PHP 方法编写类似 JavaDoc 的注释,并使用 Zero 的 RESTdoc 工具为代码创建 REST 表和测试页面(有关 RESTdoc 的详细背景,请参阅 参考资料)。我们要将 RESTdoc 注释添加到 Groovy 脚本中,这样就可以测试我们的 httpd 界面。
清单 7 显示 httpd.groovy 文件的 RESTdoc 注释;为简便起见,实现方法已被移除。您可以通过下载示例项目,查看两个脚本的 RESTdoc 注释。httpd.groovy 注释是最具有说明性的,因为它们会显示 RESTdoc 注释中使用的所有可能标记。
清单 7. 归档 httpd.groovy
/**
*
* @success 200 Returns a list of server names that can be used to build server URIs.
* @format text/json
* @example
* [
* "server1",
* "server2",
* ...
* ]
*
*/
def onList()
{
...
}
/**
*
* @success 200 Returns the configuration data for the given server name.
* @error 404 If there is no server associated with the given name.
* @format text/json
* @example
* {
* "name": "server1.ibm.com",
* "port": 8080,
* "threads": 250,
* "admin": "admin@us.ibm.com"
* }
*
*/
def onRetrieve()
{
...
}
|
有了 RESTdoc 注释之后,可以启动 Zero 应用程序(键入 zero run),并在浏览器中导航至 http://localhost:9080/resources/docs。应该能看到一个索引页面,此页面列出了应用程序中的所有 RESTful 资源类型,其中包括 httpd 和 process。单击 httpd 链接,将会看到如图 1 所示的页面。此页面显示所有的 URI 模式,此模式可用于操作资源类型以及关于处理请求和响应的代码的必要信息。
图 1. RESTdoc 页面的屏幕截图
测试 RESTful API 可以使用描述这些 API 的页面来实现。从 httpd RESTdoc 页面中单击 /httpd,这是第一个 URI 模式。您应该会看到图 2 所示的对话框。此 URI 模式没有任何变量,因此只需单击 Send,就会看到一个响应,其中包括一个服务器名称列表 — 与放入 zero.config 文件中的服务器名称相同。图 3 显示此响应:
图 2. RESTdoc 请求页面的屏幕截图
清单 3. RESTdoc 响应页面的屏幕截图
单击第二个 URI 模式 /httpd/{httpdId},并输入 server1 作为 httpdId 变量的值。注意,在输入一个值并完成 URI 模式之前,此工具不允许您单击 Send。单击 Send,应该会看到一个响应,其中包括 httpd 安装的 JSON 表示。
对 RESTdoc 界面的最后测试将针对从前一教程中复制的 “启动” 和 “停止” 功能。基于 Zero 的界面要求将 HTTP POST 或 HTTP DELETE 发送到 /httpd/{httpdId}/process 以启动或停止服务器。您应该按照下列步骤测试启动和停止功能:
- 从 RESTdoc 索引中选择
process资源。 - 单击 HTTP POST URI 模式并输入
server1作为httpdId变量的值。单击 Send。 - 在另一个浏览器窗口(或选项卡)中,访问 http://localhost:8080。应该会看到默认的 httpd 欢迎消息:系统开始工作了!
- 在 RESTdoc 页面上,单击 HTTP DELETE URI 模式,并输入
server1作为httpdId变量的值。单击 Send。 - 在 http://localhost:8080 上刷新页面。应该会得到因为服务器不再运行而连接超时的消息。
- 在 RESTdoc 页面中,单击 HTTP GET URI 模式,并输入
server1作为httpdId变量的值。单击 Send。 - 应该会看到 404 代码状态,此代码确认服务器已停止且其工件(PID 文件)已删除。
在这一节,我已经展示了如何在不编写任何附加代码或文档的情况下测试 RESTful API。RESTdoc 界面不提供自动测试的方式,但对于最初的应用程序开发或对于正在尝试学习服务的客户端程序员来说,它极其有用。
在这一节,您能够为 httpd 制作基于 Zero 的 RESTful 界面,其功能与基于 Apache Muse 的 WS-* 版本的功能一样完整。Groovy 脚本和 RESTdoc 注释的组合提供了与 Java 类和 WSDL 一样的功能和行为,并演示了 REST 可以单独处理对于 HTTP “太复杂” 的任务。REST 和 WS-* 解决方案各有其优缺点,您可以根据项目需求进行选择;最后,我们并没有指出您应该选择哪一个,我们仅仅展示了二者都是有效的选择。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 样例应用程序 | wa-pz-httprest.zip | 8KB | HTTP |
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
- 阅读以前的文章 Create a WSDM interface for an HTTP server using Apache Muse。
-
阅读 RESTdoc,即用于构建本文中 REST 表和测试页面的文档工具。
- 加入
Project Zero 社区 并查看所有的热点话题!
-
浏览 developerWorks Web 开发专区,获得工具、代码和资源并立即开始开发 Web 2.0 应用程序。
-
developerWorks Ajax 资源中心 提供所有技能级别的信息,以帮助您将 Ajax 构建到应用程序中并动态改进用户的 Web 体验。
获得产品和技术
-
下载 Apache HTTP Server,以便可以运行使用此教程构建的应用程序。
-
下载 Project Zero 并开始应用本文介绍的最佳实践。
讨论
-
加入 projectzero.org 论坛。
