[译] 使用REST API,让世界与你互动

译者注:本文较长,而且不是很好理解,加之译者水平有限,错误和疏漏在所难免,如有发现任何问题,欢迎指出,本文前半部分说的是REST的背景和基本概念,后半部分说的是 Drupal 8 对REST的支持程度,从中我们可以知道什么才是真正的REST,而这个概念,我相信很多人目前还没搞清楚。以下是正文:

Drupal 8 最让人期待的特性之一就是核心对 RESTful Web Services 的整合。Drupal开发者总是期待着能够通过核心做到一些之前做不到的事情,例如:

  • 通过API为第三方提供本地站点的数据
  • 构建更好的当前页行内编辑和添加内容的用户体验而不是整页提交
  • 开发 iPhone 和 Android 应用,其可以管理一个Drupal站点上的内容

但是,什么是 RESTful Web Services 呢?在这篇文章,我会一步一步的讨论 RESTful的概念并解释Drupal核心和模块是如何实现的这些不同的概念。

REST 简史

许多开发者因为API的流行开始知道 REST,这些 API 能够让开发者在其之上构建出 Twitter 和 Netflix 这样的产品,并且许多API还会自己调用自己定义的API。但是这些API的调用方式通常都非常的不相同。这是因为关于RESTful有许多种定义方式,其中一些更加传统,而另一些则更流行。

REST一词由 Roy Fielding 创造,他是最早的 Web 标准之一的 HTTP 协议的参与者之一。他创造 REST 来描述一般的互联网系统的架构。从他在他的论文中发表了 RESTful 系统的规格开始,其中一部分已经在开发者社区得到应用,而另一些则只有很少的社区口头支持。

为了更好的理解不同级别的RESTful架构的区别,推荐阅读:Martin Fowler’s explanation of the Richardson Maturity Model

什么是RESTful?

那么实现 RESTful 到底有哪些需求呢?

每个人都赞同的是:格式和资源
对REST的主流理解是用机器可读的格式直接与资源互动,而不是与某一个终端地址(endpoint)互动。

大多数人赞同的是:使用标准的HTTP方法
大多数认为自己提供的是RESTful的API也都使用了标准的HTTP方法,但是也不总是这样,另一些叫自己RESTful的API并没有这样做。

REST倡导者赞同的是:Hypermedia (译者注:可以译成超媒体,但作为术语直接理解即可)
最后,REST倡导者强调的一个重要的,但很少在主流RESTful API中见到的事情是超媒体控制。

使用机器可读格式

机器可读格式,例如 JSON和XML,在各种各样的Web Service中被使用。RESTful API通常使用JSON,因为其格式对人和机器来说都是可读的。你可能以前看过JSON格式的代码,下面是一个简单的例子,让你看看JSON格式长什么样子:

{
    "title": "Fancy title"
    "field_body": "Fancier content."
}

在一些API中,你需要通过在路径后面加上格式标识来请求对应的格式,例如,node/1.json。一个更RESTful的方式是使用HTTP内建的方法在不同的格式之间切换,这被称之为内容格式约定(content negotiation)。

要这样做,一个HTTP头需要被用来指定媒体类型,例如你想要获取一个JSON版本的节点,你需要发送以下HTTP头:

Accept: application/json

在这个例子中,Accept是头的名字,application/json是媒体类型。

与资源互动,而不是与终端地址互动

终端地址被用于其他类型的Web Services, 例如,XML-PRC。在这样的Services中,你有唯一一个URI用来交互。举个例子,Wordpress默认开启了 XML-PRC的服务,因此,如果你有一个WP站点,你的API将可以在如下的地址被调用。

http://example.com/xmlrpc.php

然后,你就可以给它发送一个包含post标识符的请求来获取post内容。

<methodCall>
  <methodName>wp.getPost</methodName>
  <params>
    ...
    <param>
      <value>
        <string>107</string>
      </value>
    </param>
  </params>
</methodCall>

在REST中,你不需要在消息的主体中包含ID。取而代之的是你需要与包含了标识符的URI互动。因此你不是发送请求给 /xmlrpc.php,你需要发送请求给能够标识的资源地址,例如,/blog/107。

当一些人说到他们的REST API包含了终端地址(endpoints),Roy Fielding已经非常明确的表达了REST API不需要终端地址,只需要起始地址(Starting points)。

使用标准化的请求方法

不需要使用自定义的方法,例如上面例子中提到的 wp.getPosts,一个 RESTful 服务使用标准化的请求方法。因此,为了获得内容,你需要发送一个GET请求,其他经常使用的标准化方法有POST, DELETE, PUT, PATCH等。请求方法不应该像XMLPRC那样在请求主体中指出,也不是URI的一部分,在早起的Rails 实现的REST中,对于GET请求的URI被构建成类似这样:/posts/show/1。

如果你对Rails社区的REST理解感兴趣,Steve Klabnik 提供了很好的分析,Rails之父DHH有一篇非常有名的文章:Getting Hyper about Hypermedia

URI里的这个 show 其实是多余的,因为对REST API来说资源的GET请求的含义和show是一样的。

让跳转更简洁(超媒体:Hypermedia)

Hypermedia 作为真正 RESTful API中的一部分是最复杂,最不被理解的。最基本的 hypermedia 本质是包含一些使用你的API的用户接下来可以去的链接,使用的格式我们称之为 link relations。例如,如果你构建一个是商店,支持RESTful API,当返回一个购物车里的商品列表时可以包含相关的支付链接,指出用户去哪可以去支付他们的商品。

