有谁知道遵循HATEOAS原则的RESTful客户端的例子吗?
所以现在我明白了,我们应该全部实施我们的RESTful服务,提供使客户遵循HATEOAS原则的表示。 从理论上说,这一切都很有道理,但我一直在网上search一个严格遵循这个概念的客户代码的例子。
我读得越多,我就越觉得这是一个学术讨论,因为没有人真的这样做! 人们可以嘲笑WS- *堆栈的许多缺陷,但至less清楚如何编写客户端:您可以parsingWSDL并生成代码。
现在我明白,对于一个好的RESTful服务来说,这不是必须的:您只需要知道所涉及的关系和表示,并且您应该能够对这些服务进行dynamic反应。 但即使如此,现在这个原则不应该被提炼成一些共同的图书馆吗? 提供有关您可能会收到的表示和关系的信息,并获得一些您可以在应用程序中使用的更有用的高级代码?
这些只是我的一半的想法,但我只是谨慎,如果我现在正在潜心编写一个适当的RESTful API,没有人真的能够使用它! 或者至less使用它会在后面如此痛苦,因为额外的英里人们将不得不去写胶水代码来解释我提供的关系和表示。
任何人都可以从客户的angular度来解释这一点吗? 有人可以展示一个正确的dynamic/被动RESTful客户端代码的例子,以便我可以对我实际上正在写的观众有一个想法吗? (更好的还是一个提供一些抽象的客户端API的例子)否则,它的一切都非常理论….
[编辑:请注意,我在这里发现了一个类似的问题,我不认为这是真正的答案,作者被一个维基百科的存根所束缚!]
我们目前的项目已经完成了一半。 我们返回的表示是从域对象生成的,客户端可以用XML,JSON或XHTML来请求它们。 如果它是一个像Firefox这样的XHTML客户端,那么一个人可以从知名的根资源中看到一组出站链接,并可以浏览所有其他资源。 到目前为止,纯粹的HATEOAS,对开发者来说是一个很好的工具。
但是当客户端是一个程序,而不是使用浏览器的人时,我们关心的是性能 。 对于我们的XML和JSON表示,我们目前已经抑制了相关链接的生成,因为它们使表示大小增加了三倍,从而大大影响了序列化/反序列化,内存使用和带宽。 我们的另一个效率问题是,使用纯粹的HATEOAS,客户端程序将从知名链接浏览到他们需要的信息时,使HTTP请求数量增加几倍。 因此,从效率的angular度来看,如果客户对编码的链接有所了解,似乎是最好的。
但是这样做意味着客户端必须进行大量的string连接才能形成URI,这很容易出错,并且很难重新排列资源名称空间。 因此,我们使用模板系统,客户端代码select一个模板,并要求它从参数对象中展开。 这是一种表格填充。
我真的很想看看别人在这方面的经验。 除了性能方面,HATEOAS似乎是个好主意。
编辑:我们的模板是我们在Restlet框架之上编写的Java客户端库的一部分。 客户端库处理HTTP请求/响应,HTTP标头,反序列化/序列化,GZIP编码等的所有细节。这使得实际的客户端代码非常简洁,并且有助于将其与一些服务器端的变化隔离。
Roy Fielding 关于HATEOAS的博客文章有一个非常相关和有趣的讨论。
到目前为止,我已经构build了两个访问REST服务的客户端。 两者都使用HATEOAS。 我有大量的成function够更新服务器function,而无需更新客户端。
我使用xml:base来启用相关的URL,以减less我的XML文档中的噪音。 除了加载图像和其他静态数据,我通常只跟随用户请求的链接,所以链接的性能开销对我来说并不重要。
在客户端,我感觉需要创build的唯一通用function是围绕我的媒体types和一个类来pipe理链接。
更新:
从客户的angular度来看,似乎有两种不同的方式来处理REST接口。 首先是客户知道要获取什么信息,并知道需要传递的信息。 第二种方法在客户端应用程序的人类用户控制要遵循哪些链接并且客户端可能事先不知道将从服务器返回何种媒体types时是有用的。 对于娱乐价值,我分别称这两种types的客户端,数据挖掘者和调度者。
数据挖掘者
例如,想象一下,Twitter API实际上是RESTful,并且我想要编写一个客户端来检索特定twitter用户的最新跟随者的最新状态消息。
假设我使用了令人敬畏的新的Microsoft.Http.HttpClient库,并且我已经编写了几个“ReadAs”扩展方法来parsing来自twitter API的XML,我想它会是这样的:
var twitterService = HttpClient.Get("http://api.twitter.com").Content.ReadAsTwitterService(); var userLink = twitterService.GetUserLink("DarrelMiller"); var userPage = HttpClient.Get(userLink).Content.ReadAsTwitterUserPage(); var followersLink = userPage.GetFollowersLink(); var followersPage = HttpClient.Get(followersLink).Content.ReadAsFollowersPage(); var followerUserName = followersPage.FirstFollower.UserName; var followerUserLink = twitterService.GetUserLink(followerUserName); var followerUserPage = HttpClient.Get(followerUserLink).Content.ReadAsTwitterUserPage(); var followerStatuses = HttpClient.Get(followerUserPage.GetStatusesLink()).Content.ReadAsTwitterUserPage(); var statusMessage = followerStatuses.LastMessage;
调度员
为了更好地说明这个例子,假设你正在实现一个呈现家谱信息的客户端。 客户需要能够展示树,深入到关于特定人的信息和查看相关图像。 考虑下面的代码片段:
void ProcessResponse(HttpResponseMessage response) { IResponseController controller; switch(response.Content.ContentType) { case "vnd.MyCompany.FamilyTree+xml": controller = new FamilyTreeController(response); controller.Execute(); break; case "vnd.MyCompany.PersonProfile+xml": controller = new PersonProfileController(response); controller.Execute(); break; case "image/jpeg": controller = new ImageController(response); controller.Execute(); break; } }
客户端应用程序可以使用完全通用的机制来跟踪链接并将响应传递给此调度方法。 从这里开关语句将控制传递给一个特定的控制器类,该控制器类知道如何解释和呈现基于媒体types的信息。
显然,客户端应用程序还有很多部分,但是这些对应于HATEOAS。 随时请我澄清任何问题,因为我已经浏览了很多细节。
诺基亚的Places API是RESTful,并在整个过程中使用超媒体。 它的文档中也清楚地说明,不鼓励使用URI模板/硬编码:
超媒体链接的使用
您的应用程序必须使用JSON响应中公开的超媒体链接来处理任务stream中的后续请求,而不是尝试通过URI模板构build下一步的URI。 通过使用URI模板,您的请求不会包含为下一个请求创build响应所需的关键信息。
Jim,对于HATEOAS之后的RESTful客户端缺乏示例,我也感到有些沮丧,所以我写了博客文章,展示了创build和下订单的正确HATEOAS示例。 有很less的例子通过API来做到这一点,我发现这是一个让人困惑的事情,但是这里是链接: 使用Rest的API示例 。 让我知道你的想法和你认为我做错了什么。
我写了两个HATEOAS客户端,一个是在Java中,一个是在Ruby中,我分享你的挫败感。 在这两种情况下,我完全没有缺乏对我所做的工具的支持。 例如,我使用的REST API会告诉我什么HTTP方法用于每个超文本控件,但HttpClient不让你传入方法,所以我结束了以下丑陋的代码(顺便说一句,所有的代码生活在一个自定义的Ant任务,因此BuildException
s):
private HttpMethod getHypermediaControl(Node href, Node method, NodeList children) { if (href == null) { return null; } HttpMethod control; if (method == null || method.getNodeValue().equals("") || method.getNodeValue().equalsIgnoreCase("GET")) { control = new GetMethod(href.getNodeValue()); } else if (method.getNodeValue().equalsIgnoreCase("POST")) { control = new PostMethod(href.getNodeValue()); } else if (method.getNodeValue().equalsIgnoreCase("PUT")) { control = new PutMethod(href.getNodeValue()); } else if (method.getNodeValue().equalsIgnoreCase("DELETE")) { control = new DeleteMethod(href.getNodeValue()); } else { throw new BuildException("Unknown/Unimplemented method " + method.getNodeValue()); } control.addRequestHeader(accept); return control; }
这最终成为我使用的REST客户端实用程序方法的基础。
private HttpMethod getHypermediaControl(String path, Document source) throws TransformerException, IOException { Node node = XPathAPI.selectSingleNode(source, path); return getHypermediaControl(node); } private HttpMethod getHypermediaControl(Node node) { if (node == null) { return null; } NamedNodeMap attributes = node.getAttributes(); if (attributes == null) { return null; } Node href = attributes.getNamedItem("href"); Node method = attributes.getNamedItem("method"); HttpMethod control = getHypermediaControl(href, method, node.getChildNodes()); return control; } private Document invokeHypermediaControl(HttpClient client, Document node, final String path) throws TransformerException, IOException, HttpException, URIException, SAXException, ParserConfigurationException, FactoryConfigurationError { HttpMethod method = getHypermediaControl(path, node); if (method == null) { throw new BuildException("Unable to find hypermedia controls for " + path); } int status = client.executeMethod(method); if (status != HttpStatus.SC_OK) { log(method.getStatusLine().toString(), Project.MSG_ERR); log(method.getResponseBodyAsString(), Project.MSG_ERR); throw new BuildException("Unexpected status code (" + method.getStatusCode() + ") from " + method.getURI()); } String strResp = method.getResponseBodyAsString(); StringReader reader = new StringReader(strResp); Document resp = getBuilder().parse(new InputSource(reader)); Node rval = XPathAPI.selectSingleNode(resp, "/"); if (rval == null) { log(method.getStatusLine().toString(), Project.MSG_ERR); log(method.getResponseBodyAsString(), Project.MSG_ERR); throw new BuildException("Could not handle response"); } method.releaseConnection(); return resp; }
使用这一小段代码,我可以很容易地编写将遍历返回的文档中的超媒体控件的客户端。 缺less的主要是支持表单参数。 幸运的是,对于我来说,我使用的所有控件都是无参数的,除了一个(我遵循重构方面的三条规则 )。 为了完整,这里是代码片段的样子:
HttpMethod licenseUpdateMethod = getHypermediaControl( "/license/update", licenseNode); if (licenseUpdateMethod == null) { log(getStringFromDoc(licenseNode), Project.MSG_ERR); throw new BuildException( "Unable to find hypermedia controls to get the test suites or install the license"); } else if (license != null) { EntityEnclosingMethod eem = (EntityEnclosingMethod) licenseUpdateMethod; Part[] parts = { new StringPart("license", this.license) }; eem.setRequestEntity(new MultipartRequestEntity(parts, eem .getParams())); int status2 = client.executeMethod(eem); if (status2 != HttpStatus.SC_OK) { log(eem.getStatusLine().toString(), Project.MSG_ERR); log(eem.getResponseBodyAsString(), Project.MSG_ERR); throw new BuildException("Unexpected status code (" + eem.getStatusCode() + ") from " + eem.getURI()); } eem.releaseConnection(); }
现在应该做的是看看/license/update
的孩子,弄清楚哪些参数需要通过,但是必须等到我有两个参数化表格,我需要遵循 。
顺便说一下,它已经非常令人满意和容易修改服务器,而不会影响客户端。 感觉好极了,我感到惊讶的是在一些州还没有被取缔。
您select的网页浏览器是整个WWW的“纯HATEOAS”客户端。
这个问题真的没有意义。