如何在HATEOAS服务器上实现深层链接客户端?

Mar*_*ase 9 hateoas pushstate single-page-application

在SO上有一个类似的问题,但它没有说得好,而且缺乏细节.所以我想写一个更好的问题.

我对如何使用正在使用的单页面应用程序(SPA)实现HATEOAS感兴趣pushState.我希望保留深层链接,以便用户可以在SPA中为URL添加书签并稍后重新访问它们或与其他用户共享.

具体而言,我将提出一个假设的例子.我的单页应用程序托管在https://www.hypothetical.com/.当用户在浏览器中访问此URL时,它会下载SPA和bootstraps.SPA会查看浏览器的当前状态location.href,以确定要获取和呈现的API资源.对于根URL,它会请求https://api.hypothetical.com/,它会呈现如下响应:

{
  "employees": "https://api.hypothetical.com/employees/",
  "offices": "https://api.hypothetical.com/offices/"
}
Run Code Online (Sandbox Code Playgroud)

我掩饰一些细节像acceptcontent-type,但让我们假设,这个假设的API支持内容协商和其他基于REST的善良.

现在,SPA呈现一个用户界面,向用户显示这两个链接关系,用户可以单击"员工"按钮查看员工或"办公室"以查看办公室.假设用户点击"员工".SPA需要pushState()一些新的href,否则此导航决策将不会出现在历史记录中,用户将无法使用"返回"按钮返回到第一个屏幕.

这会带来一个小小的困境:SPA应该使用什么样的href?显然,它无法推动https://api.hypothetical.com/employees/.它不仅不是SPA中的有效资源,它甚至不在同一个源中,pushState()如果新的href位于不同的原点,则抛出异常.

这种困境[也许]很容易解决:SPA知道所谓的链接关系employees,因此SPA可以对此资源的URL进行硬编码:pushState(...,'https://www.hypothetical.com/employees').接下来,它使用链接关系https://api.hypothetical.com/employees/来获取员工集合.API返回如下结果:

{
  "employees": [
    {
      "name": "John Doe",
      "url": "https://api.hypothetical.com/employees/123",
    },
    {
      "name": "Jane Doe",
      "url": "https://api.hypothetical.com/employees/234",
    },
    ...
  ]
}
Run Code Online (Sandbox Code Playgroud)

有两个以上的结果,但我缩写为省略号.

SPA希望在表中显示此数据,其中每个员工姓名都是超链接,以便用户可以查看有关特定员工的更多详细信息.用户点击"John Doe".SPA现在需要显示有关John Doe的详细信息.它可以使用链接关系轻松获取资源,它可能会得到如下内容:

{
  "name": "John Doe",
  "phone_number": "2025551234",
  "office": {
    "location": "Washington, DC",
    "url": "https://api.hypothetical.com/offices/1"
  },
  "supervisor": {
    "name": "Jane Doe",
    "url": "https://api.hypothetical.com/employees/234"
  },
  "url": "https://api.hypothetical.com/employees/123"
}
Run Code Online (Sandbox Code Playgroud)

但现在又出现了同样的困境:SPA应该选择哪个URL代表这个新的内部状态?这与上面的困境相同,除了这次不可能对单个SPA URL进行硬编码,因为有任意数量的员工.

我认为这是一个非常重要的问题,但让我们做一些hacky,以便我们继续前进:SPA通过用'www'替换主机名中的'api'来构建URL.它非常难看,但它不违反HATEOAS(SPA URL仅用于客户端),现在SPA可以pushState(...,'https://www.hypothetical.com/employees/123'.通用这种方法,SPA可以显示任何链接关系的导航选项,用户可以浏览相关资源:这个人的办公室在哪里?主管的电话号码是多少?

这仍然无法解决深层链接问题.如果用户书签https://www.hypothetical.com/employees/123,关闭浏览器,然后稍后重新访问此书签,该怎么办?现在SPA没有回忆底层API资源是什么.我们不能反转替换(例如用'api'替换'www'),因为那不是HATEOAS.

HATEOAS的心态似乎是SPA应该https://api.hypothetical.com/再次请求并关注链接回到John Doe的员工档案,但是没有固定的链接关系从employees作为集合到John Doe作为特定员工,所以这不起作用.

另一种HATEOAS方法是应用程序可以为它发现的URL添加书签.例如,它可以存储将先前看到的SPA URL映射到API URL的哈希表:

{
  "https://www.hypothetical.com/employees/123": "https://api.hypothetical.com/employees/123"
}
Run Code Online (Sandbox Code Playgroud)

这将允许SPA查找底层资源并呈现UI,但它需要跨会话的持久状态.例如,如果我们将此哈希存储在HTML5存储中并且用户清除其HTML5存储,则所有书签都将中断.或者,如果用户将此书签发送给其他用户,则该其他用户不会将SPA URL映射到API URL.

一句话:在HATEOAS API之上实现深层链接SPA对我来说非常尴尬.在我目前的项目中,我已经使用SPA构建了几乎所有的URL.作为该决定的结果,API必须为各个资源发送唯一标识符(而不仅仅是URL),以便SPA可以为它们生成良好的URL.

有没有人有这方面的经验?有没有办法满足这些看似矛盾的标准?

Cle*_*v3r 5

我也曾在这个概念上挣扎过。hatoas api 就是将客户端与服务器解耦,因此我认为将“客户端 url -> api uri”的映射硬编码到本地存储中是一种倒退,原因有您提到的等等。话虽这么说,为客户端和服务器使用单独的域语言没有任何耦合。您可以在页面上进行某些操作(例如单击员工#_),以任何您喜欢的方式触发推送状态,例如“/employees/#”。当应用程序通过链接共享引导到另一个浏览器时,某些服务将知道如何读取 DSL '/employees/#' 并将您的应用程序快速转发到适当的状态。在权限不同的情况下,您会收到一些消息,解释为什么您无法访问员工 # _,而是列出您有权查看的员工。如果您有很多像这样的深层链接视图,它可能会变得非常麻烦,但这就是用平面 url 层次结构表示复杂状态对象的代价。

我考虑过的另一种方法是明确的“共享此”按钮。单击该按钮将要求服务器在自己的 DSL 中铸造一个 url,以便当其他用户访问此共享 url 时,服务器可以将相关状态信息传输到客户端。必须设置客户端来处理这种情况,也许需要一些条件“如果此资源包含“深层链接”属性,则执行一些特殊操作。

不管怎样,这都不是一个容易解决的问题。


Chr*_*our 0

我可能遗漏了一些东西,但大概对于不同的资源类型,您有不同的视图/页面。因此,当您加载员工时,它使用employees.html视图,而当您访问员工时,您使用employee.html视图..所以为什么它会更像那样

//user clicks employees
pushState( "employees.html#https://api.hypothetical.com/employees/" )

//user clicks on an employeed
pushState( "employee.html#https://api.hypothetical.com/employees/12345/" )
Run Code Online (Sandbox Code Playgroud)

我的意思是 URI 组件肯定会对 url 进行编码...但现在您拥有指向资源视图的深层链接,不需要 hack。

我看到了pushState,但实际上它可能更准确地被认为是pushViewState

  • 我猜他指的是局部或路线,所以它是 SPA。 (2认同)