写点什么

使用模板将 Web 服务的结果转换为标记语言

  • 2017-03-22
  • 本文字数:4059 字

    阅读完需:约 13 分钟

核心要点

  • HTTP-RPC 是一个开源的框架,用来在 Java 中构建 REST 服务;
  • 同一个代码库,可以服务于 Web、移动和桌面客户端;
  • 默认提供了 JSON 响应数据;
  • 通过使用模板,支持基于 HTML 的资源表述;
  • 同时还可以生成 XML、CSV 等格式。

Web 服务是一种实现应用逻辑的方式,能够通过 HTTP 进行远程访问。它们允许分布式的客户端与服务端的功能进行交互,这些客户端通常可能是异构的,服务还经常会用来为移动应用提供后端 API。但是,它们在实现的时候,往往会采用与应用程序 Web 前端相独立的软件栈,这样的话,就会导致额外的开发和维护成本。

模板是一种将数据与展现分离的方法。它们能够输出针对特定数据结构的格式,不过模板与数据本身是相互独立的,这样就为两者的责任画出了清晰的界限。在 MVC 术语中,模板代表了视图,而数据结构代表了模型。Web 服务担任了控制器的角色,接受调用者的输入,生成模型数据并将模板用到模型上,从而产生最终的结果。

使用 HTTP-RPC 框架实现的 Web 服务会有相应的输出,我们可以借助 CTemplate 系统(也叫作 Mustache )所编写的模板将输出转换为 HTML。这样的话,一个应用可以同时服务于 Web 和移动客户端,提升了同一个代码库的使用效率,显著降低总体的开发成本。

HTTP-RPC

HTTP-RPC 是一个开源框架,用于简化基于 REST 的应用开发。它允许开发人员创建和访问基于 HTTP 的服务,在这个过程中采用了一种便利的、类似于 RPC 隐喻的做法,同时又保持了 REST 的基本原则,如无状态和统一资源访问。关于 HTTP-RPC 的更多信息,我之前的文章进行更为全面的介绍。

HTTP-RPC 服务要通过 HTTP 动作来进行访问,比如对目标资源的 GET 或 POST 请求。目标是通过路径来进行指定的,路径代表了资源的名称,通常会使用一个名词来组成 URL,比如 /calendar 或 /contacts。

参数会通过查询字符串或类似于 HTML 表单那样的请求体的方式来提供。结果通常会返回 JSON 格式,当然不返回任何值的操作也是支持的。

例如,如下的请求将会得到两个数字的和,这两个数字分别是通过 a 和 b 这两个查询参数指定的:

GET /math/sum?a=2&b=4服务将会在响应中返回 6 这个值。

样例服务

WebService 类是一个用于 HTTP-RPC Web 服务的基础抽象类。我们定义服务操作的方式就是为某个具体的服务实现添加公开方法。

@RPC 注解用来标记某个方法可以进行远程访问。这个注解会为方法关联一个 HTTP 动作和资源路径。当服务发布之后,所有带有注解的公开方法将会自动允许远程执行。

例如,如下的类可以用于实现我们前文所述的简单加法操作:

复制代码
public class MathService extends WebService {
@RPC(method="GET", path="sum")
public double getSum(double a, double b) {
return a + b;
}
}

如下的方法展示了另外一个样例,它会计算一个集合的简单统计值:

复制代码
@RPC(method="GET", path="statistics")
public Map<String, ?> getStatistics(List<Double> values) {
int count = values.size();
double sum = 0;
for (double value : values) {
sum += value;
}
double average = sum / count;
return mapOf(
entry("count", count),
entry("sum", sum),
entry("average", average)
);
}

针对该 URL 的 GET 请求就会调用这个方法,我们可以传递一个参数列表,包含了 1,3,5 这几个值:

/math/statistics?values=1&values=3&values=5返回给调用者的结果就像如下所示:

复制代码
{
"count": 3,
"sum": 9.0,
"average": 3.0
}

CTemplate

HTTP-RPC Web 服务产生的数据通常都会以 JSON 的格式返回,在有些场景下,如果能以不同的格式返回给调用者的话,那样会更加便利。例如,返回 HTML 形式的内容以支持基于浏览器的客户端应用。HTTP-RPC 原生就支持 CTemplate 系统所定义的模板,这样的话就允许对方法所产生的结果进行任意的转换。

