RESTful API 设计、HATEOAS 和资源发现

发布于 2025-01-01 07:01:01 字数 1301 浏览 1 评论 0原文

HATEOAS 背后的核心思想之一是客户端应该能够从单个入口点 URL 开始,并发现所有公开的资源和可用的状态转换。虽然我可以完美地看到 HTML 和浏览器后面的人如何点击链接和“提交”按钮,但我还是被问到如何将这一原则应用于我(不)幸运处理的问题。

我喜欢 RESTful 设计原则在论文和教育文章中的呈现方式,这些文章都有意义,如何喝杯咖啡 就是一个很好的例子。我将尝试遵循惯例并提出一个简单且没有繁琐细节的示例。让我们看看邮政编码和城市。

问题 1

假设我想设计 RESTful api 通过邮政编码查找城市。我提出了嵌套在邮政编码中的名为“城市”的资源,以便 http://api.addressbook.com/zip_codes/02125/cities 上的 GET 返回包含两条记录的文档,这些记录代表多切斯特和波士顿。

我的问题是:如何通过 HATEOAS 发现这样的 url?在 http://api.addressbook.com/zip_codes 下公开所有 ~40K 邮政编码的索引可能是不切实际的。即使拥有 40K 项索引不是问题,请记住我已经编造了这个示例,而且还有更大规模的集合。

所以本质上,我想公开的不是链接,而是链接模板,如下所示:http://api.addressbook.com/zip_codes/{:zip_code}/cities,这违背了原则并依赖于客户拥有的带外知识。

问题 2

假设我想公开具有某些过滤功能的城市索引:

  • http://api.addressbook.com/cities?name=X 上的

    GET 将仅返回名称匹配 < 的城市code>X。

  • http://api.addressbook.com/cities?min_population=Y 上的 GET 只会返回人口等于或大于 Y 的城市。

当然,这两个过滤器可以一起使用:http://api.addressbook.com/cities?name=X&min_population=Y

在这里,我不仅想公开 url,还想公开这两个可能的查询选项以及它们可以组合的事实。如果客户端没有对这些过滤器的语义以及将它们组合成动态 URL 背后的原理的带外知识,这似乎根本不可能。

那么,HATEOAS 背后的原则如何帮助使如此琐碎的 API 真正实现 RESTful?

One of the core ideas behind HATEOAS is that clients should be able to start from single entry point URL and discover all exposed resources and state transitions available for those. While I can perfectly see how that works with HTML and a human behind a browser clicking on links and "Submit" buttons, I'm quizzed about how this principle can be applied to problems I'm (un)lucky to deal with.

I like how RESTful design principle is presented in papers and educational articles where it all makes sense, How to GET a Cup of Coffee is a good example of such. I'll try to follow convention and come up with an example which is simple and free from tedious details. Let's look at zip codes and cities.

Problem 1

Let's say I want to design RESTful api for finding cities by zip codes. I come up with resources called 'cities' nested into zip codes, so that GET on http://api.addressbook.com/zip_codes/02125/cities returns document containing, say, two records which represent Dorchester and Boston.

My question is: how such url can be discovered through HATEOAS? It's probably impractical to expose index of all ~40K zip codes under http://api.addressbook.com/zip_codes. Even if it's not a problem to have 40K item index, remember that I've made this example up and there are collections of much greater magnitude out there.

So essentially, I would want to expose not link, but link template, rather, like this: http://api.addressbook.com/zip_codes/{:zip_code}/cities, and that goes against principles and relies on out-of-band knowledge possessed by a client.

Problem 2

Let's say I want to expose cities index with certain filtering capabilities:

  • GET on http://api.addressbook.com/cities?name=X would return only cities with names matching X.

  • GET on http://api.addressbook.com/cities?min_population=Y would only return cities with population equal or greater than Y.

Of course these two filters can be used together: http://api.addressbook.com/cities?name=X&min_population=Y.

