-1

Imagine an HTTP REST endpoint, where a resource is inserted, and that the resource is understood to be a "message". Each individual message is identified by a unique identifier, such as a GUID value of some sort. The same message cannot be duplicated.

Now, in a lot of cases, that would lend itself to the PUT verb, since it's idempotent. However consider the following scenario:

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"
    }

The reason for the receiver's behavior is that each distinct message is to be uniquely identified by its id field, and that if another message is sent with the same id, it is simply considered a duplicate. Furthermore, trying to change the message's contents like that and resend it with the same ID is invalid behavior on the sender's end.

So technically this is idempotent, which usually leans toward PUT. However updates are not allowed in any context here, simply inserts. So do we choose PUT or POST, or does it even matter? PUT is supposed to be the full representation of the resource, but if it's simply discarded, is it okay to still return a 2xx response?

For the purposes of this question, assume that the route used is always of the form <host>/.../message, never of the form <host>/.../message/{id}. In that case, I am wondering if being restricted to the <host>/.../message route scheme automatically means POST should be used.

Panzercrisis
  • 4,590
  • 6
  • 46
  • 85

2 Answers2

1

Now, in a lot of cases, that would lend itself to the POST verb, since it's idempotent.

This might be a typo on your end, but POST is NOT idempotent. However PUT is.

PUT is supposed to be the full representation of the resource, but if it's simply discarded, is it okay to still return a 2xx response?

Totally fine. You requested that a resource with a given id have a given state, and that will be true when the response is sent back.

So do we choose PUT or POST, or does it even matter?

I would say good options are

  • PUT /messages/{id}, 201 if the id is new, 204 if the message has the same text as the existing message, 4xx if the text is not the same as the existing message
  • POST /messages, 201 if the id is new, 4xx if the id already exists

With the PUT the client can see "oh, 2xx, message delivered", or "4xx, I messed up". But you have to have some mechanism (maybe a fingerprint) to quickly check if the text is the same as the initial message.
With the POST the client would need to inspect an error response to see if they needed to fix something and try again, or if the message was already delivered. If we returned a 2xx (other than 202) from our POST of an existing message, most clients would interpret that to mean that a new (duplicate) message had been sent.

assume that the route used is always of the form <host>/.../message, never of the form <host>/.../message/{id}

An unfortunate requirement, in that case I would use POST as described above. We technically could use PUT with that path, but typically that would signal that we want to create/replace the entire message history (you could document around it, but operations that don't do what they look like they should are not typically considered RESTfull).

kag0
  • 5,624
  • 7
  • 34
  • 67
  • Yeah, sorry, `POST` was a typo. Thanks for pointing that out. – Panzercrisis May 28 '20 at 18:23
  • Thank you. It sounds like your answer is suggesting `PUT /.../message/{id}` to be generally necessary in cases like this, with either `POST` or `/.../message` only being necessary on a much more case-by-case basis. Am I understanding that correctly? – Panzercrisis May 28 '20 at 18:30
  • 1
    Yes, that's about right. I'd recommend the `PUT` approach generally but with your constraints this would be a `POST` case. – kag0 May 28 '20 at 19:12
1

Imagine an HTTP REST endpoint, ...

According to Fielding himself

There is no such thing as a REST endpoint. There are resources. A countably infinite set of resources bound only by restrictions on URL length. (Source)

So technically this is idempotent, which usually leans toward either PUT or PATCH ...

Idempotency is an important property to a client in regards to temporary network issues which allows it to automatically resend the request in case it did not receive a response within a reasonable amount of time. In such a case a client does not know whether the initial request received the server at all or just the response got lost. Idempotency has nothing to do with your demand to only allow to write to a resource only once. In addition to that, PATCH is not idempotent by default. Idempotent just means that processing a request always results in the same outcome, regardless how many times you send that request to the server.

Each individual message is identified by a unique identifier, such as a GUID value of some sort

... each distinct message is to be uniquely identified by its id field, and that if another message is sent with the same id, it is simply considered a duplicate.

A resource always is uniquely identifiable via its URI as a URI itself can only reference one single resource. A single resource however may have multiple URIs, i.e. one with an other without query parameters available. URI is an abbreviation for uniform resource identifier, where uniform denotes to remaining the same in all cases and at all times. This makes the requirement for the id field in my opinion redundant.

As far as I understood your question, the actual text of the message does not define whether a message is a duplicate or not. So basically

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

{
  "text": "original text"
}

and

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

{
  "text": "original text"
}

are requests for two different messages as they target different resources, while you don't want to allow something like

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

{
  "text": "amended text"
}

as this would update an existing message.

In such a case I'd disable PUT completely for messages by not supporting it while allowing the creation of new messages only via POST. A request/response sample may look like this:

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

{
  "text": "Some other text"
}

which may yield a response such as:

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"
}

Attempting to update such a resource via

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"
}

should result in a

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

response, which states that a server knows the indicated PUT operation but does not support that operation on the target resource. The mandatory Allow header furthermore teaches your client about the valid HTTP operations supported by that resource.

For the purposes of this question, assume that the route used is always of the form /.../message, never of the form /.../message/{id}. In that case, I am wondering if being restricted to the /.../message route scheme automatically means POST should be used.

In such a case PUT is not an option for you unless that request contains all the messages that should be available after the request was processed. The semantics of PUT are defined as replacing the current representation stored on the target resource with the one provided in the request payload. So, if you send a PUT request to a "collection resource" you'd technically replace all of the messages currently available with the ones defined in the request.

Community
  • 1
  • 1
Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
  • Thank you. Earlier though I went back and fixed the "`PATCH` is idempotent" thing, since it was closer to a typo. (Technically I got mixed up about it for a second, but generally realized it was not idempotent.) – Panzercrisis May 29 '20 at 13:51