8

So the HTTP spec says that HTTP PUT and DELETE should be idempotent. Meaning, multiple PUT requests to the same URL with the same body should not result in additional side-effects on the server. Same goes with multiple HTTP DELETEs, if 2 or more DELETE requests are sent to the same URL, the second (or third, etc) requests should not return an error indicating that the resource has already been deleted.

However, what about PUT requests to a URI after a DELETE has been processed? Should it return 404?

For example, consider the following requests are executed in this order:

  • POST /api/items - creates an item resource, returns HTTP 201 and URI /api/items/6
  • PUT /api/items/6 - updates the data associated with item #6
  • PUT /api/items/6 - has no side effects as long as request body is same as previous PUT
  • DELETE /api/items/6 - deletes item #6 and returns HTTP 202
  • DELETE /api/items/6 - has no side effects, and also returns HTTP 202
  • GET /api/items/6 - this will now return a 404
  • PUT /api/items/6 - WHAT SHOULD HAPPEN HERE? 404? 409? something else?

So, should PUT be consistent with get and return a 404, or like @CodeCaster suggests, would a 409 be more appropriate?

j0k
  • 22,600
  • 28
  • 79
  • 90
danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Your question is misleading. It really does not matter what the second DELETE returns as long as the resource is indeed not there anymore. – Julian Reschke Oct 29 '12 at 16:58
  • @JulianReschke, really? So the first DELETE can return a 200, 202, or 204, and the second delete can return 404? Is the bottom section of this article misleading then? http://www.asp.net/web-api/overview/creating-web-apis/creating-a-web-api-that-supports-crud-operations "Therefore, the method should not return an error code if the product was already deleted." – danludwig Oct 29 '12 at 17:00
  • yes, that article is misleading. It really does not matter with respect to idempotence. – Julian Reschke Oct 30 '12 at 08:26
  • 1
    It would make more sense to me, to return 404 from the 2nd DELETE. – Cheeso Oct 30 '12 at 18:48
  • 2
    Your question should explicitly state that the desire to disallow PUT at a given URI is *your specific restriction*. There is nothing in the RFC2616 that disallows this. It is a design choice in your application protocol. – Cheeso Oct 30 '12 at 19:03
  • @Cheeso your comments and answer are apt. I should have stated that my server does not allow clients to create resource URI's via PUT. – danludwig Oct 30 '12 at 19:14

2 Answers2

10

RFC 2616, section 9.6, PUT:

The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource.

And:

If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem.

So to define 'appropriate' is to look at the 400-series, indicating there's a client error. First I'll eliminate the irrelevant ones:

  • 400 Bad Request: The request could not be understood by the server due to malformed syntax.
  • 401 Unauthorized: The request requires user authentication.
  • 402 Payment Required: This code is reserved for future use.
  • 406 Not Acceptable: The resource identified by the request [...] not acceptable according to the accept headers sent in the request.
  • 407 Proxy Authentication Required: This code [...] indicates that the client must first authenticate itself with the proxy.
  • 408 Request Timeout: The client did not produce a request within the time that the server was prepared to wait.
  • 411 Length Required: The server refuses to accept the request without a defined Content- Length.

So, which ones may we use?

403 Forbidden

The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.

This description actually fits pretty well, altough it is usually used in a permissions-related context (as in: YOU may not ...).

404 Not Found

The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.

This one too, especially the last line.

405 Method Not Allowed

The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource.

There are no valid methods we can respond with, since we don't want any method to be executed on this resource at the moment, so we cannot return a 405.

409 Conflict

Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the entity being PUT included changes to a resource which conflict with those made by an earlier (third-party) request, the server might use the 409 response to indicate that it can't complete the request. In this case, the response entity would likely contain a list of the differences between the two versions in a format defined by the response Content-Type.

But that assumes there already is a resource at the URI (how can there be a conflict with nothing?).

410 Gone

The requested resource is no longer available at the server and no forwarding address is known. This condition is expected to be considered permanent. Clients with link editing capabilities SHOULD delete references to the Request-URI after user approval. If the server does not know, or has no facility to determine, whether or not the condition is permanent, the status code 404 (Not Found) SHOULD be used instead.

This one also makes sense.


