代码之家  ›  专栏  ›  技术社区  ›  jkp

有人知道一个遵循hateoas原则的宁静客户的例子吗?

  •  44
  • jkp  · 技术社区  · 15 年前

    因此,到目前为止,我认为我们都应该实现我们的RESTful服务,提供使客户能够遵循 HATEOAS 原理。虽然从理论上讲一切都很有意义,但我一直在搜索Web,以找到一些严格遵循这个想法的客户机代码的一个好例子。

    我读得越多,就越觉得这是学术讨论,因为实际上没有人在做!人们可以抱怨WS-*堆栈的许多缺陷,但至少可以清楚地知道如何编写客户机:您可以解析WSDL并生成代码。

    现在我明白了,对于一个好的RESTful服务来说,这不应该是必需的:您应该只需要了解所涉及的关系和表示,并且您应该能够动态地对它们作出反应。但即便如此,这一原则到目前为止是否应该被提炼并抽象为一些公共图书馆呢?提供有关您可能收到的表示和关系的信息,并获得一些更有用的高级代码,您可以在应用程序中使用这些代码?

    这些都只是我的半生不熟的想法,但我只是担心,如果我现在潜入并编写一个适当的RESTfulAPI,实际上没有人能够使用它!或者,至少使用它会让人后顾之忧,因为人们需要花更多的时间编写粘合代码来解释我提供的关系和表示。

    有人能从客户的角度对此有所了解吗?有人能举一个适当动态/反应式RESTful客户机代码的例子,这样我就可以了解我真正为之写作的读者了吗?(最好还是提供一些抽象的客户机API示例)否则,这一切都是理论上的……

    [编辑:注意,我发现了一个类似的问题 here 我认为这不是真的答案,作者被一个维基百科的树桩蒙蔽了。]

    6 回复  |  直到 6 年前
        1
  •  17
  •   Jim Ferrans    15 年前

    我们在目前的项目中已经完成了一半。我们返回的表示是从域对象生成的,客户机可以在XML、JSON或XHTML中请求它们。如果它是一个类似于火狐的XHTML客户端,那么一个人会看到一组来自已知根资源的出站链接,并可以浏览到所有其他资源。到目前为止,纯粹的hateoas是开发人员的一个很好的工具。

    但我们担心 性能 当客户机是程序时,而不是使用浏览器的人。对于我们的XML和JSON表示,我们目前抑制了相关链接的生成,因为它们使表示大小增加了三倍,从而实质上影响了序列化/反序列化、内存使用和带宽。我们的另一个效率问题是,对于纯hateoas,当客户端程序从众所周知的链接向下浏览到所需的信息时,它们将发出数倍的HTTP请求。所以看起来最好,从 效率 站在立场,如果客户机知道其中编码的链接。

    但是这样做意味着客户端必须进行大量的字符串连接来形成URI, 这很容易出错,而且很难重新排列资源名称空间。因此,我们使用一个模板系统,其中客户机代码选择一个模板,并要求它从参数对象中扩展自己。这是一种填表方式。

    我真的很想看看别人在这方面的经验。除了性能方面,hateoas似乎是一个好主意。

    编辑:我们的模板是我们在上面写的一个Java客户程序库的一部分。 Restlet 框架。客户端库处理HTTP请求/响应、HTTP头、反序列化/序列化、gzip编码等的所有详细信息。这使得实际的客户端代码非常简洁,并有助于将其与某些服务器端更改隔离开来。

    Roy Fielding blog entry about HATEOAS 接下来有一个非常相关和有趣的讨论。

        2
  •  9
  •   Darrel Miller    15 年前

    到目前为止,我已经构建了两个访问REST服务的客户机。两者都只使用hateoas。在不更新客户机的情况下,我成功地更新了服务器功能。

    我使用xml:base启用相对URL以减少XML文档中的噪声。除了加载图像和其他静态数据,我通常只关注用户请求上的链接,因此链接的性能开销对我来说并不重要。

    在客户机上,我认为需要创建的唯一常见功能是围绕我的媒体类型和管理链接的类创建包装。


    更新:

    从客户机的角度来看,似乎有两种不同的方法来处理REST接口。第一个问题是客户机知道它想要得到什么信息,并且知道它需要遍历哪些链接才能得到这些信息。第二种方法在客户机应用程序的人工用户控制要跟踪哪些链接,而客户机可能事先不知道将从服务器返回什么媒体类型时很有用。对于娱乐价值,我分别称这两种类型的客户机为数据挖掘器和调度器。

    数据采集器

    例如,想象一下,TwitterAPI实际上是RESTful的,我想写一个客户端,它可以检索特定Twitter用户的最新跟踪者的最新状态消息。

    假设我正在使用很棒的新microsoft.http.httpclient库,并且我编写了一些“readas”扩展方法来解析来自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;
                }
    
            }
    

    客户机应用程序可以使用完全通用的机制跟踪链接并将响应传递给这个调度方法。从这里,switch语句将控制权传递给特定的控制器类,该类知道如何根据媒体类型解释和呈现信息。

    显然,客户机应用程序还有更多的部分,但这些是对应于hateoas的部分。请随时让我澄清任何要点,因为我已经略读了许多细节。

        3
  •  3
  •   Denis Zavedeev Brian Kelly    6 年前

    诺基亚 Places API (网络存档) snapshot )很安静,并且在整个过程中使用超媒体。在其文档中还清楚地表明,不鼓励使用URI模板/硬编码:

    超媒体链接的使用

    应用程序必须使用JSON中公开的超媒体链接 对任务流中后续请求的响应,而不是 尝试通过URI模板为下一步构造一个URI。通过 使用URI模板,您的请求将不包括关键的 创建下一个响应所需的信息 请求。

        4
  •  0
  •   jeremyh    12 年前

    Jim,我也有点对Hateoas之后的一个休息的客户缺乏范例感到沮丧,所以我写了一篇博客文章,展示了一个创建和下订单的合适的Hateoas范例。令人惊讶的是,通过API实现这一点的例子很少,我发现它有点令人困惑,但下面是链接: API Example Using Rest . 告诉我你的想法和你认为我做错了什么。

        5
  •  0
  •   Tom Howard    12 年前

    我曾经写过两个HATOOAS客户,一次是Java,一次是在Ruby上,我分享了你的沮丧。在这两种情况下,我所做的工作都完全没有工具支持。例如,我使用的RESTAPI会告诉我要为每个超文本控件使用什么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;
    }
    

    使用这一小段代码,我可以相当容易地编写客户机,这些客户机将遍历返回的文档中的超媒体控件。缺少的主要位是对表单参数的支持。幸运的是,我使用的所有控件都是无参数的,除了一个(我按照 rule of three in regards to refactoring )为了完整性,下面是代码片段的样子:

        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 想知道哪些参数需要传递,但是 that will have to wait until I have two more parameterised form that I need to follow .

    顺便说一句,经过所有的努力,在不影响客户机的情况下修改服务器是非常令人满意和容易的。这感觉太好了,我很惊讶它在某些州没有被取缔。

        6
  •  -4
  •   Gandalf    15 年前

    您选择的Web浏览器是整个www的“纯hateoas”客户端。

    在我看来,这个问题没有真正意义。