{
  "_links": {
    "self": {"href": "http://store.com/customers/linclark/cart"},
    "payment": {"href": "http://store.com/customers/linclark/cart/pay"}
  }
  "items": {
    ...
  }
}

就像上面的支付链接,因为link relations是标准化的,你不需要用一个自定义的app去与他们互动。取而代之的,其应该可以浏览一个API,并且使用通用的app。在上面的例子中,这个代码片段是 HAL (Hypertext Application Language),因此,你可以使用HAL浏览器与之互动。例如,FoxyCart使用HAL浏览器作为其API的标准的界面。

你可能看到人们使用术语HATEOAS,全称是 Hypermedia as the Engine of Application State。HATEOAS背后的思想是,你只需要一个API的起始路径,你不需要直接在你的应用中硬编码URI。一旦你知道了起始路径,你能够通过link relations找到其他资源,执行其他动作,与人通过链接查找信息非常类似。

这方面的RESTful特性短期内看不出什么效益,因为只有很少的客户端会使用hypermedia。并且实现它也需要一定的成本,因为这需要你有一个开发者或团队能同时理解你的领域以及理解Hypermedia和link relations。并且假如你的领域还没有实现标准化,需要额外的标准化方面的努力才能让这个特性变得有用。

Drupal的新特性是怎样支持REST的几个不同方面的

通过对核心的路由系统的改变和序列化模块的说明,Drupal现在是格式可感知的了。系统可以用不同的内容格式来序列化内容实体,例如节点和评论。其也能从一些数据格式解序列化成实体,但不是所有的数据格式都支持解序列化。

序列化模块支持 JSON 和 XML, 但是 XML 支持的非常有限,不支持命名空间,属性等。另外,核心模块 HAL(上面提到过),实现了 Hypertext Application Language。

系统还可以容易地扩展以支持其他格式。例如,我们可以引入对GitHub的自定义 JSON 媒体类型的支持。

  1. 在系统中注册媒体类型。这通过订阅 onKernelRequest 事件来实现,然后调用 Request::setFormat()。例如,$request->setFormat('github_json', ‘application/vnd.github.v3+json');
  2. 添加 Normalizer 类来将一个实体对象转化为数组,这些类应该实现了 NormalizerInterface 接口。你也可选地可以支持 DenormalizerInterface 接口,如果你想能够将序列化的数据转化回一个实体。
  3. 添加一个 Encoder 类。 这将会把一个 normailized 数组加密成一个字符串。 Encoder 类需要实现 EncoderInterface 接口,可选地实现 DecoderInterface 接口。
  4. 使用 services.yml 文件注册你的类到系统。你可以为你的类加上 “normalizer” 标签,他们将会自动的增加到 serializer。

另外,系统能够很容易的被扩展成支持非实体数据结构。你只需要添加 Normalizers,并且返回TRUE,让这些类支持 Normalization。

资源

REST模块为实体提供了中心化的资源API服务。无论何时你创建了一个新的实体类型,你将自动得到一个资源服务与之对应。

这些资源仍然需要被启用。为了实现这一点,需要给 rest.settings.yml 添加一个入口设置。默认地,节点资源是被启用了的,并且可以作为配置示例。

你也可以添加你自己的非实体资源。每个资源通过一个插件被提供。看看 DBLogResource,其提供了一个非实体资源的简单示例。

请求方法

REST模块提供的实体资源支持下面的请求方法:

  • GET 获取一个序列化的实体
  • POST 创建一个实体
  • PATCH 更新一个已存在实体的指定字段
  • DELETE 从数据库中移除一个实体

在一个完全 RESTful 化的系统中,用来创建实体的POST 操作实际上是一个不同的资源,因为其在独立的URI中被标识。

你可以在 EntityResource 类里找到处理这些方法的代码,例如,EntityResource::delete($id)。

为了给一个资源添加自定义方法,只需要为类添加一个匹配方法:例如,如果我们想支持 HEAD 方法,我们可以简单的为资源类添加一个head方法。

默认地,对资源的操作权限被设置成基于方法来控制,因此你将需要为你添加的方法配置权限。

Hypermedia (超媒体)

正如上面提到的,Drupal核心内置了Hypermedia格式, HAL。这是一个轻量级的为数据格式添加link relations的方法,其可以被用于 JSON 和 XML。 Drupal 核心仅仅支持 JSON 版本。

HAL normalizer 暴露了所有的实体引用,图片以及其他链接,作为link relations,并在_links属性下分组提供。默认地,为每个字段使用一个固定的URI作为link relations,而不是自定义的。next-archive 和 prev-archive 这两个link relations在 Feed Paging 和 Archiving RFC 中是标准化的。当使用这些的时候,理解这些 link relations 的工具可以在结果中进行导航访问。

为了使用一个与默认不同的 link relations 集合,要替换 LinkManager 类。

那么,你提供的API服务会提供完整的RESTful特性么?

你的Drupal8站点的API能通过完整的REST测试么?结论可能是不太可能。但这不是问题。

重要的是要理解不同人在谈论RESTful API时的含义的不同,以及可以带来的好处的不同。许多工具和服务自称 RESTful的,实际上仅仅是实现了Web API约定而已,这样做一方面只能带来有限的不完整的RESTful体验,另一方面通常要依赖于非REST的约定,实际上是违反了REST约束的。

总而言之,网站开发者最好能考虑他们提供的服务面向的是怎样的消费者,什么是这些消费者期望的。可能需要一些自定义,但理想地我们最好已经有了一个框架可以提供让你拥有满足消费者期望的能力。

作者:Lin Clark
翻译:理查