I've edited this post a few times now, it was accepted when it claimed "use 410 or 404", but now I think 403 might also be applicable, since the RFC doesn't state a 403 has to be permissions-related (but it seems to be implemented that way by popular web servers). I think I have eliminated all other 400-codes, but feel free to comment (before you downvote).

Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Thank you for this very well-referenced answer. The part that is dissonant with relates to how GET requests behave after an item is deleted. After DELETE /api/itesm/6, GET /api/items/6 will return a 404. I do not want to allow users to PUT /api/items/6 unless the resource already exists (to create, they must POST to /api/items). So 404 seems to be more appropriate than 409. I will update my question. – danludwig Oct 29 '12 at 16:45
  • @danludwig I have edited my post and learned I should read RFC's even with more attention, since half of my post was irrelevant. I'd say go with 410, or indeed a 404 if you don't have a Deleted bit on your items. – CodeCaster Oct 29 '12 at 17:00
  • There is no deleted bit, but we do log an audit trail. I want to avoid having to look into that in order to determine whether an item previously existed. What about Julian's comment? Is it okay to return a 202 from the first DELETE, and a 404 from the second? Maybe I was reading a malinformed article... – danludwig Oct 29 '12 at 17:03
  • @danludwig idempotence is about the server state. The article is wrong. The second DELETE must return a 410 or 404, since the resource doesn't exist anymore. See also: http://stackoverflow.com/questions/4088350/is-rest-delete-really-idempotent – CodeCaster Oct 29 '12 at 17:23
  • This answer is misleading. There is no requirement from RFC2616 that PUT must fail if there is no object extant at the given URI. And there no statement to that effect in the question. – Cheeso Oct 30 '12 at 19:02
  • @Cheeso it says it should: _"If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem"_. – CodeCaster Oct 30 '12 at 20:55
  • @codeCaster - Thank you for quoting that portion of the spec, but it is not relevant. I am not suggesting that a status code should be returned that does not reflect the nature of the problem. you and I are apparently interpreting the RFC2616 differently. I am saying that there is no reason stated in the spec to disallow PUT to a specified URL. I will repeat it: The spec does say that PUT must fail if an object does not already exist at the given path. – Cheeso Oct 31 '12 at 05:56
  • @Cheeso yes you are, you are suggesting to reply with 400, indicating a syntax error in the request. The portion I quoted is very relevant, it says you SHOULD reply with an appropriate status. In my answer I've selected the appropriate responses for the described case (a PUT to a non-existing resource, so it cannot be created or updated). – CodeCaster Oct 31 '12 at 07:16
  • You are misunderstanding me. If you and I are disagreeing about what I am asserting, then you are misunderstanding me. I don't know how to be more clear. The piece you quoted says *you SHOULD reply IF....* IF!! *If the resource could not be created.** The RFC does not say, "YOU MUST NOT ALLOW PUT for URIs that do not exist." The RFC does not prohibit servers from allowing PUT this way, and does not recommend (eg, using the word "should") that servers not allow it. You are apparently assuming a restriction that does not exist. In fact, a PUT To a non-extant resource may succeed. – Cheeso Oct 31 '12 at 07:31
  • I do not say PUT should be allowed for every URI. The RFC states that the server _may_ allow resource creation through PUT to "new" URI's, but also states that the server may deny such requests. I do not know how that is relevant though: the client performs a PUT to some URI where it thinks a resource resides, in order to _update_ this resource. PUT _creation_ is impossible on this server, OP said, they need to POST to create, but PUT can be used to update resources. – CodeCaster Oct 31 '12 at 07:44
  • 1
    So: _"If the resource could not be created **or modified** with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem"_ is absolutely relevant: the resource at that URI cannot be updated, because there was _no resource found_ at that URI, so a 410 (if there used to be a resource and the server is knowing and willing to publish that) or a 404. – CodeCaster Oct 31 '12 at 07:45
  • 400 or 403 seem to be the only valid options. "Gone" is a response that implies a resource was once at that location. But that is not always the case, and you wouldn't want to have to track that state on the server, either. None of the other 4xx codes make sense. – Cheeso Nov 01 '12 at 04:19
2

Your question has an unstated, assumed premise, that the resource must exist for a PUT to succeed. This is not a valid assumption.

