同一资源的不同RESTful表示
我的应用程序在/foo
有一个资源。 通常情况下,它是这样的HTTP响应负载:
{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}
客户端并不总是需要这个对象的全部四个成员。 什么是RESTfully语义的方式让客户告诉服务器在表示中需要什么? 如果它想要:
{"a": "some text", "b": "some text", "d": "some text"}
它应该如何GET
它? 一些可能性(如果我误解了REST,我正在寻找更正):
-
GET /foo?sections=a,b,d
。- 查询string(毕竟称为查询string)似乎意味着“查找与此条件匹配的资源并告诉我有关它们”,而不是“根据此自定义向我表示此资源”。
-
GET /foo/a+b+d
我最喜欢的是REST语义不包含这个问题 ,因为它的简单性。- 打破URI不透明,违反HATEOAS。
- 似乎打破资源(URI的唯一含义是识别一个资源)和表示之间的区别。 但这是值得商榷的,因为它与代表
/widget/<id>
资源的可视列表/widget/<id>
一致,这是我从来没有遇到的问题。
- 松开我的约束,对
GET /foo/a
等做出响应,并让客户端对每个/foo
组件的请求进行请求。- 如果
/foo
有数百个组件,而客户端需要100个组件,那么开销就会增加,这可能会变成一场噩梦。 - 如果我想支持
/foo
的HTML表示,我必须使用Ajax,如果我只想要一个可以被抓取,由极简主义浏览器渲染的HTML页面,这是有问题的。 - 为了维护HATEOAS,它还需要指向其他表示中存在的那些“子资源”的链接,可能在
/foo
:{"a": {"url": "/foo/a", "content": "some text"}, ...}
- 如果
-
GET /foo
,Content-Type: application/json
和{"sections": ["a","b","d"]}
。- 没有书签和不可caching。
- HTTP没有为
GET
定义正文语义。 这是合法的HTTP,但我怎样才能保证一些用户的代理不剥夺GET
请求的身体? - 我的REST客户端不会让我把
GET
请求的身体,所以我不能用它来testing。
- 一个自定义的HTTP头:
Sections-Needed: a,b,d
- 如果可能的话,我宁愿避免自定义标题。
- 没有书签和不可caching。
-
POST /foo/requests
,Content-Type: application/json
和{"sections": ["a","b","d"]}
。 用Location: /foo/requests/1
接收201
。 然后GET /foo/requests/1
接收所需的/foo
表示- 笨重的; 需要来回和一些奇怪的代码。
- 由于
/foo/requests/1
只是一个只能使用一次的别名,并且只有在被请求时才被保留,所以不可用于书签和不可caching。
我会build议查询string解决scheme(你的第一个)。 你反对其他select的论点是很好的论点(当我试图解决同样的问题的时候,我已经遇到了这个论点)。 特别是,“放松对foo/a
的约束/响应”解决scheme可以在有限的情况下工作,但是从API实现和消费两方面都给API带来了很多复杂性,而且根本不值得我们付出努力。
我会弱化你的“似乎是”意味着一个常见的例子:考虑资源是一个大的对象列表( GET /Customers
)。 页面这些对象是完全合理的,使用查询string来做这件事是司空见惯的: GET /Customers?offset=100&take=50
作为一个例子。 在这种情况下,查询string不在列出的对象的任何属性上进行过滤,而是为对象的子视图提供参数。
更具体地说,我会说你可以通过使用查询string的这些标准来保持一致性和HATEOAS:
- 返回的对象应该是与没有查询string的Url返回的实体相同的实体。
- 没有查询string的Uri应该返回完整的对象 – 在同一个Uri中查询string可用的任何视图的超集。 所以,如果你caching未装饰的Uri的结果,你知道你有完整的实体。
- 针对给定查询string返回的结果应该是确定性的,这样具有查询string的Uris可以轻松地进行caching
但是,这些Uris 返回什么有时会造成更复杂的问题:
- 返回一个不同的实体types的Uris不同,只有querystring可能是不受欢迎的(
/foo
是一个实体,但foo/a
是一个string); 另一种方法是返回一个部分填充的实体 - 如果你使用不同的实体types进行子查询,那么,如果你的
/foo
没有a
,那么404
状态是有误导性的(/foo
确实存在!),但是空的响应可能会同样令人困惑 - 返回部分填充的实体可能是不受欢迎的,但是返回实体的一部分可能是不可能的,或者可能更令人困惑
- 如果你有一个强大的模式,返回一个部分填充的实体可能是不可能的(如果
a
是强制性的,但是客户端只请求b
,则你不得不返回a
或一个无效对象的垃圾值)
在过去,我试图通过定义所需实体的具体命名“视图”来解决这个问题,并允许查询string像?view=summary
或?view=totalsOnly
– 限制排列的数量。 这也允许定义对服务的消费者“有意义”的实体的子集,并且可以被logging。
最后,我认为这归结为一致性问题:你可以相对容易地使用查询string来满足HATEOAS的指导,但是你所做的select需要在你的API中保持一致,而且据我所知,这是有据可查的。
我已经决定如下:
支持less数成员组合 :我会为每个组合提出一个名称。 例如,如果一篇文章包含作者,date和正文的成员, /article/some-slug
将返回所有内容,而/article/some-slug/meta
将仅返回作者和date。
支持多种组合:我将用连字符分隔成员名称: /foo/abc
。
无论哪种方式,如果不支持组合,我将返回一个404
。
build筑约束
rest
识别资源
从 REST 的定义 :
资源R是时间变化的隶属函数M R ( t ),其对于时间t映射到一组实体或等价的值。 集合中的值可以是资源表示和/或资源标识符。
一个表示是一个HTTP正文,一个标识符是一个URL。
这是至关重要的。 标识符只是与其他标识符和表示相关的值。 这与标识符→表示映射不同。 服务器可以将任何标识符映射到任何表示,只要这两个标识符都由相同的资源关联即可。
这是由开发人员提出资源定义,通过考虑“用户”和“post”等类别来合理描述业务。
HATEOAS
如果我真的关心完美的HATEOAS,我可以把超链接放在/foo
表示的某个地方/foo/members
,而且这个表示只包含一个到每个支持的成员组合的超链接。
HTTP
从URL的定义 :
查询组件包含非分层数据,与path组件中的数据一起用于标识URI的scheme和命名权限(如果有的话)范围内的资源。
所以/foo?sections=a,b,d
和/foo?sections=b
是不同的标识符。 但是它们可以在相同的资源内被关联 ,同时被映射到不同的表示。
HTTP的404
代码意味着服务器找不到任何东西来映射URL,而不是URL不与任何资源相关联。
function
浏览器或caching将不会有斜杠或连字符的麻烦。
其实这取决于资源的function。 例如,如果资源代表一个实体:
/customers/5
这里'5'表示客户的ID
响应:
{ "id": 5, "name": "John", "surename": "Doe", "marital_status": "single", "sex": "male", ... }
因此,如果我们仔细检查它,每个json属性实际上代表客户资源实例上的logging字段 。 假设消费者希望获得部分响应,意味着部分领域 。 我们可以把它看作是消费者希望能够通过请求select各个领域的能力,这对他来说是有趣的,但不是更多(为了节省stream量或性能,如果部分领域很难计算) 。
我认为在这种情况下,最可读和正确的API将是(例如,只获取名称和确定名称 )
/customers/5?fields=name,surename
响应:
{ "name": "John", "surename": "Doe" }
HTTP / 1.1
- 如果请求非法字段名称 – 404(未find)返回
- 如果请求不同的字段名称 – 将生成不同的响应,这也与caching一致。
- 缺点:如果请求的是相同的字段,但是字段顺序不同(例如:
fields=id,name
或fields=name,id
),虽然响应是相同的,但是这些响应将被分开caching。
HATEOAS
- 在我看来,纯粹的HATEOAS并不适合解决这个特殊的问题。 因为为了达到这个目的,你需要一个单独的资源来处理字段组合的每一个排列,这是过度的,因为它是广泛地膨胀的API(例如,你有一个资源8个字段,你将需要 排列!)。
- 如果仅为字段build模资源,但不是对所有排列进行资源化,则会对性能产生影响,例如,要将往返次数降至最低。
如果a,b,c是资源的属性,如pipe理angular色属性,正确的方法是使用第一种方法,你build议GET /foo?sections=a,b,d
因为在这种情况下,你会申请一个过滤到foo
集合。 否则,如果a,b和c是foo
集合的单数资源,那么将遵循的方法是执行一系列GET
请求/foo/a /foo/b /foo/c
。 正如你所说,这种方法对于请求有很高的有效载荷,但是这是遵循Restfull方法的正确方法。 我不会使用你提出的第二个build议,因为URL中的加号具有特殊意义。
另一个build议是放弃使用GET和POST,并为foo
集合创build一个动作,如下所示: /foo/filter
或/foo/selection
或表示集合上动作的任何动词。 这样,有一个post请求体,你可以传递一个json的资源列表。
您可以在请求头application / vnd.com.mycompany.resource.rep2中使用第二个供应商介质types,但不能将此标记为书签,但查询参数不可caching(/ foo?sections = a,b,c )你可以看一下matrix参数,但是对于这个问题,他们应该是可caching的URLmatrix参数与请求参数