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

时间:2015-03-13 19:24:13

标签: single-page-application pushstate hateoas

  

在SO上有一个similar question,但它没有说好,而且缺乏细节。所以我试着写一个更好的问题。

我对如何使用pushState的单页面应用程序(SPA)实现HATEOAS感兴趣。我希望保留深层链接,以便用户可以在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/"
}

我对acceptcontent-type等一些细节进行了修饰,但我们假设这个假设的API支持内容协商和其他RESTful优点。

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

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

这种困境[可能]很容易解决: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",
    },
    ...
  ]
}

结果有两个以上,但我省略了省略号。

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

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

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

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

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

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

John Doe

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

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

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

2 个答案:

答案 0 :(得分:4)

我也一直在努力解决这个问题。 hateoas api就是将客户端与服务器分离,所以我认为将“客户端URL - > api uri”映射硬编码到本地存储中是出于你提到的原因而向后退一步。话虽如此,关于为客户端和服务器提供单独的域语言并没有什么联系。您可以在页面上执行某些操作(例如单击employee#_),使用您喜欢的任何内容触发推送状态,也许是“/ employees /#”。当应用程序通过链接共享引导到另一个浏览器时,某些服务将知道如何读取DSL'/ employees /#'并将您的应用程序快速转发到适当的状态。在权限不同的情况下,您会收到一些消息,说明您无法访问员工#_的原因,而是列出您有权查看的员工。如果你有很多像这样的深层链接视图,它会变得非常麻烦,但这是代表具有平面url层次结构的复杂状态对象的代价。

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

无论哪种方式,这都不是一个容易解决的问题。

答案 1 :(得分:1)

我可能遗漏了一些东西,但据推测,对于不同的资源类型,您有不同的视图/页面。因此,当您加载员工时,它会使用employees.html视图,当您找到员工时,您会使用employee.html视图。为什么woudl它更像是

//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/" )

我的意思是URI组件肯定会对网址进行编码...但是现在你有了资源视图的深层链接,不需要黑客攻击。

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

相关问题