HTTP-условный PUT

Читая ответ на вопрос на идемпотентность HTTP PUT Я узнал об интересном аспекте условных запросов PUT, который приводит к следующему вопросу.

Спецификация условных запросов HTTP определяет в раздел 3.1:

Исходный сервер НЕ ДОЛЖЕН выполнять запрошенный метод, если полученное условие If-Match оценивается как ложное; вместо этого исходный сервер ДОЛЖЕН ответить либо а) кодом состояния 412 (сбой предварительного условия), либо б) одним из кодов состояния 2xx (успешно), если исходный сервер подтвердил, что запрашивается изменение состояния, а конечное состояние уже установлено. отражается в текущем состоянии целевого ресурса (т. е. изменение, запрошенное агентом пользователя, уже выполнено успешно, но агент пользователя может не знать об этом, возможно, из-за того, что предыдущий ответ был утерян или какое-то другое изменение было сделано совместимым изменением). пользовательский агент). В последнем случае исходный сервер НЕ ДОЛЖЕН отправлять поле заголовка валидатора в ответе, если только он не может убедиться, что запрос является дубликатом непосредственно предшествующего изменения, сделанного тем же пользовательским агентом.

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

HTTP/1.1 200 OK
Content-Type: application/json
ETag: "0"

{ "value": 0 }

Теперь два клиента одновременно хотят увеличить значение и отправить запрос PUT с телом, содержащим новое значение, вместе с заголовком If-Match.

PUT /value HTTP/1.1
Content-Type: application/json
If-Match: "0“

{ "value": 1 }

На первый запрос сервер отвечает со статусом 200 (OK), а на второй со статусом 412 (Precondition Failed), т.е. второй клиент должен повторить свою задачу (GET, обновить ресурс и PUT его с заголовком If-Match с новым etag ценность).

Теперь давайте предположим, что оба клиента не получили ответа и, следовательно, оба снова отправляют запрос (очевидно, что они должны использовать один и тот же заголовок If-Match). Согласно спецификации, сервер ДОЛЖЕН возвращать одинаковые коды состояния, то есть 200 для первого и 412 для второго. Если сервер вернет 200 обоим, обновление будет потеряно, а если сервер вернет 412 обоим, то оба клиента начнут заново, и в конце значение будет равно 3.

Мой вопрос в том, как это можно реализовать на стороне сервера. Я вижу необходимость в том, чтобы сервер хранил историю всех изменений состояния вместе с идентификацией клиента. Но как идентифицировать клиента? Будет ли достаточно комбинации client-ip и заголовка User-Agent?

Существуют ли (Java) библиотеки, которые реализуют это поведение из коробки? Также мне непонятно, какое поле заголовка валидатора нельзя отправлять клиенту.


person Dominik    schedule 20.03.2019    source источник
comment
Я не понимаю, где вы видите проблему. Сервер имеет текущее состояние ресурса (метаданные, такие как ETag и контент). Этого достаточно для обработки условного запроса.   -  person Julian Reschke    schedule 20.03.2019
comment
Привет @JulianReschke, спасибо за ваши комментарии! Сервер имеет новое состояние { "value": 1} с новым etag 1 и без специальной обработки вернет 412 Precondition Failed на оба повторно отправленных запроса, в результате чего оба клиента начнут заново, получат текущее состояние с новым etag, изменят данные и обновят данные. Спецификация указывает, что в этом случае первому абоненту должно быть возвращено 200, а второму абоненту — 412, т.е. сервер должен хранить историю всех переходов вместе со ссылкой на клиента. Я правильно понял и как это реализовано?   -  person Dominik    schedule 20.03.2019
comment
Я до сих пор не понимаю, зачем вам история. Текущего состояния вполне достаточно. Тот факт, что оба клиента получат ИМХО 412, не является проблемой. Зачем оптимизировать для пограничного случая?   -  person Julian Reschke    schedule 20.03.2019
comment
Я использую условное размещение для реализации оптимистической блокировки. В приведенном выше простом примере оба клиента пытаются увеличить значение, используя оптимистическую блокировку. Если оба клиента получат 412 в результате повторной отправки, то оба клиента начнут заново и запросят значение (вместе с текущим тегом etag), увеличат его и обновят значение на сервере. Наконец, значение, хранящееся на сервере, будет `{ value : 3}', что будет неправильным, поскольку операция inc применялась три раза. В приведенной выше спецификации указано, что сервер должен обрабатывать этот случай, поэтому я не хочу оптимизировать, а следую спецификации.   -  person Dominik    schedule 21.03.2019
comment
Спецификация не соответствует требованиям, которые вы, кажется, предполагаете. Потеря ответа на успешный запрос ИМХО - это крайний случай. Так что да, если вы выполняете условные запросы PUT для обновления счетчика, вы получите дополнительное обновление.   -  person Julian Reschke    schedule 21.03.2019
comment
если бы сервер в случае несовпадения заголовка If-Match только проверял, отражено ли запрошенное состояние уже в текущем состоянии и возвращал бы в этом случае 2xx, то оптимистическая блокировка не сработала бы. Поэтому в спецификации отмечается, что 2xx возвращается только в том случае, если изменение, запрошенное агентом пользователя, уже выполнено успешно, поэтому ответ зависит от агента пользователя, верно?   -  person Dominik    schedule 21.03.2019