Why PATCH is Good for Your HTTP API

来源:互联网 发布:淘宝上正品的coach代购 编辑:程序博客网 时间:2024/05/20 09:26

https://www.mnot.net/blog/2012/09/05/patch

A common problem for APIs is partial update; when the client wants to change just one part of a resource’s state. For example, imagine that you’ve got a JSON representation of your widget resource that looks like:

{  "name": "abc123",  "colour": "blue",  "count": 4}

and you want to update the “count” member’s value to “5”.

Now, you could just PUT the entire thing back with the updated value, but that requires a recent GET of its state, can get heavyweight (especially for mobile clients), and requires use of pre conditional requests to avoid “lost updates.”

For these and other reasons, many APIs define a convention for POSTing to resources that allows partial updates. E.g.,

POST /widgets/abc123?action=incrementCount

It seems simple, at first. However, this is a POST, so it doesn’t have any generic semantics; the server and client side developers have to write application-specific code to support it, then do QA on it, debug the corner cases, and eventually rewrite the API to fix the problems they inevitably find (partial updates can get subtle). Once you get a lot of these hanging around, it’s a pain.

Some folks go down the path of writing a more generic query parameter conventions for talking about the structure of a document in an API; this can be seen in the CIMI specification’s “$select” mechanism, as well as XCAP:

Assume that this document has a document URI of"http://xcap.example.com/test/users/sip:joe@example.com/index",where "test" is the application usage. This applicationusage defines a default document namespace of"urn:test:default-namespace". The XCAP URI:http://xcap.example.com/test/users/sip:joe@example.com/index/~~/foo/a:bar/b:baz?xmlns(a=urn:test:namespace1-uri)xmlns(b=urn:test:namespace1-uri)will select the first <baz> child element of the<bar> element in the document.

These approaches still bring a lot of complexity to the API; what used to be a fairly simple URI space is now made much deeper, more complex, and brittle. There’s a lot more application-specific code to write, document, debug, understand and use. Blech.

Enter PATCH

A better way is to use the HTTP PATCH method. Rather than putting the semantics into the URI, or into an application-specific POST, we put them into a generic* request format — you can think of this as a diff — which is activated by the PATCH method.

This has a bunch of advantages. Since it’s a generic format, you can write server- and client-side code once, and share it among a number of applications. You can do QA once, and make sure you get it right. Furthermore, your API will become less complex, because it has less URI conventions, leading to more flexibility and making it easier to approach for new developers. 

Using this approach will also make caches operate more correctly; since modifications to a resource will “travel” through its URL, rather than some other one, the right stored responses will get invalidated.

One final benefit; PATCH is atomic, which means that if you need to do some complex changes to the state of a resource, you can do it with confidence. It also means that you can create synthetic resources and PATCH them if you need to orchestrate changes to the state of several resources.

So, what does PATCH look like?

PATCH with JSON

We’ve been working on a PATCH format for JSON in the IETF APPSAWG. Originally proposed by Paul Bryan, I’ve been editing it so that we can get it out the door. A request to patch a JSON document looks like this:

PATCH /widgets/abc123 HTTP/1.1Host: api.example.comContent-Length: ...Content-Type: application/json-patch[  {"replace": "/count", "value": 5}]

Easy to understand, and even write by hand. If it succeeds, the response can be as simple as:

HTTP/1.1 200 OKContent-Type: text/plainConnection: closeYour patch succeeded. Yay! 

If you want to get fancy, you can also use James Snell's Prefer mechanism to tell the server your preferences for the response, along with content negotiation for its format, of course.

PATCH with XML

The closest thing we have to json-patch for XML is RFC5261, but the media type it defines is application specific, and it’s somewhat complex to boot. I’m not aware of any Open Source implementations, so I’m not sure if it’d be best to start here, or just come up with something new. Thoughts? 

Or, just don’t use XML. 

Supporting PATCH in Frameworks

JSON Patch is almost ready for Working Group Last Call, which means it’ll be an RFC sometime soon. I want to get some implementation experience with it before then (there are already a few people dipping their toes in), and I think both client-side and server-side frameworks can make PATCH even easier. 

For example, on the client side, it should be easy to “record” modifications to a JSON object and then serialise them as a JSON Patch document. On the server side, the persistence layer can do lots to make PATCH automatic, as well as enabling things like enforcing access rights, triggering side effects, etc. 

If you’re aware of any JSON Patch implementation, have ideas, or want to help (we need to build a test suite), please comment below, or contact me.

* Some people have misinterpreted the PATCH specification as saying that any media type (e.g., application/json) can be used as a PATCH format. This is missing the point; if you do that, you’re not getting any benefit from shared code, etc. and you might as well use POST.


HTTP Verb: POST, PUT和PATCH的区别

http://www.zhex.me/blog/2013/01/07/http-verb-different-between-post-put-and-patch/

一直一来对HTTP Verb的认识是 GET, POST, PUT, DELETE。 随着Rails 4的到,又让我认识了 PATCH。接踵而来的问题当然是 POST, PUT 和 PATCH 的区别了。

先来看看官方定义:

  • POST to create a new resource when the client cannot predict the identity on the origin server (think a new order)
  • PUT to override the definition of a specified resource with what is passed in from the client
  • PATCH to override a portion of a specified resource in a predictable and effectively transactional way (if the entire patch cannot be performed, the server should not do any part of it)

显而易见, post表示新增, put可以认为是完整替换, 而patch的作用是部分替换。

1234567
# POST /itemsdef create  @item = Item.new  @item.attributes = { :name => params[:name],                      :image => params[:image] }  @item.saveend

这里我们利用 POST 创建了一个服务器端从来就没有的 item。

12345678910111213
# PUT /items/{id}def replace  @item = Item.find_by_id(params[:id])  unless @item  # if @item.nil?      @item = Item.new      @item.id = params[:id]  end  @item.attributes = { :name => params[:name],                       :image => params[:image] }  @item.saveend

如果在上面的代码中,我们没有传递 :image, 那更新后 :image 将会是 nil

123456
# PATCH /items/{id}def patch  @item = Item.find(params[:id])  @item.attributes = params.slice(:name, :image)  @item.saveend

上面的代码中,我们只更新:name, :image, 其他部分内容不会被更新,也不会被滞空。


0 0
原创粉丝点击