HTTP Status 412 (Precondition Failed) and Database

2020-02-12 02:43发布

问题:

I am implementing a RESTful web service that accesses a database. Entities in the database are versioned to detect multiple updates. For instance, if the current value is {"name":"Bill", "comment":"tinker", "version":3}, if one user PUTs {"name":"Bill", "comment":"tailor", "version":3}, the request will succeed (200 OK) and the new value will be {"name":"Bill", "comment":"tailor", "version":4}. If a second user PUTs {"name":"Bill", "comment":"sailor", "version":3"} that request will fail (409 Conflict) because the version number does not match.

There are existing non-RESTful interfaces, so the design of the databases cannot be changed. The RESTful interface calls an existing interface that handles the details of checking the version.

A rule of thumb in RESTful web services is to follow the details of HTTP whenever possible. Would it be better in this case to use a conditional header in the request and return 412 Precondition Failed if the version does not match? The appropriate header appears to be If-Match. This header takes an ETag (Entity Tag) which could be a hash of the representation of the current state of the resource.

If I did this, the ETags would be for appearances' sake, because the version would still be the real thing I'm testing for.

Is there any reason I should do this, other than "making it more RESTful", whatever that is supposed to mean?

回答1:

The appropriate thing to do is always to follow the HTTP spec if you're using HTTP, and the reason is simply to allow people who understand the spec to function correctly.

412 should only be used if a precondition (e.g. If-Match) caused the version matching to fail, whereas 409 should be used if the entity would cause a conflict (the HTTP spec itself alludes to this behaviour in the definition of 409).

Therefore, a client that doesn't send ETags won't be expecting a 412. Conversely, a client that does send ETags won't understand that it's ETags that are causing a 409.

I would stick with one way. You say that "the database schema can't change", but that doesn't stop you (right in the HTTP server layer) to extract the version from the datbase representation and put it in the ETag, and then on the way in, take the If-Match header and put it back in the version field.

But doing it completely in the entity body itself isn't forbidden. It just requires you to explain the concept and how it works, whereas with the ETag solution you can just point people to the HTTP spec.

Edit: And the version flag doesn't have to be a hash of the current resource; a version is quite acceptable. ETag: "3" is a perfectly valid ETag.