[go: nahoru, domu]

mark nottingham

Why POST is Special

Sunday, 10 October 2004

Web Services HTTP APIs

In a recent post, Don gave his take on the enlightening nature of WS-Transfer:

Honestly, WS-Transfer has been in the oven for quite a while. It’s been interesting to see people’s reaction to it.

Stage 1. What good is this?

Stage 2. Ahh, the idea of uniform application protocols seems pretty useful.

Stage 3. Wow, I can model my entire universe using less verbs/operations than I have fingers on one hand. Why would I ever author another WSDL portType?

Stage 4. Ahh, the idea of uniform application protocols seems pretty useful. Specific protocols seem useful too, especially when I find myself overloading PUT to mean “reboot the machine and submit an audit record to my management log.”

I can’t wait to watch the folks outside of building 42 go through the same four stages…

This is interesting. he’s basically saying that the majority of the world’s problems can be solved by a handful of methods (which people might call “REST” or “CRUD”), and that service-specific protocol semantics are only necessary in exceptional cases. In HTTP, most of those cases fit into POST (I think his fingers slipped when he typed PUT).

In this, Don’s showing that he’s not that far removed from the RESTifarian crowd. Considering who he is, this isn’t a small admission. It does beg the question, however, of what the difference between “specific protocols” and POST are; after all, I can already reboot my router, transfer money and do lots of other scary, real-world things with POST.

The More Things Change…

Let’s take Don’s example. Here’s what it might look like as an HTTP request;

POST /power_switch HTTP/1.1
Host: dons-machine
Content-Type: application/mmf+xml
Content-Length: …

<mmf xmlns=”http://www.gotdotnet.com/team/dbox/mmf/1.0”\>
  <instruction type=”reboot”/>
  <instruction type=”audit”>
  <audit-target>don’s mgmt log</audit-target>
  </instruction>
</mmf>

(if you can’t tell, I’m making this up as I go along, for purposes of illustration. Don’t use this language to control spacecraft, nuclear power plants or other mission-critical applications.)

If we ignore the syntactic details and the protocol-specific goo for getting it across the wire (like “HTTP/1.1” and “Content-Length”), the interesting information here can be split roughly into a triple of method, URI, content:

(“POST”, “dons-machine/power_switch”, appdata)

which, using a good HTTP client API, might look like this:

web = http.Dict()
MachineStatus = web.POST(‘dons-machine/power_switch’, appdata)

Don, OTOH, would like* a specific method for something so important, perhaps something that would boil down to:

web_service = ws.service('dons-machine')
MachineStatus = web_service.RebootAndAuditMe(appdata)

From the standpoint of interface semantics, the difference here is really just one between saying POST machineMgmtFormat and MANAGEMACHINE. In the uniform approach, the service-specific semantics are pushed down into the type (media type) and content (entity body) of the data. In the specific approach, they’re surfaced in the interface itself.

This isn’t a big difference. In fact, I’d wager we could easily redesign HTTP to get rid of POST — e.g., declare that those methods whose names are lowercase are resource-specific, while GET, PUT, DELETE and the rest of the uniform crowd would remain uppercase — and not lose anything. Intermediaries couldn’t take advantage of resource-specific semantics in either case, but they’d be functionally equivalent. What’s the big deal, then?

Protocol Engineering is Social Engineering

While there isn’t much technical difference between these approaches, there’s a big gap in how people can use them.

In HTTP, creating new methods is expensive, while creating new URIs is very, very cheap. OTOH, Web services makes creating new methods cheap, while making the creation of new URIs expensive. This is not because either approach is technically limited; it’s due to the design of both the specifications — like WSDL — and the toolkits that people use.

Therefore, a uniform solution is likely to have a large number of resources, with a few methods each, while a more specific one is likely to have a small number of resources (if not one) with a larger number of methods.

If uniform interfaces encourage people to overload methods, the corollary is that specific interfaces encourages people to overload resources; instead of spreading your application out to get the maximum benefit of those uniform semantics (e.g., GETting the current state of your machine’s power switch), you tend to pile everything onto one poor resource.

