具有表单和链接的JSON超媒体API

20

我正在计划一个REST API,希望符合REST的HATEOAS约束。但是我也想提供JSON格式。所以我的问题是,是否有传统方法来用JSON表示链接和表单。

我发现了一些链接示例,看起来这是一种常见的表示链接的方式:

"links": [ 
{"rel": "self", "href":"http://example.org/entity/1"},
{"rel": "friends", "href":"http://example.org/entity/1/friends"}] 

就代表表单而言,我并没有看到太多相关的内容。我想也许有人会思考类似的东西,但同时也要考虑到所有的注意事项:

"forms" : [
{"rel" : "new client", "action" : "/clients", "method": "post", 
"fields" : ["name":"string", "zipcode":"int", "signedup":"date", "state": ["Alabama",...]...]}]

灵感来自观看Jon Moore的视频,他建议JSON不是超媒体API的好格式:

http://oredev.org/2010/sessions/hypermedia-apis

顺便说一下,这是一次非常好的演讲!

欢迎提供所有意见!


1
回答有些晚了,但是 Mike(Hal 规范的作者)正在开发 Halo 规范,该规范处理并补充了 Hal 中的表单。https://groups.google.com/forum/?fromgroups=#!msg/api-craft/7ywrN0u-6_E/DWb_j_1z1EMJ。此外,这里还提供了一些示例: https://gist.github.com/mikekelly/893552 。不过,我不确定该规范的开发活动有多活跃。 - Geert-Jan
5个回答

7
我调查了一段时间这个主题,但我不确定人们使用哪些可能的解决方案以及哪些不使用。只有很少的例子可用...所以我需要一些专家的审查...(我的示例大多在HAL+JSON中。)
1.)
我有一种感觉,链接关系应该仅限于GET,因为在HTML中,它们是用于包括像样式表之类的东西。我猜其他人也有同样的感觉,因为IANA链接关系中有一个edit-form和一个create-form。
  • So the first possible solution to dereference links with form relations and so download the form descriptions for the write operations. These form descriptions can contains HTML fragments or a schema which we can use to generate the forms. For example

    Just to mention you can use the same approach by sending the same links in the header and send raw JSON as body.

    {
        "_links": {
            "edit-form": {
                "href": "http://example.com/users/1?form=edit",
                "type": "text/html",
                "title": "Edit user"
            }
        }
    }
    

如果关联链接不仅仅是用于读取,那又如何呢?

2.)

那么我们可以使用HAL的内置功能:

  • If we send data, then we can use the type to describe the request body instead of the response body. Ofc. in this case there should not be a response body, or this solution will be confusing.

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/vnd.example.user+json",
                    "title": "Edit user"
                }
            }
        }
    

    So in this case the client will know that my:edit means that this is an edit form, and by checking the MIME type it will know what type of form to display.

  • An alternative solution to use the custom link relation for the same purpose:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit-user": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user"
                }
            }
        }
    

    So by fetching the docs http://example.com/rels/edit-user we can find a description about how to build a form for editing users and so we can support the my:edit-user link relation in our client. The docs can contain optionally a HTML form or some schema, or an RDF document using a form description vocab, etc...

  • We can follow the same approach by the profile property of the links. For example:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "profile": "http://example.com/profiles/user"
                }
            }
        }
    

    So in here the link relation means that this is an edit form and the profile describes how to generate the form under the http://example.com/profiles/user URL.

3.)

或者,我们可以使用自定义属性来扩展HAL。

  • For example dougrain-forms does this:

        {
            "_forms": {
                "edit": {
                    "href": "http://example.com/users/1",
                    "headers": {
                        "content-type": "application/json"
                    },
                    "title": "Edit user",
                    "method": "PUT",
                    "schema": {
                        "required": [
                            "name"
                        ],
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        },
                        "title": "user properties"
                    }
                }
            }
        }
    
  • But you can use any alternative approach as long as we don't have a standard about HAL and about HAL forms, for example I would rather use a mongoose schema like solution:

        {
            "name": "John",
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "method": "PUT",
                    "_embedded": {
                        "schema": {
                            "name": "String"
                        }
                    }
                }
            }
        }
    

4.)

不要使用链接关系和简单的JSON格式,如HAL,请使用一个或多个词汇表的RDF。RDF更难使用,但它是将客户端与REST服务解耦的细粒度解决方案,而HAL只是一种粗粒度的解决方案...
  • For example JSON-LD with Hydra and a custom vocab:

    {
        "@context": [
            "http://www.w3.org/ns/hydra/core",
            "https://example.com/docs#"
        ],
        "@id": "https://example.com/users/1",
        "name": "John",
        "operation": {
            "@type": "ReplaceResourceOperation",
            "title": "Edit user",
            "method": "PUT",
            "expects": {
                "@id": "https://example.com/docs#User",
                "supportedProperty": {
                    "@type": "SupportedProperty",
                    "title": "name",
                    "property": "https://example.com/docs#User.name",
                    "range": "http://www.w3.org/2001/XMLSchema#string",
                    "required": true
                }
            }
        }
    }
    

