Шаблон API клиентского сервера в REST (ненадежный сетевой вариант использования)

Предположим, что взаимодействие с клиентом и сервером происходит через ненадежную сеть (падение пакетов). Клиент вызывает сервер RESTful api (через http через tcp):

  • выдача POST на http://server.com/products
  • сервер создает объект ресурса "продукт" (сохраняется в базе данных и т.д.).
  • сервер возвращается 201 Создан с заголовком местоположения " http://server.com/products/12345"
  • ! TCP-пакет, содержащий ответ HTTP, отбрасывается, и в конечном итоге это приводит к подключению tcp reset

Я вижу следующую проблему: клиент никогда не получит идентификатор вновь созданного ресурса, но у сервера будет создан ресурс.

Вопросы: Является ли это поведение на уровне приложения или он должен заботиться об этом? Каким образом веб-инфраструктура (и Rails в частности) обрабатывает подобную ситуацию? Существуют ли какие-либо статьи/публикации в REST для этой темы?

Ответ 1

Если нецелесообразно создавать дублирующие ресурсы (например, продукты с одинаковыми названиями, описаниями и т.д.), тогда на сервере могут быть созданы уникальные идентификаторы, которые можно отследить от созданных ресурсов, чтобы предотвратить дублирование запросов обработанный. В отличие от предложения Darrel генерации уникальных идентификаторов на клиенте, это также помешает отдельным пользователям создавать дублированные ресурсы (которые вы можете или не можете найти желательными). Клиенты смогут различать "созданные" ответы и "дублировать" ответы по своим кодам ответа (201 и 303 соответственно, в моем примере ниже).

Псевдокод для создания такого идентификатора - в этом случае - хэш канонического представления запроса:

func product_POST
    // the canonical representation need not contain every field in
    // the request, just those which contribute to its "identity"
    tags = join sorted request.tags
    canonical = join [request.name, request.maker, tags, request.desc]
    id = hash canonical

    if id in products
        http303 products[id]
    else
        products[id] = create_product_from request
        http201 products[id]
    end
end

Этот идентификатор может быть или не быть частью URI созданных ресурсов. Лично я был бы склонен отслеживать их отдельно - за счет дополнительной таблицы поиска - если бы URIs были подвержены пользователям, поскольку хэши, как правило, уродливы и трудны для людей, чтобы помнить.

Во многих случаях также имеет смысл "истекать" эти уникальные хэши через некоторое время. Например, если вы должны сделать API перевода денег, пользователь, передающий одну и ту же сумму денег одному и тому же человеку на расстоянии нескольких минут, вероятно, указывает, что клиент так и не получил ответ "успех". Если пользователь передает одну и ту же сумму один раз в месяц, с другой стороны, они, вероятно, платят за аренду.; -)

Ответ 2

Клиент получит сообщение об ошибке, если сервер не отвечает на POST. Затем клиент обычно повторно выдавал запрос, поскольку он предположил, что он потерпел неудачу. С головы до ног я могу придумать два подхода к этой проблеме.

Во-первых, клиент может генерировать какой-то идентификатор запроса, такой как guid, который он включает в запрос. Если сервер получает запрос POST с двойным идентификатором GUID, он может отказаться от него.

Другим подходом является PUT вместо POST для создания. Если вы не можете заставить клиента генерировать URI, вы можете попросить сервер предоставить новый URI с помощью GET, а затем выполнить PUT для этого URI.

Если вы ищете что-то вроде "make POST idempotent", вы, вероятно, найдете множество других предложений о том, как это сделать.

Ответ 3

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

Это можно легко реализовать на уровне структуры. Структура может хранить кеш завершенных ответов. Запросы должны иметь уникальный запрос, чтобы любые повторные попытки обрабатывались как таковые, а не как новые запросы.

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

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

Хотя то, что я предлагаю, похоже на то, что предлагают другие, я настоятельно рекомендую вам поддерживать этот уровень сетевой отказоустойчивости только для этого, обрабатывать запросы/ответы на запросы и не разрешать ему обрабатывать дублированные ресурсы из отдельных запросов, которые задача уровня приложения. Объединение обеих частей будет замалчивать всю функциональность и не оставит вас четкого разделения обязанностей.

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

И для некоторых связанных с ними опытов другими людьми перейдите здесь.

Удачи.

Ответ 4

Как указывали другие респонденты, основная проблема заключается в том, что стандартный метод HTTP POST не является идемпотентным, как другие методы. В настоящее время предпринимаются усилия по установлению стандарта для метода иподомирования POST, известного как Post-Once-Exactly, или POE.

Теперь я не говорю, что это идеальное решение для всех в описываемой вами ситуации, но если это так, что вы пишете как сервер, так и клиент, вы можете использовать некоторые идеи от POE. Проект находится здесь: http://tools.ietf.org/html/draft-nottingham-http-poe-00

Это не идеальное решение, и, вероятно, поэтому оно не было снято за шесть лет с момента подачи проекта. Некоторые из проблем и некоторые умные альтернативные варианты обсуждаются здесь: http://tech.groups.yahoo.com/group/rest-discuss/message/7646

Ответ 5

HTTP - это протокол без учета состояния, то есть сервер не может открыть HTTP-соединение. Все соединения инициализируются клиентом. Поэтому вы не можете решить такую ​​ошибку на стороне сервера.

Единственное решение, о котором я могу думать: если вы знаете, какой клиент создал продукт, вы можете предоставить ему созданные им продукты, , если он извлекает эту информацию. Если клиент больше никогда не свяжется с вами, вы не сможете передавать информацию о новом продукте.