For example, take a look at WS-MetadataExchange. MEX is the first spec to use WS-Transfer, and it can’t help but define a GetMetadata method to go along with Get, instead of splitting things up into separate resources.

People may be willing and able to make the right choice as to when to have uniform and specific interfaces. Maybe I’m being a bit cynical, but people also tend to go POSTal pretty quickly, and I’d wager that opening up the field won’t help; most people will skip Don’s step three straight to step four, and only then go back and sprinkle a few uniform semantics onto their applications. Unfortunately for them, it’ll be too late; they’ll be locked into a design that has a paucity of endpoints and not enough meat to hang those uniform semantics onto.


11 Comments

Mark Baker said:

Interesting post Mark, but “In the uniform approach, the service-specific semantics are pushed down into the type” isn’t true; interface semantics and data semantics are (or should be) orthogonal due to layering. Your example isn’t using POST semantics.

I’m not sure what to make of “reboot and audit”, i.e. whether “audit” is an implementation detail or if there’s an actual expectation by the client that it will be performed, but there’s ways to do both uniformly I believe.

Saturday, October 9 2004 at 6:37 AM

Patrick Logan said:

“Other times, it allows you to do something that you cannot resource-model at all, maybe like reboot.”

A reboot seems to be just a little less tangible than some other more familiar web side effects, such as a sales transaction.

If you sell a product, the real resource is the tangible product that is delivered. The sales database records that fact, and the result from the HTTP method is a representation of that database record.

If you reboot a machine, the real resource is the tangible machine that is rebooted. The system log records that fact, and the result from the HTTP method is a representation of that system log.

I don’t think too much squinting is necessary to see the similarities between these two side effects.

Saturday, October 9 2004 at 9:12 AM

Mark Baker said:

One way to do a reboot RESTfully would be to PUT “off” then PUT “on” (so long as the Web server is separately powered, of course 8-). Another would be to POST, say, “10” (i.e. a shutdown timer - but it could be an empty document too) to a power managing resource.

I think we must be disconnected regarding layering, because I don’t know anybody who’d want their data to interfere with their interface unless there was no choice, no? I certainly agree with you that POST is sometimes used in such a manner, and also that it’s actually the appropriate method to use when this is what you have to do. But if I didn’t have to, then I wouldn’t, since I want my system to inherit all the properties induced by the interface constraint … Is that strict? I suppose, but I think it quite practical.

I don’t think we disagree by much. Perhaps it’s just a matter of degree, with me thinking that uniform semantics suffice for somewhere around 95% of all the things you might need to coordinate over a network using documents as messages, while others (probably DonB and DaveO - how about you?) might think that number was closer to, what, 30 or 40%? (just guessing, of course)

It’s really encouraging that the discussion has gotten this far anyhow, especially if the disagreement can be boiled down to this integer metric. It was just a couple of years ago that some super smart Web services proponents were telling me that REST was just for human-to-machine communications!!

Saturday, October 9 2004 at 9:23 AM

Mark Baker said:

Savas, in your example I think you need to be clear about what “mgmt:reboot-request” is, because many might interpret that as an operation name and therefore RPC. If it was a “type”, then that would be better, but should therefore be optional; i.e. the outermost element could just be “foo:Description” and it would still work (see my ducktyping blog entry last week).

BTW, my “human” comments weren’t about you, so no worries. They know who they are. 8-)

Sunday, October 10 2004 at 6:07 AM

Robert Sayre said:

“So, putting aside auditing, how to you model something like ‘reboot’ RESTfully? How do you push it up into a URI? http://dons-machine/rebooter?”

http://dons-machine/power

DELETE turns it off PUT turns it on, or reboots it if it’s already there

You can stick boot params in the PUT body, then you’ll know how it was started when you GET it later on.

Is that crazy?

Sunday, October 10 2004 at 8:09 AM

Ken MacLeod said:

