Кто-нибудь знает пример клиента RESTful, который следует принципу HATEOAS?

Итак, теперь я понимаю, что все мы должны внедрять наши службы RESTful, предоставляющие представления, которые позволяют клиентам следовать HATEOAS. И хотя все это имеет смысл в теории, я прочесываю сеть, чтобы найти один хороший пример некоторого клиентского кода, который следует за идеей строго.

Чем больше я читаю, тем больше я начинаю чувствовать, что это академическая дискуссия, потому что никто на самом деле не делает этого! Люди могут стонать все, что им нравится в WS- * stack, многие изъяны, но по крайней мере ясно, как писать клиенты: вы можете разбирать WSDL и генерировать код.

Теперь я понимаю, что это не обязательно для хорошего сервиса RESTful: вам нужно только знать о связанных отношениях и представлениях, и вы должны иметь возможность динамически реагировать на них. Но все же, разве этот принцип не должен быть перегоняем и абстрагироваться до некоторых распространенных библиотек? Укажите информацию о представлениях и отношениях, которые вы можете получить, и получите более полезный код более высокого уровня, который вы можете использовать в своем приложении?

На самом деле это только наполовину искушенные идеи, но я просто опасаюсь, что если я сейчас погружусь и напишу правильно RESTful API, никто на самом деле не сможет его использовать! Или, по крайней мере, использование этого будет такой болью взади, потому что лишняя миля людям придется писать код клея, чтобы интерпретировать отношения и представления, которые я предоставляю.

Может ли кто-нибудь пролить свет на это с точки зрения клиента? Может ли кто-нибудь показать пример правильного динамического/реактивного клиентского кода RESTful, чтобы у меня появилась идея аудитории, на которой я на самом деле пишу? (еще лучше пример API-клиента, который предоставляет некоторые абстракции) В противном случае все его довольно теоретические....

[edit: note, я нашел подобный вопрос здесь, на который я не думаю, что на него действительно ответил, автор был заперт с помощью википедии!]

Ответ 1

Мы немного наполовину сделали это в нашем текущем проекте. Представления, которые мы возвращаем, генерируются из объектов домена, и клиент может запрашивать их либо в XML, JSON, либо в XHTML. Если это клиент XHTML, например Firefox, то человек видит набор исходящих ссылок из известного корневого ресурса и может просматривать все остальные ресурсы. Пока что чистый HATEOAS и отличный инструмент для разработчиков.

Но мы обеспокоены производительностью, когда клиент - это программа, а не человек, использующий браузер. Для наших представлений XML и JSON мы в настоящее время подавляем генерации связанных ссылок, поскольку они увеличивают размеры представлений и, следовательно, существенно влияют на сериализацию/десериализацию, использование памяти и пропускную способность. Наша другая проблема эффективности заключается в том, что при чистом HATEOAS клиентские программы будут в несколько раз увеличивать количество HTTP-запросов при их просмотре с известной ссылки на нужную им информацию. Так что лучше всего, с точки зрения эффективности, если клиенты имеют знания о закодированных в них ссылках.

Но это означает, что клиент должен выполнить много конкатенации строк, чтобы сформировать URI, который подвержен ошибкам и затрудняет перераспределение пространства имен ресурсов. Поэтому мы используем систему шаблонов, в которой клиентский код выбирает шаблон и просит его развернуть себя из объекта параметра. Это тип заполнения формы.

Я очень хочу увидеть, что другие испытали на этом. HATEOAS кажется хорошей идеей, помимо аспектов производительности.

Изменить: наши шаблоны являются частью клиентской библиотеки Java, которую мы написали поверх Restlet. Клиентская библиотека обрабатывает все детали HTTP-запросов/ответов, заголовки HTTP, десериализацию/сериализацию, кодирование GZIP и т.д. Это делает фактический код клиента довольно кратким и помогает изолировать его от некоторых изменений на стороне сервера.

Рой Филдинг запись в блоге о HATEOAS имеет после этого очень актуальную и интересную дискуссию.