The relevant portion of the spec (RFC2616) says:

the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource.

The spec does not say, "An object at the referenced URI must already exist in order for a PUT to that URI to succeed."


The easy example is a web store implemented via REST. GET returns a representation of the object at the given path, while DELETE removes the item at the given path. Those are easy. But the POST and PUT are not much more difficult to understand. POST can do anything, but one use of POST creates an object in a container that the client specifies, and lets the server return the URI of the newly created object within that container. PUT is more limited; it gives the server the representation for an object at a given URI. The object may already exist, or it may not. PUT is not a synonym for REPLACE.

In my opinion 409 or 410 is wrong for a PUT, unless the container itself - the thing you are trying to put into, does not exist.

therefore:

POST /container
   ==> returns 200 with `Location:/container/resource-12345`

PUT /container/resource-98928
   ==> returns 201 CREATED or 200 OK

PUT /this-container-does-not-exist/resource-22828282
   --> returns 400

Of course it is up to you, whether you'd like your server to allow these PUT semantics. But there's nothing in the spec that says you must not allow clients to provide the URI of the resource that he is PUTting.

Cheeso
  • 189,189
  • 101
  • 473
  • 713
  • See my updated answer. Returning a 400 is definetely not the way to go since that's for malformed syntax in the request. You do not defend your choice for the 400, nor do you say _why_ 409 or 410 are wrong, you just say they are. Furthermore you talk about the container not existing which would allow a 409 or 410 response, I think you mean like OP's `/api/items/` path as a container. Why would `PUT`ting to `/api/tiems/6` (non-existant container) return a 409 or 410? – CodeCaster Oct 30 '12 at 21:30
  • That is correct, I refer to `/api/items` as a container. Why `PUT` to `/api/items/6` would return 409 or 410 is not clear to me; I am not the one advocating such a protocol. I suggested a 201, 200, or 400. You pointed out that I did not *explain* why I think 409 and 410 are wrong, but *you never asked me to explain.* Is this some sort of passive-aggressive question on your part? – Cheeso Oct 31 '12 at 05:54
  • Please read my answer for why a PUT might result in a 404, 409 or 410 and feel free to comment on my reasoning. Now you're just being offensive and not answering my questions, probably because you don't agree with me. I want you to explain why the status codes I supposed aren't valid, because I did my best to explain why they are, and you just reply "they're wrong, 400 is right". – CodeCaster Oct 31 '12 at 07:10
  • 409 is inappropriate because there is no conflict. 410 is inappropriate because in fact, it's ok to PUT something when it does not exist. You are construing PUT to mean "Replace", and that is not correct in the general case. HTTP and REST allow a PUT of a document to a specific location on a server, regardless whether a document already exists at the given location. Regarding "not answering your questions": previous to this comment, you asked no questions. Instead, you pointed out something I did not provide. That is not a question, it's an observation. – Cheeso Oct 31 '12 at 07:35
  • I said **a** PUT, in OP's case 409 is inappropriate, which I explained in my answer. I asked a question in my first comment to your answer. RFC says: _" If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. [...] If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem"_. – CodeCaster Oct 31 '12 at 07:51
  • A client may PUT a new resource _if it thinks that URI points to a new resource_. This is used in CalDAV for example, where a clients generates a UID for a new event and PUTs it to an URI containing that UID (`PUT /calendars/123-456-789.ics`). That is not the case in this question, where the client is only allowed to _update_ resources through PUT, a limitation thats (now) been stated by OP and a freedom the RFC provides in. So, in this case, PUT == update. If a client of his server wants to update a non-existant resource, 410 or 404 are the most appropriate response and I'll leave it to that. – CodeCaster Oct 31 '12 at 07:58
  • 1
    ok, CodeCaster, given 410 means "The requested resource is **no longer** available," what should the server return if the client tries a PUT to a URI that *was never available*? 410 Gone? Are you going to suggest that the server should return 410 if the URI was *at one time* available, and 400 (or something other than 410) if the URI had never been used before? That makes no sense to me as a protocol. It puts undue burden on the server and client both. 400 is simpler, and works for all cases. – Cheeso Oct 31 '12 at 17:20