首页 文章 类别 VISION 关于


【翻译】轻量级超文本应用协议hal

HAL(Hypertext Application Language)是一种轻量级的超文本应用描述协议,它的实现基于REST,并有效解决了REST中资源结构标准化和如何有效定义资源链接的问题。本文是对HAL规范草案的简单翻译,该规范当前尚处于草案阶段,仅供参考。

简介

在非HTML的应用(“Web APIs”)中,使用超链接让客户端能够在服务的资源中跳转是一个常见的需求,JSON超文本应用语言(HAL)是在JSON中,用于表达超媒体控制(例如超链接)的一种标准。

HAL是一种通用的媒体类型,使用它可以开发Web API并且暴露出一系列的链接。这些API的客户端可以使用这些链接在应用中执行相关类型的操作。

HAL规范为超媒体的服务和消费提供了统一的接口,使用通用的类库,任何使用HAL的API可以被重用。

HAL的主要设计目标是通用和简单,可以在很多不同的领域内使用,并且提供了实现超媒体Web API所必须的最小化的结构。

HAL文档

HAL文档使用RFC4627描述的格式,并且媒体类型为”application/hal+json”。

它的根对象必须是一个资源对象(Resource Object)。

例如:

GET /orders/523 HTTP/1.1
Host: example.org
Accept: application/hal+json

HTTP/1.1 200 OK
Content-Type: application/hal+json

{
    "_links": {
      "self": { "href": "/orders/523" },
      "warehouse": { "href": "/warehouse/56" },
      "invoice": { "href": "/invoices/873" }
    },
    "currency": "USD",
    "status": "shipped",
    "total": 10.20
}

这里,我们有一个表述了URL “/orders/523” 的订单资源的HAL文档,它有一个”warehouse”和”invoice”链接,并且它自身的状态有”currency”, “status”, 和 “total”这三个属性。

资源对象(Resource Objects)

一个资源对象代表了一个资源,它有两个保留的属性:

  1. _links: 包含了到其它资源的链接.
  2. _embedded: 包含了内嵌的资源.

这些属性必须是合法的JSON,代表了当前资源的状态。

保留属性

保留的 _links 属性是可选的。

它是一个属性名为链接关系对象(由RFC5988定义)并且值为一个链接对象或者链接对象数组的对象。这些链接的主题资源是包含了_links对象作为属性的资源对象。

_embedded

保留的 _embedded 属性是可选的。

它是一个属性名为链接关系对象(由RFC5988定义)并且值为一个链接对象或者链接对象数组的对象。

内嵌的资源可能是完整的,部分的或者与来自目标URI表述的资源版本不一致的资源。

链接对象

一个链接对象代表了一个从包含的资源到URI的超链接。它有下列属性:

href

属性href是必须的。

它的值是一个URL RFC3986 或者URI模板 RFC6570

如果它的值是一个URI模板的话,这个链接对象应该有一个值为truetemplated属性。

templated

属性templated是可选的。

它的值是布尔型的,如果链接对象的href属性的值是一个URI模板的话,它的值应该是true

如果该属性未定义或者它的值是true之外的其它值,则认为它是false的。

type

属性type是可选的。

它的值是一个用于作为提示消息的字符串,当引用一个非关联的目标资源时,表明期望的媒体类型。

deprecation

属性deprecation是可选的。

它的出现表明了这个链接是在未来要被弃用。它的值是一个提供了关于未来弃用的信息的URL地址。

当遍历链接的时候,如果发现这个属性,客户端应该提供一些提醒(例如在日志中记录一条警告消息)。这个提醒应该包含弃用属性的值,这样客户端的维护者可以轻松的找出这个弃用的属性。

name

属性name是可选的。

它的值可能会被用来作为选择共享了相同关系类型的链接对象的第二个key。

profile

属性profile是可选的。

它的值是一个用于提示关于目标资源的配置信息(定义在I-D.wilde-profile-link)的URI字符串。

title

属性title是可选的。

它的值是一个字符串,用于给链接一个人类可读的标识符(定义在RFC5988)标签。

hreflang

属性hreflang是可选的。

它的值是一个指明目标资源(RFC5988)的语言的字符串。

示例文档

下列是一个表述了一个订单列表的示例文档

GET /orders HTTP/1.1
Host: example.org
Accept: application/hal+json

HTTP/1.1 200 OK
Content-Type: application/hal+json

