
[英]How can I avoid hardcoding URLs in a RESTful client/server web app with deep linking?
[英]How to implement deep linking client on top of HATEOAS server?
SO 上有一个类似的问题,但措辞不佳且缺乏细节。 所以我想写一个更好的问题。
我对如何通过使用pushState
的单页应用程序 (SPA) 实现 HATEOAS 很感兴趣。 我想保留深层链接,以便用户可以在 SPA 中为 URL 添加书签,稍后重新访问它们或与其他用户共享。
为了具体起见,我将提出一个假设的例子。 我的单页应用程序托管在https://www.hypothetical.com/
。 当用户在浏览器中访问这个 URL 时,它会下载一个 SPA 和引导程序。 SPA 查看浏览器的当前location.href
以确定要获取和呈现的 API 资源。 在根 URL 的情况下,它请求https://api.hypothetical.com/
,它呈现如下响应:
{
"employees": "https://api.hypothetical.com/employees/",
"offices": "https://api.hypothetical.com/offices/"
}
我忽略了一些细节,例如accept
和content-type
,但我们假设这个假设的 API 支持内容协商和其他 RESTful 特性。
现在 SPA 呈现一个用户界面,向用户显示这两个链接关系,用户可以单击“Employees”按钮查看员工或单击“Offices”查看办公室。 假设用户点击“员工”。 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 来代表这个新的内部 state? 这与上面的困境相同,除了这次不可能对单个 SPA URL 进行硬编码,因为有任意数量的员工。
我认为这是一个非常重要的问题,但让我们做一些 hacky 以便我们继续前进:SPA 通过将主机名中的“api”替换为“www”来构造一个 URL。 它非常丑陋,但它没有违反 HATEOAS(SPA URL 仅在客户端使用)现在 SPA 可以pushState(...,'https://www.hypothetical.com/employees/123'
。概括这个方法,SPA可以显示任何链接关系的导航选项,用户可以探索相关资源:这个人的办公室在哪里?主管的电话号码是多少?
这仍然没有解决深层链接。 如果用户将https://www.hypothetical.com/employees/123
书签,关闭浏览器,然后稍后重新访问该书签会怎样? 现在 SPA 不记得底层 API 资源是什么。 我们无法反转替换(例如,将“www”替换为“api”),因为那不是 HATEOAS。
HATEOAS 的心态似乎是 SPA 应该再次请求https://api.hypothetical.com/
并通过链接返回到 John Doe 的员工资料,但是没有固定的链接关系可以从作为集合的employees
获取到作为集合的John Doe
特定的员工,这样是行不通的。
另一种 HATEOAS 方法是应用程序可以为它发现的 URL 添加书签。 例如,它可以存储一个 hash 表,该表将以前看到的 SPA URL 映射到 API URL:
{
"https://www.hypothetical.com/employees/123": "https://api.hypothetical.com/employees/123"
}
这将允许 SPA 找到底层资源并呈现 UI,但它需要跨会话持久 state。 例如,如果我们将这个 hash 存储在 HTML5 存储中并且用户清除他们的 HTML5 存储,那么所有的书签都会被破坏。 或者,如果用户将此书签发送给另一个用户,则该其他用户不会拥有 SPA URL 到 API URL 的映射。
底线:在 HATEOAS API 之上实施深度链接 SPA 对我来说感觉非常尴尬。 在我当前的项目中,我求助于让 SPA 构造几乎所有的 URL。 作为该决定的结果,API 必须为各个资源发送唯一标识符(不仅仅是 URL),以便 SPA 可以为它们生成良好的 URL。
有没有人有这样做的经验? 有没有办法满足这些看似矛盾的标准呢?
我也一直在努力解决这个问题。 hateoas api就是将客户端与服务器分离开来,所以我认为将“客户端URL - > api uri”映射硬编码到本地存储中是因为你提到的原因而向后退了一步。 话虽如此,关于为客户端和服务器提供单独的域语言并没有什么联系。 您可以在页面上执行某些操作(例如单击employee#_),使用您喜欢的任何内容触发推送状态,也许是“/ employees /#”。 当应用程序通过链接共享引导到另一个浏览器时,某些服务将知道如何读取DSL'/ employees /#'并将您的应用程序快速转发到适当的状态。 在权限不同的情况下,您会收到一些消息,说明您无法访问员工#_的原因,而是列出您有权查看的员工。 如果你有很多像这样的深层链接视图,它会变得非常麻烦,但这是代表具有平面url层次结构的复杂状态对象的代价。
我考虑的另一种方法是明确的“分享这个”按钮。 单击该按钮将要求服务器在其自己的DSL中填充URL,以便当另一个用户访问该共享URL时,服务器可以将相关状态信息传输到客户端。 客户端必须设置为处理这种情况,可能有一些条件“如果此资源包含'深层链接'属性,请执行一些特殊操作。
无论哪种方式,这都不是一个容易解决的问题。
我可能遗漏了一些东西,但可能对于不同的资源类型,你有不同的视图/页面。 因此,当您加载员工时,它会使用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
由客户端决定它使用哪些 URL; 他们不必(也不应该)匹配服务器的 URL。 我在另一个答案中详细说明了方法和原因: How can I avoid hardcoding URLs in a RESTful client/server web app with deep linking?
只要客户端可以确定资源类型和标识符,它就可以向服务器查询它需要检索资源的 URL。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.