The Hypertext Transfer Protocol rfc(2616) states:
Client Error 4xx: The 4xx class of status code is intended for cases in which the client seems to have erred.
Let’s look at a couple of scenarios which illustrate what I see as an annoying gap in convention for how to handle this class of error.
400 Bad Request The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.
POST /user
{ "username" : <at least 12 characters> }
POST /user
{ "usernam" : "this_is_long_enough" }
(notice the json key)400 BAD REQUEST
Compared to (assuming the same GIVEN, AND)
POST /user
{ "username" : "tooshort" }
400 BAD REQUEST
The first example (where the username field name was misspelt) you could think of as a “psuedo-compile” error (e.g. it could be found through a sufficiently advanced static analysis tool). The second example (where the value did not meet some validation criteria) could only really be found at runtime. However both give the same status code.
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.
GET /report/{report_id}
GET /reprt/1
404 NOT FOUND
Compared to (assuming the same GIVEN, AND)
/report/2
404 NOT FOUND
Again, the first example (where the resource type was misspelt) you know is wrong simply be inspecting the contract, but the second example you can’t know if it’s wrong without knowing the internal state of the provider. Again, the same status code is returned.
So the two examples above give us two distinct classes of “the client seems to have erred”
From a consumer point of view, the action they would have to take pretty different:
So given the above what are the options.
The first option (always!) is Do Nothing. You are obviously already including explanations for 4xx errors as per the spec:
Client Error 4xx: the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition
However, I find this unsatisfactory. It requires a level of inspection that shouldn’t be required for such a broad difference in context, response entities are harder to see in most network devices, logging frameworks and debug tools and there is no good convention for how to structure them.
As per Microsoft
I’m not really a big fan. Whilst it’s not explicitly against the spec as far as I can see, I think most people (and maybe protocol implementations!) expect status codes to be 3 digit integers NOT floats/strings .
Nginx, twitter and Spring Framework have all had a habit of doing in the past.
But it means there is no external reference point for the (unless you are a big-enough deal to get on the Wikipedia Page!)
So it turns out the existing 4XX codes are sufficient if (a) you are willing to think more carefully about what they actually mean and (b) you don’t mind resorting to some that came from the WebDav spec!
Contract failures:
405 METHOD NOT ALLOWED
= Request could not be pattern-matched (uri + method) to an endpoint (e.g don’t know which contract to inspect). I’m advocating using this for e.g. GET /doesnotexist
400 BAD REQUEST
= Request matched endpoint but did not meet the agreed contract. In this case I mean it is more likely that the consumer dev team will need to change their implementation.“Dynamic/Runtime” failures:
422 UNPROCESSABLE ENTITY
= Request matched and met syntactic contract but validation failed404 NOT FOUND
= Request matched contract but stateful entity (or 410 GONE
if you know a resource instance has been deleted the id will never be reused)Other useful 4xx codes:
409 CONFLICT
= When a PUT is used to an identifier that already exists or a POST (creation or update) breaks a unique constraint429 TOO MANY REQUESTS
= When a throttling limit has been hitThe great thing about this approach is you get to use my favourite HTTP Status Dog
Tweet comments powered by Disqus