5

我一直在开发一个API,使用JSON Hyper Schema。你可以浏览它,甚至可以注册、登录并执行一些操作。请查看这里:http://api.psprt.com

[编辑] 在这里查看我的最新作品:www.passportedu.com https://github.com/bpanahij/HypermediaServer https://github.com/bpanahij/client-schema.json

我也将API代码开源了: https://github.com/bpanahij/passportedu_schema

随意查看、借鉴和评论。

JSON Hyper Schema (参见 JSON-Schema) 有一种方法可以通过属性成员指定表单:

{
"id": "/api/v1",
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "PassportEDU API",
"name": "PassportEDU API",
"type": "object",
"description": "Bringing global students together with global schools.",
"links": [
   {
      "title": "Log In",
      "rel": "authenticate",
      "href": "/api/v1/authenticate",
      "method": "POST",
      "properties": {
        "username": {
          "title": "Your username",
          "description": "Your email address or username",
          "type": "string"
        },
        "password": {
          "title": "Your password",
          "description": "Your password",
          "type": "password"
        }
      },
      "required": ["username", "password"]
   }
   ]
}

1
你的例子有争议。将表单添加到HAL(或任何作为基础的MIME类型)中是可以的,但该示例涉及登录,而在REST webservice中永远不会找到它,因为按照REST的要求,请求必须是无状态的。http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_3 - inf3rno
实际上,如果请求没有携带状态,则它是毫无意义的。服务器应该是无状态的。登录是一个无状态操作,因为它在成功后总是有用户已登录的结果。HATEOAS:REST 的终极状态,代表超媒体作为应用程序状态引擎。它承诺解耦应用程序代码的状态,并允许用户将状态传递到应用程序中。这就是为什么 HATEOAS 如此强大的原因:它可以轻松扩展,并减少服务器保持状态的需求。 - Brian P Johnson
1
@inf3rno 这只是好的API的众多特点之一: 你所提到的是Leonard Richardson所称的REST的第1级:资源 (http://martinfowler.com/articles/richardsonMaturityModel.html)REST API可以远远超出仅用CRUD表示数据模型的范畴。它们可以代表应用程序的状态。如果应用程序遵循CRUD和SOLID原则的设计,则符合REST。添加超媒体控件,如内容协商和链接,只会使应用程序更好。 - Brian P Johnson

5

JSON Schema标准(特别是“超级模式”)绝对允许这样做。您可以引用一个JSON(超级)模式(使用HTTP头),并且该模式定义了如何将您的数据解释为超文本的规则。

构建链接所需的信息可以位于任何位置。超级模式文件说明如何从数据中组装链接URI(可以是模板),并且它们还指定了HTTP方法、编码类型等。

要获得表单功能:您可以指定要与请求一起提交的数据的完整模式。必需/可选属性、数组长度约束等等。

作为演示,以下是一个JavaScript库的部分演练,该库了解超级模式并可以为链接呈现适当的表单:jsonary.com


Jsonary还有一个“入门”页面:http://jsonary.com/get-started-guide/ - 你可以提取ZIP文件,然后得到一个超模式感知的客户端来开始使用。 - cloudfeet

4

请查看Collection+JSON、HAL和/或Siren。


7
B.5. 为什么HAL没有表单? 在HAL中省略表单是一项有意的设计决策,旨在使其专注于API链接。因此,HAL是建立更复杂功能的基础媒体类型的良好选择。未来计划添加一种额外的媒体类型,在HAL之上增加类似表单的控件。 所以它并不是一个合适的选择...我们将看看其他的。 - Jay Pete
3
我没有看到任何描述形式,因此它并没有真正回答我的问题。 - Jay Pete
2
Siren有表单,只是称其为“操作”。它支持HTML 5中定义的大多数控件类型,如果不是全部。 - ischell

0

据我所知,目前没有公开规定的通用JSON格式,其中包含表单。如果您需要,可以自由定义一个并发布规范。个人建议基于HAL。

如果您决定编写自己的格式,请创建邮件列表并邀请其他人参与。如果不这样做,您可能会过度迎合自己的需求,无意中忽略了一些要求,从而使其无法广泛适用。


嗨,尼古拉斯,我会考虑做这件事的。我希望已经有人完成了这个任务,因为随着我攻击它,任务似乎在增长。这不是一项简单的任务,特别是如果您想在帖子正文中指定对象而不仅仅是字段。例如,我想让某个端点接受一个客户对象的帖子,并且不必指定该对象中的每个字段作为帖子字段。当人们想在XHTML Rest Hypermedia API中执行此操作时,他们该怎么做? - Jay Pete
可以使用PATCH请求到URI,或者定义POST请求到URI更新任何提供的字段,而PUT请求到API会将任何未传递的字段设置为它们的默认值。我认为允许POST到非集合资源是M2M API中的不良实践,仅在基于浏览器的API中因当前Web浏览器的限制而勉强可接受。 - Nicholas Shanks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接