[译]用模版技术转换Web服务输出结果

        要实现通过HTTP远程访问应用程序,Web服务是一种途径。它们允许各种异构的分布式客户端访问服务器端的功能,更是经常被用于为移动应用提供后端API支持。不过它们一般基于独立的技术栈实现,与web前端是分开的,所以需要额外的开发和维护成本。

        模版技术实现了数据和呈现的分离,允许数据结构最终的输出格式独立于数据本身,划清了各自的职责界限。在MVC模式里,模版对应视图,数据结构对应模型。Web服务在这里是作为控制器,它接收来自调用者的输入,产生模型数据,然后应用模版生成最终的结果。

        这篇文章将描述如何使用模版把Web服务输出结果转换成HTML,其中模版使用CTemplate(或者Mustache)编写,Web服务使用HTTP-RPC实现。这样可以只用一个代码库就可以为web和移动客户端提供服务,极大降低了开发成本。

 

HTTP-RPC

        HTTP-RPC是一个开源框架,目的是为了简化REST应用的开发。它允许开发者在保留REST无状态和统一资源访问基本准则的前提下,以一种便利的RPC方式访问基于HTTP的Web服务。更多关于HTTP-RPC的信息,可以在我之前的那篇文章看到。

        通过把HTTP动词GET或POST应用到目标资源上,完成对HTTP-RPC服务的访问。目标资源用能够代表它们名字的路径来表示,而且一般是名词,比如/calendar或/contacts。

        参数跟HTML表单一样,出现在查询字符串或请求体里。服务可能产生任意类型的结果内容,不过一般以JSON格式返回。有些不返回结果的操作也是被允许的。

        举个例子,下面的请求将获得两数的相加之和,这两个数分别是在查询字符串里指定的a和b:

GET /math/sum?a=2&b=4

        这个服务将在响应里返回6。

 

服务示例

        HTTP-RPC服务有一个抽象基类WebService,通过往具体实现类里添加公共方法来实现服务行为。@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) ); 
}

    通过GET方法访问下面的URL,上面的方法就会被调用,参数是一个包含1,3,5的列表:

/math/statistics?values=1&values=3&values=5

        返回结果如下:

{ "count": 3, "sum": 9.0, "average": 3.0 }

 

CTemplate

        HTTP-RPC服务生成的数据一般以JSON格式返回,不过如果能以其它不同的格式返回给调用端,对他们来说会更方便。比如,如果客户端是基于浏览器的应用,那么就返回HTML。HTTP-RPC使用CTemplate来支持模板,允许调用结果被转换成任意格式。

        CTemplate定义了一组“占位符”,当处理模板的时候,这些“占位符”会被“数据字典”里的值代替。

  • {{_variable_}} - 注入一个来自数据字典的变量并输出
  • {{#_section_}}...{{/_section_}} - 定义一个循环体
  • {{>include}} - 导入另一个模板定义的内容
  • {{!_comment_}} - 对模板内容的注释

        服务返回的值相当于数据字典。它们一般是java.util.Map类的实例,值跟键相关联。

        下面的代码演示了如何使用一个简单的模板把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”这几个值的占位符的。在程序运行期间,这些占位符会分别被数据字典里的值替换,生成最终的结果。

 

模板注解

        @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是服务名称加上与“contentType”相对应的文件扩展名。

        有了这个注解,当用GET方法访问statistics.html时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.html

        因为HTTP-RPC是在服务器端应用模版,所以在客户端不需要做任何处理。不过,在开发的时候使用curl来测试模版还是很管用的。

 

实际的例子

        举个实际的例子,我们从BIRT示例里拿出一个表,对这个表做了一个SQL查询,然后web服务把结果返回。

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实例,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 }, ... 
] 

        下面的模版可以把JSON转换成HTML,适合在浏览器中显示。注意到模版里的^html修饰符,它们可以保证输出结果是经过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。

转载于:https://my.oschina.net/xuemingdeng/blog/743394

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值