CTemplate 定义了一组“记号(marker)”,在处理模板的时候,这些记号会被“数据字典(data dictionary)”所提供的值替换掉:

  • {{_variable_}}——从数据字典中获取一个变量注入到输出中;
  • {{#_section_}}{{/_section_}}——定义一个重复性的内容区域;
  • {{>include}}——引入其他模板所指定的内容;
  • {{!_comment_}}——为模板的内容提供文本化的信息。

服务方法返回的值代表了数据字典。通常来讲,这会是一个 java.util.Map 实例,它的 key 代表了字典所提供的值。

如果我们要展现 getStatistics() 方法的结果,那么以 Web 页面形式所提供的简单模板可能就会如下所示:

复制代码
<html>
<head>
<title>Statistics</title>
</head>
<body>
<p>Count: {{count}}</p>
<p>Sum: {{sum}}</p>
<p>Average: {{average}}</p>
</body>
</html>

需要注意的是,我们为“count”、“sum”和“average”值使用了变量记号。在执行期,这些记号将会被数据字典中相应的值所替换(也就是方法所返回的 Map 中的值),从而产生最终的输出。

模板注解

@Template 注解用来将一个模板文档和方法关联到一起。这个注解的值代表了模板的类型和名称,这个模板会应用到方法的结果上。例如:

复制代码
@RPC(method="GET", path="statistics")
@Template(name="statistics.html", mimeType="text/html")
public Map<String, ?> getStatistics(List<Double> values) { ... }

“name”元素指向了包含模板定义的文件,它通过相对于服务类型的资源路径来进行指定。

“contentType”元素指定了模板所产生的内容类型。HTTP-RPC 会使用它来识别所请求的模板。在 URL 中,将所需 MIME 类型相关的文件扩展名与服务名称合并到一起,就能发起针对特定表述的请求。

添加上注解之后,对 _statistics.html_ 的 GET 请求将会调用 getStatistics() 方法并在结果上使用模板,结果如下:

复制代码
<html>
<head>
<title>Statistics</title>
</head>
<body>
<p>Count: 3.0</p>
<p>Sum: 9.0</p>
<p>Average: 3.0</p>
</body>
</html>

输出结果可以通过如下命令查看,我们使用 curl 命令下载原始的 JSON 响应,然后使用 mustache 命令来应用模板:

curl -s "http://localhost/math/statistics?values=1&values=3&values=5" | mustache - statistics.htmlHTTP-RPC 使用模板的过程发生在服务端,并不需要在客户端进行处理。不过,在开发期,使用 curl 来对模板进行测试是一种很棒的方式。

实际样例

作为一个更为实际的样例,我们考虑如下的 Web 服务,它返回下面这个数据库表的 SQL 查询结果,这个表来源于 BIRT 的示例数据库:

复制代码
CREATE TABLE Products (
productCode VARCHAR(50) NOT NULL,
productName VARCHAR(70) NOT NULL,
productLine VARCHAR(50) NOT NULL,
productScale VARCHAR(10) NOT NULL,
productVendor VARCHAR(50) NOT NULL,
productDescription TEXT NOT NULL,
quantityInStock SMALLINT NOT NULL,
buyPrice DOUBLE NOT NULL,
MSRP DOUBLE NOT NULL,
PRIMARY KEY (productCode)
);

如下所示的代码,展现了返回表内容的服务方法。ResultSetAdapter 类能够从服务方法中高效地返回 SQL 查询的结果。这个类实现了 List 接口,让 JDBC 结果集中的每一行都以 Map 实例的形式进行展现,这样的话,数据非常适于序列化为 JSON 格式。HTTP-RPC 能够确保在所有的数据写入完成后,数据集会正常关闭:

复制代码
@RPC(method="GET")
@Template(name="products.html", contentType="text/html")
public ResultSetAdapter getProducts() throws SQLException {
Statement statement = getConnection().createStatement();
String sql = "SELECT * FROM Products";
return new ResultSetAdapter(statement.executeQuery(sql));
}

服务方法所返回的原始 JSON 输出可能会如下所示,对于移动应用来说,使用这种类型的数据是非常便利的:

复制代码
[
{
"productCode": "S10_1678",
"productName": "1969 Harley Davidson Ultimate Chopper",
"productLine": "Motorcycles",
"productScale": "1:10",
"productVendor": "Min Lin Diecast",
"productDescription": "This replica features working kickstand...",
"quantityInStock": 7932,
"buyPrice": 48.81,
"MSRP": 95.7
},
...
]

下面所示的模板可以用来将结果转换为 HTML,适用于在 Web 浏览器中展现。注意,在这里使用了“^html”修饰符(modifier),确保输出的内容经过了正常的 HTML 编码。关于修饰符的更多信息,可以参考项目的文档

复制代码
<html>
<head>
<title>Product List</title>
</head>
<body>
<table>
{{#.}}<tr>
<td>{{productCode:^html}}</td>
<td>{{productName:^html}}</td>
<td>{{productLine:^html}}</td>
<td>{{productScale:^html}}</td>
<td>{{productVendor:^html}}</td>
<td>{{productDescription:^html}}</td>
<td>{{quantityInStock}}</td>
<td>{{buyPrice:format=currency}}</td>
<td>{{MSRP:format=currency}}</td>
</tr>{{/.}}
</table>
</body>
</html>

输出的结果可能会像如下所示:

复制代码
<html>
<head>
<title>Product List</title>
</head>
<body>
<table>
<tr>
<td>S10_1678</td>
<td>1969 Harley Davidson Ultimate Chopper</td>
<td>Motorcycles</td>
<td>1:10</td>
<td>Min Lin Diecast</td>
<td>This replica features working kickstand...</td>
<td>7932</td>
<td>$48.81</td>
<td>$95.70</td>
</tr>
...
</table>
</body>
</html>

小结

本文概要阐述了如何使用模板将 REST 服务的结果转换为 HTML,在需要同时支持 Web 和移动客户端应用的情况下,这种方式能够显著减少开发成本。需要注意的是,模板并不局限于 HTML,它们还能用来很便利地生成其他的数据表述形式,比如 XML 或 CSV。

HTTP-RPC 的最新版本可以在该地址下载。如果想了解更多信息的话,参见项目的 README 文档。

关于作者

Greg Brown是一名软件工程师,在咨询、产品以及开源开发方面有着 20 年以上的经验。他目前的关注点在于移动应用和 REST 服务。

查看英文原文: Using Templates to Transform Web Service Results into Markup

2017-03-22 18:391849

评论

发布
暂无评论
发现更多内容

Retool 是什么,怎么样? —— Retool 低代码工具测评

蒋川

低代码 低代码开发平台 retool

2022南京14届-物联网-博览会

InfoQ_caf7dbb9aa8a

坐实大数据资源调度框架之王,Yarn为何这么牛

华为云开发者联盟

大数据 hadoop mapreduce YARN 资源调度框架

读《Software Engineering at Google》(01)

术子米德

架构师成长笔记

2022南京14届-智慧工地-博览会

InfoQ_caf7dbb9aa8a

Docker 实战教程之从入门到提高(二)

汪子熙

Docker 容器 虚拟化 docker image 4月月更

如何做好复盘

Hockor

复盘

Python 中的鸭子类型和猴子补丁

AlwaysBeta

Python

“一只股票一张表”, TDengine 在青岛金融研究院量化分析场景中的应用

TDengine

数据库 tdengine 物联网

Android技术分享| Android 中部分内存泄漏示例及解决方案

anyRTC开发者

音视频 内存 内存泄漏 移动开发 Andriod

[Day12]-[动态规划]-零钱兑换

方勇(gopher)

LeetCode 数据结构和算法

2022南京14届-人工智能-博览会

InfoQ_caf7dbb9aa8a

虎符即将引入稳定币USN 并开启USN专场活动

区块链前沿News

虎符交易所 稳定币

欧拉开发者大会即将开启,全球芯片、整机、软件厂商共建数字基础设施开源操作系统

科技热闻

Thinkphp6实现定时任务功能详解教程

CRMEB

读《Software Engineering at Google》(02)

术子米德

架构师成长笔记

设计消息队列存储消息数据的 MySQL 表格

「架构实战营」

云图说丨不同区块链之间如何跨链交互?

华为云开发者联盟

区块链 跨链 可信 可信跨链服务 跨链交互

如何使用参数化查询提高Cypher查询的性能

华为云开发者联盟

参数化 Cypher查询 华为云图引擎 GES 参数化查询

云智慧10年资深架构师带你了解:普通程序员向架构师成长必经之路

云智慧AIOps社区

程序人生 架构师 Meetup 晋升 成长计划

腾讯一面:你平时怎么排查并调优慢 SQL 的

老周聊架构

MySQL 4月月更

react源码解析7.Fiber架构

buchila11

React

龙蜥社区成立DeepRec SIG,开源大规模稀疏模型深度学习引擎

OpenAnolis小助手

深度学习 开源 龙蜥社区 sig 稀疏模型

基于Flink-CDC数据同步方案

领创集团Advance Intelligence Group

算法 java

jackson学习之七:常用Field注解

程序员欣宸

4月月更

react源码解析8.render阶段

buchila11

React

【PIMF】开源鸿蒙首款IDE低代码入门OpenHarmony应用开发

离北况归

低代码 OpenHarmony Openharmony啃论文俱乐部 OpenHarmony应用开发 可视化界面

解析分布式系统的缓存设计

vivo互联网技术

分布式 服务器 缓存服务

自己动手写Docker系列 -- 5.6实现删除容器

Go Docker 4月月更

Linux驱动开发-编写RFID-RC522射频刷卡模块驱动

DS小龙哥

4月月更

云效 Projex是什么?Projex企业级高效研发项目管理平台

阿里云云效

阿里云 项目管理 研发 敏捷研发 项目协作

使用模板将Web服务的结果转换为标记语言_Java_Greg Brown_InfoQ精选文章