Here I'd like to expose not only url, but also these two possible query options and the fact that they can be combined. This seems to be simply impossible without client's out-of-band knowledge of semantics of those filters and principles behind combining them into dynamic URLs.

So how principles behind HATEOAS can help making such trivial API really RESTful?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

撩动你心 2025-01-08 07:01:01

我建议使用 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 中缺少的是 formrel 属性。

查看这篇文章

总而言之,有几个理由将 XHTML 视为
RESTful 服务的默认表示。首先,你可以
利用重要元素的语法和语义,例如

而不是发明自己的。第二,你会结束
提供感觉很像网站的服务,因为它们将
用户和应用程序均可浏览。 XHTML 仍然是
由人类解释——只是开发期间的程序员
而不是运行时的用户。这简化了整个过程
开发流程,让消费者更容易了解如何
你的服务有效。最后,您可以利用标准 Web
开发框架来构建您的 RESTful 服务。

另请查看 OpenSearch


To reduce the number of request consider this response:

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>

I suggest using XHTML forms:

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

What's missing in HTML is a rel attribute for form.

Check out this article:

To summarize, there are several reasons to consider XHTML as the
default representation for your RESTful services. First, you can
leverage the syntax and semantics for important elements like <a>,
<form>, and <input> instead of inventing your own. Second, you'll end
up with services that feel a lot like sites because they'll be
browsable by both users and applications. The XHTML is still
interpreted by a human—it's just a programmer during development
instead of a user at runtime. This simplifies things throughout the
development process and makes it easier for consumers to learn how
your service works. And finally, you can leverage standard Web
development frameworks to build your RESTful services.

Also check out OpenSearch.


To reduce the number of request consider this response:

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>
鹤舞 2025-01-08 07:01:01

我想到了这个解决方案,但我不确定我是否真的推荐它:返回描述端点的 WADL URL,而不是返回资源 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();
    }
}

This solution comes to mind, but I'm not sure that I'd actually recommend it: instead of returning a resource URL, return a WADL URL that describes the endpoint. Example:

<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>

That example was autogenerated by CXF from this Java code:

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();
    }
}
海风掠过北极光 2025-01-08 07:01:01

在回答问题 1 时,我假设您的单一入口点是 http://api.addressbook.com/zip_codes,其目的是使客户端能够遍历整个 zip 集合代码并最终检索与其相关的城市。

在这种情况下,我将使 http://api.addressbook.com/zip_codes 资源返回到邮政编码第一页的重定向,例如:

http://api.addressbook .com/zip_codes?start=0&end=xxxx

这将包含一个“页面”的邮政编码链接(无论适合系统处理的数字,再加上下一页(和上一页)的链接如果有的话)

。如果需要的话,客户端可以抓取整个邮政编码列表。

每个页面中返回的 URL 看起来类似于:

http://api.addressbook.com/zip_codes/02125

然后它就可以了。问题在于决定是否在邮政编码 URL 返回的表示形式中包含城市信息,或者根据需要包含指向它的链接。

现在,客户端可以选择是否遍历整个邮政编码列表,然后进行请求。邮政编码(和然后是城市),或者请求邮政编码页面,然后请求深入到某个部分

In answer to question 1, I'm assuming your single entry point is http://api.addressbook.com/zip_codes, and the intention, is to enable the client to traverse the entire collection of zip codes and ultimately retrieve the cities related to them.

In which case i would make the http://api.addressbook.com/zip_codes resource return a redirect to the first page of zip codes, for example:

http://api.addressbook.com/zip_codes?start=0&end=xxxx

