避免在前端构建 (REST API) URL

Avoiding building (REST API) URLs on the frontend

我已经在后端工作了一段时间,最近才开始在前端工作,这让我更接近端到端的 REST 实现。

更重要的是,REST 的一个重要原则是使其可发现和一致,以便客户端知道如何通用地处理资源(HATEOAS、JsonApi 等)。 我一直在阅读 this Google article 并且有以下几点:

If an API uses HTTP simply and directly, it will only have to document three or four things. (And if an API requires you to read a lot of documentation to learn how to use it, then it is probably not using HTTP as the uniform API.) The four elements of an HTTP API are:

  1. A limited number of fixed, well-known URLs. These are analogous to the names of the tables in a database. For optional extra credit, make all the fixed URLs discoverable from a single one.

以后……

There is also a shortage of people who understand how to design good HTTP/REST APIs. Unfortunately, we see many examples of APIs that attempt to adopt the entity-oriented HTTP/REST style, but fail to realize all the benefits because they do not follow the model consistently. Some common mistakes are:

  1. Using "local identifiers" rather than URLs to encode references between entities. If an API requires a client to substitute a variable in a URI template to form the URL of a resource, it has already lost an important part of the value of HTTP’s uniform interface. Constructing URLs that encode queries is the only common use for URI templates that is compatible with the idea of HTTP as a uniform interface.

我同意两者,但我看不出如何实现

情况是这样的:

当导航到 /site/articles 时,前端会知道调用 /articles API 端点 - 这是 Google 提到的“有限固定网址”之一。 鉴于文章实体返回的 links,删除/更新也会完成。 使用客户端导航,前端还可以“重定向”到 /site/articles/1.

棘手的部分是当用户直接导航到 /site/articles/1 时——页面如何知道在不构建 URL 本身(或以某种方式翻译它)的情况下调用 /articles/$id

这些是我看到的选项:

  1. 构建URL(这基本上就是上面提到的“第一个常见错误”)
    // /site/articles/1
    const apiUrl = '/articles/' + location.pathname.split('/')[3]
    // /articles/1
    
  2. 从发现 link 中构建 URL(先前选项的变体,IMO 仍然很糟糕)
    // /site/articles/1
    const apiUrl = api.endpoints.articles + location.pathname.split('/')[3] // or replace or whatever
    // /articles/1
    
  3. 在前端编码实体“self”link URL
    // /site/articles/L2FydGljbGVzLzE=
    const apiUrl = atob(location.pathname.split('/')[3])
    // /articles/1
    
    我对此的关注是:
    • 有点丑
    • 不安全(xsrf/开放重定向...)
    • 它强制前端构建 URLs(只有它能理解)
  4. 编码实体标识符(我认为是“自我”link),然后在 /articles 中查找它,然后调用返回的 link
    // /site/articles/L2FydGljbGVzLzE=
    const entityId = atob(location.pathname.split('/')[3])
    const apiUrl = api.get(api.endpoints.articles)
        .first(article => article.links.self === entityId)
        .links.self
    // /articles/1
    
    • 比3还要丑。
    • 足够安全
    • 乍一看似乎毫无意义...

如果您担心用户通过键入 URL 直接导航到页面,那么这是固定的 well-known URL 之一。很可能任何“bookmark-able”都会出现在该列表中。

这里的关键是短语“对实体之间的引用进行编码”。这不是实体之间的 link,它是初始的 entry-point,因此可以从头开始构建 URL。这是一种不灵活的做法,但作为 entry-point 你别无选择。 “常见错误”是在导航关系时在整个应用程序中嵌入“URL-building”。 IE。通过用户 ID 获得一篇文章的“评论者”列表,并通过将路径耦合到文章页面中的用户来构建这些 URLs。