{
    "_links": {
        "self": { "href": "/orders" },
        "next": { "href": "/orders?page=2" },
        "find": { "href": "/orders{?id}", "templated": true }
    },
    "_embedded": {
    "orders": [{
         "_links": {
           "self": { "href": "/orders/123" },
           "basket": { "href": "/baskets/98712" },
           "customer": { "href": "/customers/7809" }
         },
         "total": 30.00,
         "currency": "USD",
         "status": "shipped"
    },{
         "_links": {
           "self": { "href": "/orders/124" },
           "basket": { "href": "/baskets/97213" },
           "customer": { "href": "/customers/12369" }
         },
         "total": 20.00,
         "currency": "USD",
         "status": "processing"
        }]
    },
    "currentlyProcessing": 14,
    "shippedToday": 20
}

在这里的订单列表文档中,提供了一个指向下一页的next链接和一个包含可以使用id变量替换以直接跳转到指定订单的URI模板的find链接。

它也包含两个内嵌的orders资源,每一个都有自己到相关的basketcustomer资源的链接,并且有totalcurrencystatus属性。

另外,订单列表资源有它自己的currentlyProcessingshippedToday属性。

媒体类型参数

profile

媒体类型标识符application/hal+json可能也包含一个额外的profile参数(定义在I-D.wilde-profile-link)。

包含profile参数的HAL文档也应该包含一个属于根资源的profile链接。

建议

Self 链接

每个资源对象应该包含一个对应于IANA注册的self关系(定义在RFC5988)的self链接,它的目标是资源的URI。

链接关系

自定义的链接关系类型(在RFC5988中的扩展关系类型)应该是URI,当在web浏览器中被反向引用的时候,以HTML页面的形式提供描述目标资源的含义和行为的文档。这将会提高API的可发现性。

对这些URI来说,可以使用CURIE语法 W3C.NOTE-curie-20101216使其更简洁。CURIE在HAL文档中是通过一系列的根资源对象上的关系类型为curies的链接对象建立起来的。这些链接包含了一个拥有rel标记,并且通过name属性命名的URI模板。

{
    "_links": {
      "self": { "href": "/orders" },
      "curies": [{
        "name": "acme",
        "href": "http://docs.acme.com/relations/{rel}",
        "templated": true
      }],
      "acme:widgets": { "href": "/widgets" }
    }
}

上面例子中的关系http://docs.acme.com/relations/widgets通过CURIE语法,被缩写为acme:widgets

超文本缓存模式

超文本缓存模式允许服务端使用内嵌的资源动态的减少客户端发送请求的数目,提高应用的性能和效率。

对于任何给定的链接关系,为了实现缓存的目的,客户端可以自动的从内嵌的资源(如果提供了的话)中读取信息。

要激活客户端对给定链接执行这种行为,服务器应该在文档中嵌入相关的资源。

服务器不应该对一个内嵌的资源完全”swap out”一个链接,因为客户端对该技术的支持是可选的。

下列例子展示了对一个author链接的超文本缓存模式:

之前:

{
    "_links": {
      "self": { "href": "/books/the-way-of-zen" },
      "author": { "href": "/people/alan-watts" }
    }
}

之后:

{
    "_links": {
      "self": { "href": "/blog-post" },
      "author": { "href": "/people/alan-watts" }
    },
    "_embedded": {
      "author": {
        "_links": { "self": { "href": "/people/alan-watts" } },
        "name": "Alan Watts",
        "born": "January 6, 1915",
        "died": "November 16, 1973"
      }
    }
}

附录 常见问题

  • 客户端如何知道资源的含义/结构/语义/类型?

    解决这个问题有两种方法,这两种方案都需要额外的文档,用人类和(或)机器可读(例如一个HTML页面或者JSON模式文档)的方式来描述资源。不同点在于这个URI在客户端是在哪里共享的:

    1. 之前的链接关系类型的URI
    2. 来自资源本身的profile链接
  • 使用HAL的话从哪里可以找到相关类库?

    在这里维护了一份相关类库的列表: http://stateless.co/ hal_specification.html

  • 为什么保留属性都有一个下划线前缀?

    我们选择这个前缀是为了最小化该属性与资源状态名称的冲突。另一个原因是为了让它与属于资源的标准属性看起来在视觉上更加容易辨别。

  • 所有下划线开头的属性都是保留的吗?

    不是这样的,HAL只预留了本规范中提到的名称。