用REST分割头发:标准的JSON REST API是否违反HATEOAS?

今天早上我正在读REST,并且遇到了HATEOAS原理(“超媒体作为应用程序状态的引擎”) 。

引用REST Wikipedia页面 :

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

而Roy Fielding的博客 :

…如果应用程序状态(因此API)的引擎没有被超文本驱动,那么它不能是RESTful,也不能是REST API。 期。

我读到这样的:客户端可能只需要根据服务器的响应主体(超文本)提供的动作来请求状态改变。

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

当资源以其他方式表示时 – 例如JSON,XML,YAML等。这一点并不明显。

我们来看一个“REST”JSON API的例子:

我通过发送一个POST请求来创build一个新的资源(例如一个新的注释)

/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背后的理论的好奇心,而不是对现实世界最佳实践的build议。

作为超媒体types的JSON不定义应用程序stream的标识符。 HTML有链接和表单标签,引导用户完成一个过程。

如果您的应用程序只关心资源上的PUT,POST,DELETE,GET,那么您的文档可以很容易地解释这一点。

然而,如果在评论中join反驳更加复杂,反驳是一种不同的资源,那么评论就需要超媒体types来引导消费者去反驳。

你可以使用HTML / XHTML,创build你自己的“精明的+ json”或者使用别的东西。 以下是所有不同的媒体typeshttp://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和Node构build超媒体API”一书深入到超媒体和媒体types。 它展示了如何在XML或JSON中为特定或通用目的创build媒体types。

Jon Moore于2010年11月就如何编写真正的RESTful(即HATEOAS支持)API和客户端进行了精彩的演讲 。 在第一部分中,他build议JSON不是适用于REST的媒体types,因为它缺乏表示链接和支持HTTP方法的常用方法。 他认为,好的XHTML实际上是完美的,因为parsing它的工具(即XPath)是可用的,它支持表单(思考GET链接模板和PUT,POST和DELETE方法),并有一个很好理解的方式识别超链接,以及主要通过在任何标准Web浏览器上使用API​​的能力而获得的一些其他优点(简化了开发人员,质量保证和支持人员的工作)。

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

我意识到像JSON-Schema和其他 RFC正在努力尝试在JSON中标准化事件,但同时,摩尔的讲话使我相信XHTML是一个尝试。

RESTful解决scheme是利用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" < } 

菲尔丁的论文提出了两种types的元数据: 表示元数据 ; 和资源元数据

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

通过充分利用HTTP提供的function,您不再需要任何超出限制的信息,并且变得更加RESTful。

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

这是一个整洁的devise; 客户要求一个表示,并且另外收到了一大堆关于资源的额外的元数据,这使得能够有效地请求进一步的表示。

我认为你在维基百科REST页面上的引用在词汇select方面有些误导,并且在这里没有帮助(注意,自从问题被提出以后,它得到了改进)。

所有的HTTP客户端都必须假设一个GET方法可能对大多数资源是可用的。 他们这样做是因为支持GETHEAD是HTTP / 1.1服务器的最低要求。 没有这个假设,networking将无法运作。 如果客户端可以假定GET可用,那么为什么不对其他常用方法(如DELETEPOST)做出其他假设?

REST和HTTP旨在充分利用对一组基本方法的假设的权力,以减lessnetworking上的总体请求量; 如果请求成功,则不需要进一步的通信; 但是如果请求失败且状态为“405方法不允许”,那么客户端立即收到可能通过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的可用function来解决问题将是RESTful。

一个完全可发现的JSON API,不需要任何带外知识就可以简洁地expression出来:

“我也可以使用相同的URL删除注释,这是客户端通过带外信息(文档)所了解的,而不是由服务器的响应发现和驱动的。

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

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

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

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

另一个坚实的(并在2013年5月新)尝试解决HATEOAS的JSON可以在这里find:

JSON API:http://jsonapi.org/

你的问题的前提是REST经常被误解,API响应主体不仅要负责传递所请求资源的表示状态,还负责传递资源所属应用程序的总体状态 。 这两件事情 – 资源状态和应用程序状态是不一样的。

按照定义,响应实体主体为您提供某个时间点的资源状态。 但是单个资源只是包含应用程序的许多资源之一。 应用程序状态是从应用程序消费者的angular度来看,在任何时间点的所有与范围相关的资源的组合状态 – 人或机器。 为了提供这种“应用程序状态”,3级REST API使得HATEOAS成为可能。

由于超文本是大多数人在提到HATEOAS中的“超媒体”时所指的意思,因此超文本的特殊function就是能够链接到其他媒体。 而且,由于大多数经验都是通过HTTP / HTML来体验超文本,所以这往往导致许多人认为超链接只能通过响应实体内的锚标签或链接标签来实现 – 但事实并非如此。

如果传输协议是HTTP,那么应用程序状态可以并且应该通过标题进行通信。 具体来说,一个或多个“链接”标题用'rel'属性来提供语义。 链接头和ALLOW头是HTTP机制,用于传达下一个可能的状态转换以及如何访问它们。

如果您决定不使用这些内置机制,而不是使用这些内置机制,则可以尝试通过在资源状态通信通道(即响应主体)上“捎带”来传递应用程序状态,从而导致尝试devise某种forms的附加规范进入资源本身的devise。

当这样做完成时 – “捎带” – 很多都遇到了内容types问题,因为响应正文必须由XML或JSON等MIME / Contenttypes来指定,这意味着要了解如何通过某些自定义来实现HATEOAS机制内容types特定的格式,如自定义XML标签或嵌套对象的键值对。 你可以做到这一点,许多做 – 例如见上面的json-apibuild议,但HTTP已经提供了这个机制。

我认为这是对我们来说,因为人们一直认为我们必须看到或能够点击这些链接,正如在正常的Web使用情况下,但我们正在谈论的API,我只能假设正在build造不是为了人类消费,而是为了机器消耗 – 对吗? 在大多数HTTP接口的人机接口中,头文件(作为响应的一部分)是不可见的,即浏览器不是REST的问题,而是市场上HTTP代理的实现限制。

希望这可以帮助。 顺便说一句,如果你想要一个良好的人工浏览器的API谷歌的“爪哇API浏览器”