In Sys-V style Unix land, reboot is modelled as a run-state. In Linux convention, run state 3 is “multiuser, networked”, 0 is “halt”, and 6 is “reboot”.

http://dons-machine/run-state

PUTting the value of 6 would reboot.

Sunday, October 10 2004 at 8:25 AM

Savas Parastatidis said:

Hey Mark,

Interesting post!

What’s wrong with a message that carries the following document:

<mgmt:reboot-request> <mgmt:when>10secs</mgmt:when> </mgmt:reboot-request>

in a trully service-oriented manner. The machine is not exposed as a resource. Instead, the machine hosts a service which can receive messages of the above form. More importantly, the same message can be transported over any underlying protocol. Furthermore, one can imagine a single service that receives messages similar to the above:

<mgmt:reboot-request> <mgmt:machine>Mark Baker’s machine :-)</mgmt:machine> <mgmt:when>now</mgmt:when> </mgmt:reboot-request>

All you need to do the above is have the uniform semantics of a single operation which receives and processes the messages.

Finally, a comment to Mark Baker’s entry above. Mark, I don’t know who you have in mind when you say that WS folks have been saying that REST is about human-to-computer interaction but I feel that I need to clarify my personal position since I have said something similar in the past and continue to say it :-) I believe that the Web really works because is about human-to-computer interaction and it happens that the Web is built on top of REST. I feel that for computer-to-computer we need more. REST is great if one wishes to build around the concept of resource-orientation (the Web is full of resources) but not if the architecture is service and message-oriented. But I think we agree to disagree on this one :-)

.savas.

Sunday, October 10 2004 at 10:02 AM

Savas Parastatidis said:

MarkB,

Applying the idea of the uniform semantics of the ProcessMessage operation into the WS realm means that one can always send a SOAP message for processing to a WS. You can send an empty SOAP message to any WS. It doesn’t really mean that something will happen as a result.

I was very careful to not use “mgmt:reboot” :-). The “reboot-request” element is just a root of a document. No verb or RPC semantics implied. A management specification defines the formats of the documents in the “mgmt” namespace and the semantics of processing those docs. Definitely not RPC semantics there :-)

.savas.

Monday, October 11 2004 at 1:08 AM

Erik Johnson said:

Since I’m not really saving anything, I guess it’s jut as correct to GET a reboot.

WS-Transfer has a weirdness I didn’t expect to find: The PUT operation is also a GET operation. You are supposed to return the new representation of the resource after the changes are applied UNLESS the new representation is identicle to the representation in the payload of the PUT operation. I’m not sure if REST has a similar rule.

The only way this can really work is if the resource representation received from the GET operation is the same as that used in the PUT operation. But that’s rarely the case, especially for activity data like orders: the order total is present when you retrieve an order, but you are never allowed to send it. If you prefer to use UpdateGrams or similar styles, how is the service supposed to know if the caller’s view of the current state matches the server? I guess you have to just include version info – which absolutely implies a specific architecture.

So then I started to question whether the WS-Transfer authors intended the spec for use in some (unsaid) subset of use cases. Maybe they mean “resource” in a very strict sense (non-activity data)? Maybe it was just factored out of MEX?

Anyway, I think this is all still knocking at the door of representing application semantics in service descriptions. For all this thinking, we have only been able to settle on GET and PUT (plus DELETE and CREATE). Even then, many disagree that this helps the situation!

Monday, October 11 2004 at 2:30 AM

Patrick Logan said:

I think the fundamental problem is we need to distinguish between verbs for coordination and verbs for action. Verbs for coordination should be a small fixed set that describe the putting of messages into (and taking of messages from) a shared space.

Verbs for action (e.g. to reboot some machine at some time) by their very nature will be larger and less stable. Certain domains have more stable sets of verbs than others, and many others would be expected to stabilize with increased use.

More along these lines at http://patricklogan.blogspot.com/2004/10/verbs-for-doing-one-thing-well.html

Monday, October 11 2004 at 3:15 AM