This would contain a "page" worth of zip code links (whatever number is suitable for the system to handle, plus a link to the next page (and previous page if there is one).

This would enable a client to crawl the entire list of zip codes if it so desired.

The urls returned in each page would look similar to this:

http://api.addressbook.com/zip_codes/02125

And then it would be a matter of deciding whether to include the city information in the representation returned by a zip code URL, or the link to it depending on the need.

Now the client has a choice whether to traverse the entire list of zip codes and then request the zipcode (and then cities) for each, or request a page of zip codes, and then request drill down to a parti

九公里浅绿 2025-01-08 07:01:01

我遇到了同样的问题 - 所以我通过一个实际的例子来解决这两个问题(以及一些你还没有想到的问题)。 http://thereisnorightway.blogspot.com/2012 /05/api-example-using-rest.html?m=1

基本上,问题 1 的解决方案是更改表示形式(正如 Roy 所说,将时间花在资源上)。您不必返回所有 zip,只需让您的资源包含分页即可。举个例子,当您从新闻网站请求新闻页面时 - 它会为您提供今天的新闻,并链接到更多内容,即使所有文章可能都位于相同的 url 结构下,即 ...article/123 等

问题 2 是有点笨拙 - 在 http 中有一个名为 OPTIONS 的小命令,我在示例中使用它基本上反映了 url 的功能 - 尽管你也可以在表示中解决这个问题,但它只会更复杂。基本上,它返回一个显示资源功能的自定义结构(包括可选参数)。

让我知道你的想法!

I was running into these same questions - so I worked through a practical example that solves both of these problems (and a few you haven't thought of yet). http://thereisnorightway.blogspot.com/2012/05/api-example-using-rest.html?m=1

Basically, the solution to problem 1 is that you change your representation (as Roy says, spend your time on the resource). You don't have to return all zips, just make your resource contain paging. As an example, when you request news pages from a news site - it gives you todays news, and links to more, even though all the articles may live under the same url structure, I.e. ...article/123, etc

Problem 2 is a little ackward - there is a little used command in http called OPTIONS that I used in the example to basically reflect the url's capability - although you could solve this in the representation too, it would just be more complicated. Basically, it gives back a custom structure that shows the capabilities of the resource (including optional parameters).

Let me know what you think!

青丝拂面 2025-01-08 07:01:01

我感觉您跳过了书签网址。这是第一个网址,而不是获取城市或邮政编码的网址。

因此,您从 ab:=http://api.addressbook.com 开始,

第一个链接返回可用链接的列表。这就是网络的运作方式。您访问 www.yahoo.com,然后开始点击链接,但不知道它们去了哪里。

因此,从原始链接 ab: 您将得到其他链接,它们可能具有 REL 链接,解释应如何访问这些资源或可以提交哪些参数。

我们在设计系统时首先想到的是从书签页面开始,确定可以访问的所有不同链接。

我确实同意你关于“客户端对这些过滤器语义的带外知识”的看法,我很难相信机器可以适应现有的内容,除非它有一些先入为主的规范,例如 HTML。客户端更有可能是由了解所有可能性的开发人员构建的,然后对应用程序进行编码以“可能”期望这些链接可用。如果链接可用,则程序可以使用开发人员在操作资源之前实现的逻辑。如果它不存在,那么它就不会执行该链接。最后,在开始遍历应用程序之前布置可能的路径。

I feel like you skipped over the bookmark URL. That is the first url, not the ones to get cities or zip codes.

So you start at ab:=http://api.addressbook.com

This first link returns back a list of available links. This is how the web works. You go to www.yahoo.com and then you start clicking links not knowing where they go.

So from the original link ab: you would get back the other links and they could have REL links that explain how those resources should be accessed or what parameters can be submitted.

The first think we did when designing our systems is to start from the bookmark page and determine all the different links that could be accessed.

I do agree with you about the 'client's out-of-band knowledge of semantics of those filters' it's hard for me to buy that a machine can just adapt to what is there unless it had some preconceived specification like HTML. It's more likely that the client is built by a developer who knows all the possibilities and then codes the application to 'potentially' expect those links to be available. If the link is available then the program can use the logic the developer implemented prior to act the resource. If it's not there then it just doesn't execute the link. In the end possible paths are laid out prior to beginning to traverse the application.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文