Using the PUT method with incomplete representatio

2019-01-25 15:19发布

问题:

What is the standard behavior expected from a PUT with an incomplete representation of the resource?

For instance, I have a User at the /api/users/1 that's represented by the HAL json below:

{'id': 1,
 'username': 'joedoe',
 'email': 'joe@doe.com',
 'password_hash': '9039dmk38f84uf4029i339kf32f0932i',
 'last_visit': '2013-11-04 21:09:01',
 'public': true,
 '_links': {'self': {'href': 'http://foo.bar.com/api/users/1'}}

}

Then, I make a PUT request to change username and email, with a representation missing the other attributes:

PUT /api/users/1

{'username': 'joeydoey',
 'email': 'joey@doey.com'}

Up till now, I always assumed this should be treated as an error, because it implies a partial update, but this answer got me thinking about it, and it makes sense to say that an incomplete representation is still a complete replacement that's giving the server liberty to fill in the blanks with defaults.

I can't find anything that I could relate to this on the HTTP standard, so I have to ask, what's the standardized behavior expected in this case?

  1. It should result in an error, because it implies a partial update. The schema of the PUT payload should be identical to the one retrieved with a GET to the same resource and media type.

  2. It should succeed, because the server is free to fill the blanks with the defaults for that media type. In this case, it would reset the password to a blank or default password and refresh the hash accordingly, and set the last_visit and public values to defaults. This option makes a lot more sense when you consider HATEOAS, if the client is submitting the same media-type returned by the server, since it can't predict how the hypermedia controls will change, the representation is necessarily incomplete every time the client isn't sending all hyperlinks, and the server must reset them accordingly.

  3. Both 1 and 2 are valid, because there's no standardized behavior and it's up to the media type to decide what to do with it. This doesn't feel right because a PUT is not subordinated to the resource itself, but rather replaces it.

Keep in mind that I'm not asking what feels right or what makes sense. I'm asking which one is backed by the standards.

回答1:

As long as the result of the PUT is a complete replacement from the client's understanding of the resource (i.e., the previous values for those properties not passed do not influence their value after the PUT), it should succeed. However, it does make it a bit confusing to look at, since so many people have a tendency to equivocate this use of PUT with field-level update semantics (rather than complete replacement).

While there's technically no violation of REST constraints here, it's probably a better idea to pass in all values and not resort to server defaults, because that will help preserve forward compatibility. Defaults can change over time, so generally they should be avoided.

Your example of links, however, is not referring to not passing defaults, so it's not a good example of an "incomplete representation." Rather, links aren't part of the client's representation of the resource to the server. I think you're bringing another concept into the mix here: properties which are only returned from the server to the client. This is what I was talking about on the other thread which sparked this post.

What I am talking about is not an incomplete representation; it's a different representation. You are really dealing with two different media types (i.e., representations) describing the same resource. One originates from the client (we'll call it application/vnd.example.api.client), and the other originates from the server (application/vnd.example.api.server). They may not explicitly be labeled as such, but that's at least implicitly what's happening. Therefore, since they are two different media types, they express different things about the same resource.

Since you mentioned HAL, consider that a client doesn't usually POST a message of media type application/hal+json to the server. Take a look at the signup rel from HALTalk for an example. The expected content type is application/json, not application/hal+json. And, if you look at the example post, there is nothing HAL-ish about it. No links, no embedded objects, etc. But... if you then GET the URL returned from the Location header returned from this POST, it will, assuming your client accepts HAL over JSON, return a response of type application/hal+json (i.e., a user with links). Two different media types, two different representations of the same resource.

So let me decorate your example with Accept and Content-Type headers to illustrate my point.

Request

PUT /api/users/1 HTTP/1.1
Content-Type: application/vnd.example.api.client+json

{'username': 'joeydoey',
 'email': 'joey@doey.com'}

Response

200 OK
Content-Type: application/hal+json;profile=application/vnd.example.api.server

{'id': 1,
 'username': 'joeydoey',
 'email': 'joey@doey.com',
 'password_hash': '9039dmk38f84uf4029i339kf32f0932i',
 '_links': {'self': {'href': 'http://foo.bar.com/api/users/1'}}
}

Most systems don't go to this amount of detail to describe their media types. Usually it's just framed in a more generic type (like application/json or only one custom media type). However, none of this changes that fact that while it is the same underlying resource, these are two different representations. Make sense?



回答2:

PUT is for replacement. The server can modify/augment the data, but what's the final representation should be a function of the payload, not of the final state.

In your example, the password hash doesn't seem to be something the server can fill in, right? In that case, the PUT should result in an error.



标签: api http rest