使用REST拆分头发:标准JSON REST API是否违反了HATEOAS?

时间:2012-01-29 17:36:53

标签: api rest

今天早上我正在阅读一些关于REST的内容,我遇到了HATEOAS principle ("hypermedia as the engine of application state")

引用REST Wikipedia page

  

客户端仅通过服务器在超媒体内动态识别的动作(例如,通过超文本内的超链接)进行状态转换。除了应用程序的简单固定入口点之外,客户端不会假定任何特定操作可用于除先前从服务器接收的表示中描述的任何特定资源。

Roy Fielding's blog

  

...如果应用程序状态的引擎(以及API)没有被超文本驱动,则它不能是RESTful,也不能是REST API。期。

我将其读作:客户端可能只根据服务器响应正文(超文本)提供的操作请求状态更改。

在HTML世界中,这非常有意义。客户端应该只能根据通过超文本(HTML)提供的链接请求状态更改(新操作/页面)。

当资源以其他方式表示时 - 例如JSON,XML,YAML等。这不是那么明显。

让我们举个例子“REST”JSON API:

我通过向

发送POST请求来创建新资源(例如新评论)

/comments.json? # with params...

服务器响应:

# Headers
HTTP/1.1 201 Created 
Location: http://example.com/comments/3
Content-Type: application/json; charset=utf-8
... Etc.

# Body
{"id":3,"name":"Bodacious","body":"An awesome comment","post_id":"1"}

我知道我现在可以在标题中返回的URI中访问此评论:http://example.com/comments/3.json

当我访问http://example.com/comments/3.json时,我看到了:

{"id":3,"name":"Bodacious","body":"An awesome comment","post_id":"1"}

假设API的文档告诉我可以通过向同一URI发送DELETE请求来删除此注释。这在“REST”API中相当普遍。

然而:

来自GET http://example.com/comments/3.json的服务器的响应并没有告诉我任何关于能够通过发送DELETE请求来删除评论的信息。它向我展示的是资源。

我也可以使用相同的URL删除注释,这是客户端通过带外信息(文档)知道的,并且不会被服务器的响应发现和驱动。

此处,客户端 ,假设DELETE操作(以及可能的其他操作)可用于此资源,并且此信息以前未从服务器接收过。

我是否误解了HATEOAS,或者我是否正确地说,与严格意义上的匹配上述描述的API不会成为REST API?

我知道100%坚持REST并不总是可行或最务实的方式。我发布这个问题纯粹是为了满足我对REST背后的理论的好奇心,而不是对现实世界最佳实践的建议。

6 个答案:

答案 0 :(得分:20)

Jon Moore在2010年11月给出了an excellent talk关于编写真正RESTful(即HATEOAS支持)API和客户端的细节。在第一部分中,他建议JSON不是REST的适当媒体类型,因为它缺乏一种通常理解的表示链接和支持的HTTP方法的方式。他认为好的'XHTML实际上是完美的,因为解析它的工具(即XPath)很容易获得,它支持表单(想想GET链接模板和PUT,POST和DELETE方法),并且有一个很好理解的方法。识别超链接,以及主要通过在任何标准Web浏览器中使用API​​实现的一些其他优势(简化开发人员,QA和支持人员的工作。)

在观看他的演讲之前我总是提出的论点是,JSON的带宽消费者比任何* ML语言都低得多,例如: XML,HTML,XHTML。但是在可能的情况下使用简洁的XHTML,例如相对链接而不是绝对链接(在他的演讲中使用的示例中暗示但不那么明显),并且通过使用gzip压缩,这个论点失去了很多权重。

我意识到诸如JSON-Schemaother RFC之类的努力正在尝试用JSON标准化事物,但与此同时,摩尔的谈话使我确信尝试了XHTML。

答案 1 :(得分:18)

作为超媒体类型的JSON没有定义应用程序流的标识符。 HTML具有链接和表单标记,用于指导用户完成整个过程。

如果您的应用程序仅涉及资源上的PUT,POST,DELETE,GET,那么您的文档可以轻松解释这一点。

但是,如果它更复杂,比如在评论中添加反驳,并且反驳是一个不同的资源,那么评论你需要超媒体类型来引导消费者创建反驳。

您可以使用HTML / XHTML,创建自己的'bodacious + json'或使用其他内容。以下是所有不同的媒体类型 http://www.iana.org/assignments/media-types/index.html

我正在使用HAL,它有一个非常活跃的群组。这是它的链接。

http://www.iana.org/assignments/media-types/application/vnd.hal+json

http://stateless.co/hal_specification.html

“使用HTML5和节点构建超媒体API”一书深入探讨了超媒体和媒体类型。它显示了如何在XML或JSON中为特定或通用目的创建媒体类型。

答案 2 :(得分:15)

RESTful解决方案是利用Allow-header通知客户可用的方法/操作:

> GET /posts/1/comments/1 HTTP/1.1
> Content-Type: application/json
>
< HTTP/1.1 200 OK
< Allow: HEAD, GET, DELETE
< Content-Type: application/json
<
< {
<  "name": "Bodacious",
<  "body": "An awesome comment",
<  "id":   "1",
<  "uri": "/posts/1/comments/1"
< }

