HTTP REST: если запрос идемпотентен, но после вставки ресурс нельзя изменить, следует ли использовать PUT или POST? - PullRequest
0 голосов
/ 28 мая 2020

Представьте себе конечную точку HTTP REST, в которую вставлен ресурс, и этот ресурс воспринимается как «сообщение». Каждое отдельное сообщение идентифицируется уникальным идентификатором, например каким-либо значением GUID. Одно и то же сообщение не может быть продублировано.

Теперь, во многих случаях, это поддается глаголу PUT, поскольку он идемпотентный. Однако рассмотрим следующий сценарий:

1. The sender sends this message to the receiver:

    {
        "id": 123,
        "text": "original text"
    }

2. Now the receiver has this value for the message stored in its database:

    {
        "id": 123,
        "text": "original text"
    }

3. For whatever reason, the sender tries to send the same message again, but with amended
   text:

    {
        "id": 123,
        "text": "amended text"
    }

4. The receiver receives that as well, but since the id field is the same as before, no
   action is taken, and this is what the receiver still has in its database:

    {
        "id": 123,
        "text": "original text"
    }

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

Так что технически это идемпотентно, что обычно склоняется к PUT. Однако обновления здесь не разрешены ни в каком контексте, просто вставки. Так что мы выбираем PUT или POST, или это вообще имеет значение? PUT должен быть полным представлением ресурса, но если он просто отброшен, можно ли по-прежнему возвращать ответ 2xx?

Для целей этого вопроса предположим, что использованный маршрут всегда имеет форму <host>/.../message и никогда не имеет форму <host>/.../message/{id}. В этом случае мне интересно, означает ли автоматическое ограничение схемой маршрута <host>/.../message использовать POST.

Ответы [ 2 ]

1 голос
/ 29 мая 2020

Представьте себе конечную точку HTTP REST, ...

По словам самого Филдинга

Конечная точка REST не существует. Ресурсы есть. Счетно бесконечный набор ресурсов, связанных только ограничениями на длину URL. ( Источник )

Так что технически это идемпотент, который обычно склоняется к PUT или PATCH ...

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

Каждое отдельное сообщение идентифицируется уникальным идентификатором, например значением GUID некоторого вида

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

Ресурс всегда однозначно идентифицируется через его URI, поскольку сам URI может ссылаться только на один единственный ресурс. Однако один ресурс может иметь несколько URI, т. Е. Один с другим без доступных параметров запроса. URI - это сокращение от унифицированного идентификатора ресурса , где uniform означает , остающееся неизменным во всех случаях и всегда . Это делает требование для поля id , на мой взгляд, избыточным.

Насколько я понял ваш вопрос, фактический текст сообщения не определяет, является ли сообщение дубликатом или нет . Итак, в основном

PUT /messages/eaff31bd-5291-4287-a7dd-fbe5b1e47b67 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...

{
  "text": "original text"
}

и

PUT /messages/eb1db214-d231-4a50-916c-8de2d64c7d3a HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...

{
  "text": "original text"
}

- это запросы для двух разных сообщений, поскольку они нацелены на разные ресурсы, в то время как вы не хотите разрешать что-то вроде

PUT /messages/eaff31bd-5291-4287-a7dd-fbe5b1e47b67 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
...

{
  "text": "amended text"
}

, так как это обновит существующее сообщение.

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

POST /messages HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
Accept: application/json
...

{
  "text": "Some other text"
}

, что может дать ответ, например:

HTTP/1.1 201 Created
Content-Type: application/json; charset="utf-8"
Location: http://www.acme.com/messages/740177d2-1de9-41bd-bd5b-6a52f39bf227
Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4

{
  "text": "some other text"
}

Попытка обновить такой ресурс через

PUT /messages/740177d2-1de9-41bd-bd5b-6a52f39bf227 HTTP/1.1
Host: www.acme.com
Content-Type: application/json; charset="utf-8"
Etag: 33a64df551425fcc55e4d42a148795d9f25f89d4

{
  "text": "updated text"
}

должен привести к ответу

HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD

, в котором говорится, что сервер знает указанную операцию PUT, но не поддерживает эту операцию на целевом ресурсе . Обязательный заголовок Allow, кроме того, сообщает вашему клиенту о допустимых HTTP-операциях, поддерживаемых этим ресурсом.

Для целей этого вопроса предположим, что используемый маршрут всегда имеет форму / ... / message, а не в форме /.../message/{id}. В этом случае мне интересно, означает ли автоматическое ограничение схемы маршрута /.../message POST.

В таком случае PUT не подходит для вас, если только этот запрос содержит все сообщения, которые должны быть доступны после обработки запроса. Семантика PUT определяется как замена текущего представления, хранящегося в целевом ресурсе, на представление, предоставленное в полезных данных запроса. Итак, если вы отправляете запрос PUT на «ресурс сбора», вы технически заменяете все сообщения, доступные в настоящее время, на те, которые определены в запросе.

1 голос
/ 28 мая 2020

Так вот, во многих случаях это поддается глаголу POST, поскольку он идемпотент.

Это может быть опечатка с вашей стороны, но POST - это НЕ идемпотентный. Однако PUT есть.

PUT должен быть полным представлением ресурса, но если он просто отброшен, можно ли по-прежнему возвращать ответ 2xx?

Совершенно нормально. Вы запросили, чтобы ресурс с заданным идентификатором имел заданное состояние, и это будет верно, когда ответ будет отправлен обратно.

Итак, выбираем ли мы PUT или POST, или это вообще имеет значение?

Я бы сказал, что хорошими вариантами являются

  • PUT /messages/{id}, 201, если идентификатор новый, 204, если сообщение имеет тот же текст, что и существующее сообщение, 4xx, если текст отличается от существующего сообщения
  • POST /messages, 201, если идентификатор новый, 4xx, если идентификатор уже существует

С PUT клиент может видеть «о, 2xx, сообщение доставлено» или «4xx, я напортачил». Но у вас должен быть какой-то механизм (возможно, отпечаток пальца), чтобы быстро проверить, совпадает ли текст с исходным сообщением.
С POST клиенту нужно будет проверить ответ об ошибке, чтобы узнать, нужно ли исправить что-нибудь и попробуйте еще раз, или если сообщение уже доставлено. Если бы мы вернули 2xx (кроме 202) из ​​нашего POST существующего сообщения, большинство клиентов интерпретировали бы это как означающее, что было отправлено новое (дублированное) сообщение.

предполагаем, что Используемый маршрут всегда имеет форму <host>/.../message и никогда не имеет форму <host>/.../message/{id}

К сожалению, требование, в этом случае я бы использовал POST, как описано выше. Технически мы могли бы использовать PUT с этим путем, но обычно это будет сигнализировать о том, что мы хотим создать / заменить всю историю сообщений (вы можете задокументировать это, но операции, которые не делают то, что выглядят так, как должны, обычно не считается RESTfull).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...