Putting off PUT

Crell's picture

In earlier discussions, we had tried to map out how we would handle different HTTP methods within Drupal. As often happens, though, no plan survivies first contact with the enemy. We've had to make some adjustments along the way, but some of them have been bigger than others.

Perhaps the biggest stumbling block has been the HTTP PUT method. Based on our experience so far, the REST team has decided the best course of action is to simply drop support for it from the rest.module in Drupal 8.

In the original plan, we had determined that the semantics of PUT were such that we could still support it for updating of entities. It would be tricky, but we could. Most of the questions revolved around the idea of revisioning, and we figured out how to map the URIs such that revisioning made sense with PUT.

Unfortunately, we've run into other issues with PUT while implementing the rest.module. Specifically, the requirement that PUT's body be the value to be set at that URI, and that would then be returned by a GET to that URI. For simple binary objects (files) or simple data objects, that's fine. However, Drupal's entities are not simple data objects. They're actually really complex, especially when you consider that in Drupal 8, every entity is likely to contain at least one relationship to another entity. (An Entity Reference in Drupal parlance, or a Link in Hypermedia-tongue.) Consider nodes, where every node will, at minimum, have a Link to its authoring User. Then mix in taxonomy and you have potentially several more.

That is no issue when representing an entity in HAL when responding to a GET request. HAL, which we'll be moving to for our primary format, has a standard way of handling links to other entities and for embedding entities with the main object for improved performance, and they're reasonably straight forward to generate.

For PUT, however, it becomes more complicated because the PUT would be required to include exactly the Link relations that we would then get back on a GET. That's not always feasiable, especially given how inter-related the Links are in Drupal. PUT's semantics, however, say that we could not ignore them, and the resulting changes to Drupal's data store could be... complicated.

Another issue is field permissions. Klausi has been working on figuring out field permission support when doing RESTful updates for a while, but it's challenging. And, really, impossible to do without violating the rules of PUT. As soon as you enable field-level edit restrictions, you cannot PUT an entire node object at once unless you have permission to edit every field. If you don't have that permission, there is no situation in which PUT can be legal since PUT cannot update only some fields. It must be the whole object.

By contrast, PATCH and POST do not have these issues. POST's semantics are, largely, "you define it". We can define handling of field permissions however we want, and can ignore any Link relations that are problematic to process. PATCH, while less widely used, naturally allows for only partial updates and therefore makes field permissioning much easier to support.

But why not just use raw application/json for PUT instead? We considered that, and it's apparently a not uncommon practice in the HAL world, but realized that in order to do it properly given Drupal's highly inter-connected data model we would need to still include some portions of the HAL structure anyway, at which point we may as well just use HAL, at which point we're right back at dealing with PUT.

So, the REST team has concluded that PUT is simply more trouble than it's worth at this point. We therefore propose to drop PUT support from the rest.module entirely and rely on POST and PATCH for write operations.

That does not, of course, in any way preclude someone from further developing the PUT question in contrib. Nor does it preclude someone from offering their own non-rest.module-based or non-entity-based Hypermedia API through Drupal that supports PUT. All of the routing plumbing is there to support it if someone wants. This applies only to the rest.module as it appears in core.

We're pretty well sold on cutting our losses on PUT, but I will leave this discussion open until 4 March in case someone can offer a strong alternative.

Comments

While I'm sure this will be a

dsnopek's picture

While I'm sure this will be a little disappointing to some, I'd prefer that the rest.module in core at the very least be correct. I think you guys made the right decision!

PUT support optional for contrib

klausi's picture

To clarify this further: we are talking about dropping PUT for the entity resource plugin in REST module. The architecture of the module still allows other resource plugins that might be provided by Drupal 8 contributed modules to implement the put() method to do stuff on PUT.

But for the default core entity implementation it just creates headaches (as Crell explained), so we will rely on PATCH requests to update entities.

For PUT, however, it becomes

Owen Barton's picture

For PUT, however, it becomes more complicated because the PUT would be required to include exactly the Link relations that we would then get back on a GET. That's not always feasiable, especially given how inter-related the Links are in Drupal.