Fielding's dissertation列出了两种类型的元数据:表示元数据;和资源元数据

HTTP / 1.1中的Allow-header用作资源元数据,因为它描述了资源的某些属性;即它允许的方法。

通过充分利用HTTP提供的功能,您无需任何越界信息,并变得更加RESTful。

简单HTTP上下文中的HATEOAS描述了客户端如何通过使用 GET 跟踪URI从一种表示导航到另一种表示,而Allow-header通知客户端资源支持的其他方法生成了表示。

这是一个整洁的设计;客户要求表示,并另外收到一大堆关于资源的额外元数据,以便有效地请求进一步的表示。

我认为维基百科REST页面中的引用在选择单词方面有些误导,并且在这里没有帮助(N.B.自提出这个问题以来它已得到改进)。

所有HTTP客户端都必须假设GET方法可能适用于大多数资源。他们这样做是因为支持 GET HEAD 是HTTP / 1.1服务器的最低要求。如果没有这种假设,网络将无法运作。如果客户端可以假设 GET 可用,那么为什么不对常见方法做出其他假设,例如 DELETE POST

REST和HTTP旨在利用对一组基本方法进行假设的能力,以减少网络上的请求总量;如果请求成功,则无需进一步通信;但如果请求失败且状态为“405 Method Not Allowed”,则客户端会立即通过Allow-header接收可能成功的请求:

> ANNIHILATE /posts/1/comments/1 HTTP/1.1
> Content-Type: application/json
>
< HTTP/1.1 405 Method Not Allowed
< Allow: HEAD, GET, DELETE
< Content-Type: application/json
<

如果基本的HTTP / 1.1方法集不够,那么您可以自由定义自己的方法。但是,在定义新方法或将元数据放入消息体之前,使用HTTP的可用功能解决问题将是RESTful。

答案 3 :(得分:8)

一个完全可发现的JSON API,它不需要任何带外知识,因为你如此简洁地说:

  

“我也可以使用相同的URL删除注释,这是客户端通过带外信息(文档)知道的,并且不会被服务器的响应发现和驱动。”

......完全有可能。它只需要一个简单的标准和一个理解标准的客户端。查看hm-json和hm-json浏览器项目:

https://bitbucket.org/ratfactor/hm-json-browser/

正如您在演示中所看到的,绝对不需要带外文档 - 只有一个入口点URI可以通过浏览发现所有其他资源及其HTTP方法。

顺便说一下,在起诉答案中提到的HAL非常非常接近你对HATEOAS的假设要求。这是一个很好的标准,它有很多很酷的想法,比如嵌入式资源,但它没有办法告知客户端所有可用的HTTP方法,例如给定资源的DELETE。

答案 4 :(得分:4)

可以在此处找到另一个可以解决HATEOAS for JSON的实体(以及截至2013年5月的新版本):

<强> JSON API: http://jsonapi.org/

答案 5 :(得分:3)

您的问题的前提包含REST 经常被误解的方面 - API响应正文实体不仅负责传达所请求资源的表示状态,还负责传达应用程序的整体状态资源属于。这两件事 - 资源状态和应用程序状态不是一回事。

响应实体主体按定义为您提供某个时间点的资源状态。但是,单个资源只是构成应用程序的众多资源之一。应用程序状态是所有范围相关资源的组合状态 - 在任何时间点 - 从应用程序使用者的角度 - 人或机器。为了提供这种“应用程序状态”,3级REST API可以实现HATEOAS。

由于超文本是大多数人在提到HATEOAS中的“超媒体”时的意思,超文本的特殊功能是它能够链接到其他媒体。此外,由于大多数人通过HTTP / HTML体验超文本,这往往会导致许多人认为超链接只能通过响应实体主体内的锚标签或链接标签实现 - 但事实并非如此。

如果传输协议是HTTP,则应用程序状态可以 通过标头进行通信。具体来说,一个或多个具有'rel'属性的'Link'HEADERS提供语义。 Link HEADER和ALLOW标头是用于传达下一个可能的状态转换以及如何访问它们的HTTP机制。

如果您决定不使用这些内置机制,那么您的选择是尝试通过资源状态通信通道(即响应主体)上的“背负式”来传达应用程序状态,这会导致尝试设计某种形式在资源本身的设计中增加了规范。

当这样做 - “背负式” - 许多人遇到内容类型问题,因为响应主体必须由MIME /内容类型(如XML或JSON)指定,这意味着要弄清楚如何实现HATEOAS机制通过一些自定义内容类型特定格式,如自定义XML标记或键:嵌套对象的值对。你可以这样做,许多人这样做 - 例如请参阅上面的json-api建议,但HTTP已经为此提供了机制。

我认为它归于我们,因为人类总是认为我们必须看到或能够像在正常的网络用例中那样点击这些链接,但我们正在谈论的API,我只能假设它不是为人类构建的消费,但机器消费 - 对吧?事实上,标题 - 作为响应的一部分存在 - 在HTTP的大多数人类界面中是不可见的,即浏览器不是REST的问题,而是市场上HTTP代理的实现限制。

希望这会有所帮助。顺便说一句,如果你想要一个良好的人类浏览器API谷歌'Paw API浏览器'