Обработка побочных эффектов, вызванных дублированием запросов POST.

Допустим, у нас есть веб-служба, которая создает и обновляет бронирование конференц-залов. Обновления могут изменить различные аспекты бронирования, такие как время и номер комнаты.

Давайте представим, что сетевое подключение пользователя к сервису может быть ненадежным (например, мобильная сеть), и два пользователя А и Б пытаются последовательно обновить одно и то же бронирование.

Пользователь A отправляет запрос POST, чтобы изменить время встречи на 14:00, запрос достигает сервера, и сервер успешно обработал запрос. Однако ответ пользователю А теряется из-за сетевого подключения, и пользователь А считает, что запрос не выполнен.

Прежде чем пользователь A попытается снова, пользователь B отправляет свой запрос на изменение времени встречи на 14:30, и он успешно отвечает пользователю B.

Теперь пользователь А повторяет (возможно, автоматически) тот же запрос снова, и на этот раз и запрос, и ответ выполняются без проблем. Другими словами, время встречи снова переносится на 14:00.

В приведенном выше гипотетическом сценарии дублированные запросы пользователя А приводят к тому, что запрос пользователя Б перезаписывается, что приводит к неправильному состоянию на стороне сервера.

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

Каковы лучшие методы или методы для решения этой проблемы?


person skyork    schedule 24.04.2015    source источник
comment
Посмотрите, есть ли PRG (en.wikipedia.org/wiki/Post/Redirect/Get) может тебе помочь.   -  person ott--    schedule 24.04.2015


Ответы (1)


Это распространенная проблема с одновременными пользователями. Один из способов решить эту проблему — применять условные запросы, требуя от клиентов отправки заголовка If-Unmodified-Since со значением Last-Modified для ресурса, который они пытаются изменить. Это гарантирует, что никто другой не изменил его между последней проверкой и настоящим моментом. В вашем случае это помешает A перезаписать изменения B.

Например, пользователь А хочет изменить время встречи. Он отправляет запрос GET для ресурса собрания и сохраняет значение заголовка ответа Last-Modified. Затем он отправляет запрос POST со значением Last-Modified в заголовке If-Unmodified-Since. Следуя вашему примеру, этот запрос действительно выполняется, но ответ теряется.

Если A немедленно повторит запрос, он завершится с ошибкой 412 Precondition Failed, так как условие больше недействительно.

Если тем временем B делает то же самое и снова меняет время встречи, когда A пытается повторить запрос, не проверяя текущее значение Last-Modified, соответствующее изменениям B, он также терпит неудачу с 412 Precondition Failed.

person Pedro Werneck    schedule 24.04.2015
comment
Спасибо за ответ. Не могли бы вы уточнить использование If-Unmodified-Since и Last-Modified в этом случае? Какая сторона устанавливает какой заголовок? - person skyork; 25.04.2015
comment
Означает ли это, что на стороне сервера необходимо обновлять отметку времени для ресурса (в данном случае — сведения о каждой встрече) каждый раз при обновлении? И когда какой-либо клиент отправляет запрос POST со своей собственной версией значения Last-Modified внутри поля заголовка If-Unmodified-Since, сервер сверяет это значение с сохраненной меткой времени для запрошенного ресурса. Если отметка времени находится после значения Last-Modified, то сервер не должен обрабатывать запрос и должен ответить 412 Precondition Failed. Верно ли это понимание? - person skyork; 25.04.2015
comment
Клиент не отправляет собственную версию Last-Modified. Он должен выдать запрос GET и использовать значение, возвращенное в этом ответе, в заголовке If-Unmodified-Since запроса POST. Если текущая сохраненная метка времени находится после значения If-Unmodified-Since, сервер должен выйти из строя с 412 Precondition Failed. Если клиент не отправляет заголовок If-Unmodified-Since, сервер должен выйти из строя с 428 Precondition Required. - person Pedro Werneck; 25.04.2015