Ответ 2

До сих пор я создал два клиента, которые получают доступ к службам REST. Оба используют исключительно HATEOAS. У меня был огромный успех, позволяющий обновлять функциональность сервера без обновления клиента.

Я использую xml: base для включения относительных URL-адресов для уменьшения шума в моих XML-документах. Помимо загрузки изображений и других статических данных, я обычно использую только ссылки на пользовательские запросы, поэтому накладные расходы по производительности для меня несущественны.

На клиентах единственная общая функциональность, которую я чувствовал необходимость создания, - это обертки вокруг моих типов медиа и класса для управления ссылками.


Update:

Кажется, существуют два разных способа взаимодействия с REST-интерфейсами с точки зрения клиента. Во-первых, клиент знает, какую информацию он хочет получить, и знает, какие ссылки ему нужно пройти, чтобы добраться до этой информации. Второй подход полезен, когда пользователь-пользователь клиентского приложения контролирует, какие ссылки следует следовать, и клиент может заранее не знать, какой тип носителя будет возвращен с сервера. Для развлекательной ценности я называю эти два типа клиентов, шахтер данных и диспетчер, соответственно.

Data Miner

Например, представьте себе, что API-интерфейс Twitter был фактически RESTful, и я хотел написать клиент, который будет возвращать последнее сообщение о статусе самого последнего последователя конкретного пользователя Twitter.

Предполагая, что я использовал потрясающую новую библиотеку Microsoft.Http.HttpClient, и я написал несколько методов расширения ReadAs для анализа XML, исходящего из API twitter, я предполагаю, что это будет выглядеть примерно так:

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

Nokia API мест - RESTful и использует гипермедиа по всему миру. В своей документации также ясно, что препятствует использованию шаблонов URI/hardcoding:

Использование ссылок гиперссылки

Ваше приложение должно использовать ссылки гипермедиа, выставленные в JSON ответы для последующих запросов в потоке задач, а не пытаясь создать URI для следующих шагов с помощью шаблонов URI. От используя шаблоны URI, ваш запрос не будет включать критические информацию, необходимую для создания ответа для следующего запрос.

Ответ 4

Джим, я также немного расстроился из-за отсутствия примеров с клиентом RESTful после HATEOAS, поэтому я написал сообщение в блоге, в котором показан правильный пример HATEOAS для создания и размещения заказа. Есть удивительно мало примеров того, как это делается с помощью API, и я нашел его путаным, но вот ссылка: Пример API с использованием Rest. Дайте мне знать, что вы думаете и что думаете, что я сделал неправильно.

Ответ 5

Я написал два клиента HATEOAS, один раз на Java и один раз в Ruby, и я разделяю ваше разочарование. В обоих случаях была полная нехватка инструментальной поддержки для того, что я делал. Например, API REST, который я использовал, скажет мне, какой метод HTTP использовать для каждого элемента управления гипертекстом, но HttpClient не позволяет вам пройдите в методе, поэтому я закончил со следующим уродливым кодом (BTW весь код живет в рамках пользовательской задачи 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;
}

С помощью этого небольшого количества кода я могу довольно легко писать клиенты, которые будут перемещать элементы гипермедиа в возвращаемых документах. Основной бит, который отсутствует, - это поддержка параметров формы. К счастью для меня все элементы управления, которые я использую, являются без параметров, кроме одного (я следую правилу из трех в отношении рефакторинга). Для полноты здесь приведен фрагмент кода:

    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, чтобы выяснить, какие параметры нужно передать, но которые придется ждать пока у меня еще две параметризованные формы, которые мне нужно выполнить.

BTW это после всех усилий, это было чрезвычайно удовлетворительным и простым в изменении сервера, не влияя на клиента. Это было так хорошо, что я удивлен, что он не объявлен вне закона в некоторых штатах.

Ответ 6

Ваш веб-браузер по своему выбору является "чистым клиентом HATEOAS" для всего WWW.

Вопрос действительно не имеет смысла imo.