REST风格的api设计,HATEOAS和资源发现
HATEOAS背后的核心思想之一是客户端应该能够从单一入口点URL开始,并发现所有可用的暴露资源和状态转换。 虽然我完全可以看到HTML和浏览器背后的人物是如何工作的,但点击链接和“提交”按钮后,我被问到如何将这个原则应用于我不幸遇到的问题。
我喜欢在论文和教育文章中介绍RESTful设计原则,如何获得一杯咖啡就是一个很好的例子。 我会尽量遵循惯例,并提出一个简单而且没有繁琐细节的例子。 我们来看看邮政编码和城市。
问题1
比方说,我想设计RESTful api来通过邮政编码查找城市。 我提出了嵌入到邮政编码中的名为“城市”的资源,以便http://api.addressbook.com/zip_codes/02125/cities
上的GET返回包含代表多尔切斯特和波士顿的两条记录的文档。
我的问题是:如何通过HATEOAS发现这样的网址? 在http://api.addressbook.com/zip_codes
下暴露所有〜40K邮政编码的索引可能是不切实际的。 即使40K物品指数没有问题,请记住,我已经做出了这个例子,并且有更大量的收藏。
所以基本上,我想公开的不是链接,而是链接模板,而是像这样: http://api.addressbook.com/zip_codes/{:zip_code}/cities
: http://api.addressbook.com/zip_codes/{:zip_code}/cities
: http://api.addressbook.com/zip_codes/{:zip_code}/cities
,这违背了原则,并依赖于出局客户拥有的带宽知识。
问题2
比方说,我想要公开具有特定过滤功能的城市索引:
GET http://api.addressbook.com/cities?name=X
只会返回名称与X
匹配的城市。
GET http://api.addressbook.com/cities?min_population=Y
只会返回人口等于或大于Y
。
当然,这两个过滤器可以一起使用: http://api.addressbook.com/cities?name=X&min_population=Y
: http://api.addressbook.com/cities?name=X&min_population=Y
。
在这里,我想要公开的不仅是url,还有这两个可能的查询选项以及它们可以组合的事实。 如果没有客户端对这些过滤器的语义的带外知识以及将它们组合到动态URL后面的原则,这似乎根本不可能。
那么HATEOAS背后的原理是如何帮助我们将这种琐碎的API变得真正RESTful的呢?
我建议使用XHTML表单:
GET /
HTTP/1.1 OK
<form method="get" action="/zip_code_search" rel="http://api.addressbook.com/rels/zip_code_search">
<p>Zip code search</p>
<input name="zip_code"/>
</form>
GET /zip_code_search?zip_code=02125
HTTP/1.1 303 See Other
Location: /zip_code/02125
HTML中缺少的是form
的rel
属性。
看看这篇文章:
总而言之,将XHTML视为您的RESTful服务的默认表示法有几个原因。 首先,您可以利用<a>
, <form>
和<input>
等重要元素的语法和语义,而不是自行创建。 其次,你最终会得到类似于网站的服务,因为它们可以被用户和应用程序浏览。 XHTML仍然由一个人解释 - 在开发过程中它只是一个程序员,而不是运行时的用户。 这简化了整个开发过程中的事情,并使消费者更容易了解您的服务如何运作。 最后,您可以利用标准的Web开发框架来构建您的RESTful服务。
另请查看OpenSearch。
为了减少请求的数量,请考虑这个响应:
HTTP/1.1 200 OK
Content-Location: /zip_code/02125
<html>
<head>
<link href="/zip_code/02125/cities" rel="related http://api.addressbook.com/rels/zip_code/cities"/>
</head>
...
</html>
想到这个解决方案,但我不确定我是否真的推荐它:不是返回资源URL,而是返回描述端点的WADL URL。 例:
<application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<grammars/>
<resources base="http://localhost:8080/cities">
<resource path="/">
<method name="GET">
<request>
<param name="name" style="query" type="xs:string"/>
<param name="min-population" style="query" type="xs:int"/>
</request>
<response>
<representation mediaType="application/octet-stream"/>
</response>
</method>
</resource>
</resources>
</application>
该示例由CXF从此Java代码自动生成:
import javax.ws.rs.GET;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
public class Cities {
@GET
public Response get(@QueryParam("name") String name, @QueryParam("min-population") int min_poulation) {
// TODO: build the real response
return Response.ok().build();
}
}
在回答问题1时,我假定您的单一入口点是http://api.addressbook.com/zip_codes
,其目的是让客户遍历整个邮政编码集合并最终检索相关城市给他们。
在这种情况下,我会让http://api.addressbook.com/zip_codes
资源返回重定向到邮政编码的第一页,例如:
http://api.addressbook.com/zip_codes?start=0&end=xxxx
这将包含一个“页”值得的邮政编码链接(无论数字是适合系统处理,加上一个链接到下一页(和前一页,如果有的话)。
如果需要,这将使客户能够抓取整个邮政编码列表。
每个页面返回的网址与此类似:
http://api.addressbook.com/zip_codes/02125
然后决定是否将城市信息包含在由邮政编码网址返回的表示中,或根据需要链接到该表示。
现在,客户可以选择是否遍历整个邮政编码列表,然后为每个邮政编码请求邮政编码(然后请求城市),或者请求一个邮政编码页面,然后请求向下追溯到某个单位
链接地址: http://www.djcxy.com/p/41015.html