Is there a more detailed discussion of this issue somewhere? I am unclear if the issue is translating the URI to an entity reference, or the metadata around the link itself, or something else.

you can't have any pudding...

bendiy's picture

Crell,

Thanks for the update. PATCH can meet most of the needs of PUT and avoid these complications. I believe you could send the whole body in a PATCH if you really need to update the whole entity and still be RESTful.

You identified two issues with PUT:

  1. Complex Link Relations
  2. HAL [...] has a standard way of handling links to other entities and for embedding entities with the main object for improved performance.

    PUT would be required to include exactly the Link relations that we would then get back on a GET.

    I'm a little confused by this. Is a GET going to embed the author entity on a node or just include a link? A PUT should include the node entity's body with links to the author or taxonomy terms. PUT should not have the author and taxonomy data embedded in the node entity's body, just links. In most use cases, you will GET the entity, edit it and then PUT it. You will already have all the links in the GET and can change them if needed in the PUT.

    Is the problem that the GET is going to embed automatically, so you will not get just the links, but the embedded data? If that's the case, you could add an embed flag on the header. GET with "embed=false" and then PUT with "embed = false". That can allow you to GET and PUT with just the links. However, I don't like the idea of embedding automatically. You can get decent performance by setting up caching correctly and avoid multiple round trips.

  3. Field Permissions
  4. I don't see any issue here. That doesn't sound like reason enough to skip PUT. If there is a field that the user does not have permission to edit, just reply with an error code, 401 Unauthorized. I would expect a standard configuration to be making PUT's as a privileged user. If they are not a privileged user, throw an error that tells them why.

Entities with embedding are

Grayside's picture

Entities with embedding are composites, and composites are always fussy on write. This proposal makes sense, though the param to toggle embedding seems strange.

.

mradcliffe's picture

Thank you for the explanation, Crell. I wish we could implement PUT, but if we have to implement the strict Restful approach then properties would need to be resources as well.

(begin whining)
In my honest opinion, I think the IETF did not need to constrain PUT as it really sacrifices ease-of-use for semantics, and it I get a warm feeling whenever anyone ignores it in their implementation.

Double post

Grayside's picture

Double post

Idempotence

ctrahey's picture

Couple thoughts:

  1. This discussion has not mentioned the key purpose of PUT: Idempotence.

HTTP 1.1 spec defines PUT as idompotent. RFC 5789 (PATCH) declares: "PATCH is neither safe nor idempotent". I think this alone should be a major factor in your decision, and I recommend it be reversed perhaps for this reason alone.

  1. There seems to be confusion in the community between a resource and a representation, and if it costs us decisions like this, then we need to clear it up. A key part of REST is decoupling a resource and it's representations, and it is the representations which get transferred. This is key because if there is enough metadata included in the representations for both client and server to understand the requests/responses, then there is no need to have a representation perfectly mirror the stored resource (or the representation returned by a GET request). Here's a simple example: a 'last modified' field. This is clearly something may not be knowable by a client when PUTing, but will perhaps be part of the GET'd representation.

  2. The community around REST will write clients to use PUT. Let's take this seriously.

In short: I don't know where these two statements come from (can you explain?):

Specifically, the requirement that PUT's body be the value to be set at that URI, and that would then be returned by a GET to that URI

...because the PUT would be required to include exactly the Link relations that we would then get back on a GET

As soon as you enable field-level edit restrictions, you cannot PUT an entire node object at once unless you have permission to edit every field.

PUT is often implemented

linclark's picture

PUT is often implemented incorrectly. The team decided that it wasn't worth the effort for us to ensure we were using it correctly.

This is key because if there is enough metadata included in the representations for both client and server to understand the requests/responses, then there is no need to have a representation perfectly mirror the stored resource (or the representation returned by a GET request).

When you talk about an understanding between client and server, you either mean out-of-band (big REST no no) or you mean defined in the media type (which cannot alter semantics of a method). See What's the justification behind disallowing partial PUT? Note the answer from Jan Algermissen, who has authored specifications in this space.

Feel free to implement PUT however you want (standard or non-standard) in your own contrib module though. This system is easily extended.

Web Services and Context Core Initiative

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: