首页 文章

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

提问于
浏览
40

今天早上我在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"}

我知道我现在可以在 Headers 中返回的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删除注释,这是客户端通过带外信息(文档)知道的,并且不会被服务器的响应发现和驱动 .

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

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

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

6 回答

  • 3

    作为超媒体类型的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中为特定或通用目的创建媒体类型 .

  • 15

    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的讨论使我确信尝试了XHTML .

  • 20

    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列出了两种类型的元数据: representation metadata ;和 resource metadata .

    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 .

  • 4

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

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

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

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

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

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

  • 18

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

    JSON API: http://jsonapi.org/

  • 8

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

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

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

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

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

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

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

    希望这可以帮助 . 顺便说一句,如果你想要一个良好的人类浏览器的API google'Paw API Browser'

相关问题