- 浏览: 30711 次
- 性别:
- 来自: 北京
最新评论
本文对RESTful HTTP的基础原理做了一个概览,探讨了开发者在设计RESTful HTTP应用时所面临的典型问题,展示了如何在实践中应用REST架构风格,描述了常用的URI命名方法,讨论了如何使用统一接口进行资源交互,何时使用PUT或POST以及如何支持非CURD操作等。
相关厂商内容
还记得Delphi么?Embarcadero携ER/Studio、RAD Studio XE和HTML5 Builder重装上阵
QCon成都2013大会准备就绪,InfoQ用户免费参加(4.11/12两天)
天猫百度360京东捕鱼达人航班管家等讲师确认参加QCon成都2013
虚拟化技术自动化测试环境部署:EMC高级软件工程师邵育亮
持续集成与持续交付专家乔梁:近十年实战项目案例之深度剖析
相关赞助商
QCon全球软件开发大会2013,北京国际会议中心,4月25~27日,最后报名中详情请点击!
REST是一种风格,而不是标准。因为既没有REST RFC,也没有REST协议规范或者类似的规定。REST架构是Roy Fielding(他也是HTTP和URI规范的主要作者之一)在一篇论文中描述的。像REST这样的架构风格通常会定义一组高层决定让应用程序去实现。所有实现了某种特定架构风格的应用程序,都使用相同的模式,也用相同的方式使用别的架构元素,如缓存,分布式策略等。Roy Fielding把REST定义成一种架构风格,其目标是“使延迟和网络交互最小化,同时使组件实现的独立性和扩展性最大化”
虽然REST受Web技术的影响很深,但是理论上REST架构风格并非绑定在HTTP上。然而,HTTP是唯一与REST相关的实例。基于该原因,本文描述了通过HTTP实现的REST,通常它也被称为RESTful HTTP。
REST并没有创造新的技术,组件或服务,隐藏在RESTful HTTP背后的理念是使用Web的现有特征和能力。RESTful HTTP定义了如何更好地使用现有Web标准中的一些准则和约束。
资源
资源是REST中最关键的抽象概念,它们是能够被远程访问的应用程序对象。一个资源就是一个标识单位,任何可以被访问或被远程操纵的东西都可能是一个资源。资源可以是静态的,也就是该资源的状态永远不会改变。相反,某些资源的状态可能随着时间推移呈现很大的可变性。这两种类型的资源都是有效的。
图1中所显示的这些类都能被很容易地映射成资源。面向对象设计者不容易理解把实体类(如Hotel或者Room)映射成资源,同样他们也不太理解从控制类(如coordination,事务和某个类的控制类)到资源的映射。
图1:分析模型的例子
分析模型是识别资源的一个非常好的“切入点”。然而,并非只能进行1对1映射,比如, <Hotel>.listOccupancy()操作也可以被建模成资源。此外,也有可能某些资源只表示实体的某一部分。资源设计的主要驱动力是网络因素而不是对象模型。
任何重要的资源都应该能够通过一个唯一的标识被访问。RESTful HTTP使用URI来识别资源。URI提供了Web通用的识别机制,它包含了客户端直接与被引用的资源进行交互时需要的所有信息。
如何命名资源标识?
虽然RESTful HTTP并没有明确指出如何构造一个URI路径,但实际上经常被使用的是一些特定的命名模式。URI命名模式有助于应用程序调试和跟踪,通常一个URI包含资源类型及其后面用于定位特定资源的标识。这样的URI不包括指定业务操作的动词(verb),而只用于定位资源。图中a1给出了Hotel资源的一个示例,同一个Hotel资源也可以通过URI(a2)访问。同一资源可以被多个URI引用。
(a1) http://localhost/hotel/656bcee2-28d2-404b-891b
(a2) http://127.0.0.1/hotel/656bcee2-28d2-404b-891b
(b) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4
(c) http://localhost/hotel/656bcee2-28d2-404b-891b/Reservation/15
(d) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4/Reservation/15
(e) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4/Reservation/15v7
(f) http://localhost/hotel/656bcee2-28d2-404b-891bv12
图2:资源寻址的例子
URI也可以被资源用来在资源表示(representation)之间建立关联。例如,Hotel表示通过URI去引用已分配的Room资源,而不是使用普通的RoomID。使用普通的ID会强制调用者通过对资源的访问去构造URI,而调用者如果没有主机名和基础URI路径等上下文信息是无法访问到该资源的。
超链接常被客户端用于资源导航。RESTful API是超文本驱动的,这表示客户端通过获得一个Hotel表示,就能够导航到已分配的Room表示和Reservation表示。
在实践中,图1所示的这些类经常被映射成某种业务对象,这意味着在业务对象的整个生命周期中URI将保持不变。如果要创建一个新资源,则要为之分配一个新的URI。而一旦这个新资源被删除,相应的URI则跟着失效。如图2中的(a),(b),(c)和(d)就是这种标识的例子。另一方面,URI也可以用来引用资源快照,比如(e)和(f)就是对这类快照的引用,其URI中包含了一个版本标识。
URI还可以定位子资源,如示例中的(b),(c),(d)和(e)。通常,被聚集的对象会被映射成子资源,如Room是被Hotel聚集的。被聚集的对象通常没有自己的生命周期,如果它的父对象被删除,所有的被聚集对象也跟着被删除。
然而,如果一个子资源可以从一个父资源移动到另一个父对象, 那么在它的URI中就不应该包含其父资源的标识。比如图1中的Reservation资源,它就可以被分配给另一个Room资源。如果一个Reservation资源的URI包含了其父资源Room的标识,如(d)所示,则当Room实例标识改变时,如果该Reservation资源又被另一个资源引用的话,这就会出问题。为了避免无效的URI,Reservation应该通过(c)这样的方式进行寻址。
通常,资源的URI是由服务器控制的。客户端访问资源时并不需要理解资源的URI命名空间结构。比如,使用(c)和(d)两个URI结构对客户端而言具有效果相同。
统一资源接口
为了简化整体系统架构,REST架构风格包含了统一接口的概念。统一接口包含一组受限的良定义的操作,由它们进行资源的访问和操作。不论什么资源,都使用相同的接口。客户端与Hotel,Room或CreditScore等资源交互时使用的接口是一样的。统一接口独立于资源的URI,并且也不需要类似IDL的文件去描述可用的操作。
RESTful HTTP的接口非常流行且广为使用。它包含标准的HTTP方法如GET,PUT和POST(浏览器使用它发出请求并提取页面)。不幸的是,很多开发者认为实现RESTful应用就是用一种直接使用HTTP的方式,这种理解是错误的。举个例子,HTTP方法的实现必须要遵循HTTP规范的,而通过GET方法创建或修改对象是不遵守HTTP规范的。
应用统一接口
关于何时以及如何使用不同的HTTP动词(verb),在Fielding的论文中没有任何表格、列表或其他方式的描述。对于大部分方法,如GET或 DELETE,通过阅读HTTP规范就能清楚其含义,而对于POST和部分更新,就不那么容易了。在实践中,对资源进行部分更新有好几种方法,下文将有详细介绍。
表1列出了大部分重要的方法GET,DELETE,PUT和POST的典型用法:
重要
方法
典型用法
典型状态码
安全?
幂等?
GET
- 获取表示
- 变更时获取表示(缓存)
200(OK) - 表示已在响应中发出
204(无内容) - 资源有空表示
301(Moved Permanently) - 资源的URI已被更新
303(See Other) - 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
是
是
DELETE
- 删除资源
200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求t
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
否
是
PUT
- 用客户端管理的实例号创建一个资源
- 通过替换的方式更新资源
- 如果未被修改,则更新资源(乐观锁)
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示/p>
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
否
是
POST
- 使用服务端管理的(自动产生)的实例号创建资源
- 创建子资源
- 部分更新资源
- 如果没有被修改,则不过更新资源(乐观锁)
200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
否
否
表1:统一接口示例
表示
对资源的操纵永远是通过其表示实现的。资源可能永远不会在网络中传输,相反,传输的是资源的表示。资源的表示包括数据和描述数据的元数据,例如,HTTP头“Content-Type” 就是这样一个元数据属性。
图3展示了如何使用Java获取表示。该例程使用了Java HTTP库xLightweb中的HttpClient类,这个库由作者本人维护。
HttpClient httpClient = new HttpClient();
IHttpRequest request = new GetRequest(centralHotelURI);
IHttpResponse response = httpClient.call(request);
图3:获取表示的Java例程
通过调用HTTP客户端的call方法,一个访问Hotel资源表示的HTTP请求就被发送出去。返回的表示如图4所示,它也包含了用于指示实体主体的多媒体类型的Content-Type头。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 277
Content-Type: application/x-www-form-urlencoded
classification=Comfort&name=Central&RoomURI=http%3A%2F%2Flocalhost%2Fhotel%2F
656bcee2-28d2-404b-891b%2FRoom%2F2&RoomURI=http%3A%2F%2Flocalhost%2Fhotel%2F6
56bcee2-28d2-404b-891b%2FRoom%2F1
图4:RESTful HTTP交互
如何支持特定表示?
为了避免传输很大的数据集,有时应该接收表示属性一个子集。在实现时,用于指定部分属性的一种方式就是支持对指定属性的寻址,如图5所示。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 26
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort
图5:属性过滤
图5中所示的GET调用只请求了一个属性(classification),如果要请求多个属性,所请求的属性要用逗号隔开,如图6所示。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification,name HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 43
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort&name=Central
图6:多属性过滤
确定所需属性的另一种方法是使用查询参数,通过它列出所请求的属性,如图7所示。查询参数将用于定义查询条件以及更复杂的过滤或查询准则。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b?reqAttr=classification&reqAttr=name HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 43
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort&name=Central
图7:查询字符串
在上述例子中,服务器总是返回以application/x-www-form-urlencoded编码的媒体类型的表示。该媒体类型将实体编码成键值对列表。键值方法理解起来很容易,但缺点是,它不适用与更加复杂的数据结构。此外,这种媒体类型不支持标量数据类型的绑定,如Integer,Boolean,Date等。基于这个原因,通常使用XML,JSON或Atom来表征资源(JSON也没有定义Data类型的绑定)
HttpClient httpClient = new HttpClient();
IHttpRequest request = new GetRequest(centralHotelURI);
request.setHeader("Accept", "application/json");
IHttpResponse response = httpClient.call(request);
String jsonString = response.getBlockingBody().readString();
JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON(jsonString);
HotelHotel= (Hotel) JSONObject.toBean(jsonObject, Hotel.class);
图8:请求JSON表示
通过设置“Accept”请求头,客户端就可以请求指定的表示编码。图8展示了如何对application/json类型的表示的请求。JSONlib将把图9中显示的返回响应消息映射成Hotel bean。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/json
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 263
Content-Type: application/json; charset=utf-8
{"classification":"Comfort",
"name":"Central",
"RoomURI":["http://localhost/hotel/656bcee2-28d2-404b-891b/Room/1",
"http://localhost/hotel/656bcee2-28d2-404b-891b/Room/2"]}
图9:JSON表示
如何报告错误?
当服务器不支持所请求的表示时怎么办?图10展示了一个请求XML表示资源的HTTP交互,若服务器不支持这种表示,它将返回一个HTTP 406响应,表示拒绝处理该请求。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: text/xml
RESPONSE:
HTTP/1.1 406 No match for accept header
Server: xLightweb/2.6
Content-Length: 1468
Content-Type: text/html; charset=iso-8859-1
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 406 No match for accept header</title>
</head>
<body>
<h2>HTTP ERROR: 406</h2><pre>No match for accept header</pre>
...
</body>
</html>
图10:不支持的表示
RESTful HTTP服务端程序必须根据HTTP规范返回状态码。状态码的第一个数字标识返回类型,1xx表示临时响应,2xx表示成功响应 ,3xx代表转发,4xx表示客户端错误,5xx代表服务端错误。使用错误的响应码,或者总返回200响应,并在消息主体中包含特定应用程序的响应,这两种做法都是不好的实践。
客户代理和中介也要分析返回码。例如,xLightweb HttpClient默认会把持久的HTTP连接保存在连接池中,当一个HTTP交互完成时,持久化HTTP连接就应返回到内部连接池已备重用。而只有完好的连接才能被放回连接池,比如,若返回码是5xx,那该连接就不会重回连接池了。
有时某些特定的客户端要求更简洁的返回码。一种方法是增加一个HTTP头“X-Header”,用它来详细描述HTTP状态码。
REQUEST:
POST /Guest/ HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 94
Content-Type: application/x-www-form-urlencoded
zip=30314&lastName=Gump&street=42+Plantation+Street&firstName=Forest&country=US&
city=Baytown&state=LA
RESPONSE:
HTTP/1.1 400 Bad Request
Server: xLightweb/2.6
Content-Length: 55
Content-Type: text/plain; charset=utf-8
X-Enhanced-Status: BAD_ADDR_ZIP
AddressException: bad zip code 99566
图11:附加状态码
通常只有在进行编程问题诊断时才需要详细的错误码。尽管比起详细的错误码,HTTP状态码的描述性总是要差很多,但是在大多数情况下,它们对于客户端正确处理问题已经足够了。另一种方法是在响应主体中包含详细的错误码。
PUT还是POST?
较之流行的RPC方式,HTTP方法不仅仅在方法名上有所不同,而且HTTP方法中的某些属性(如幂等性,安全性等)也扮演着重要的角色。不同的HTTP方法的幂等性和安全性属性也是不同的。
HttpClient httpClient = new HttpClient();
String[] params = new String[] { "firstName=Forest",
"lastName=Gump",
"street=42 Plantation Street",
"zip=30314",
"city=Baytown",
"state=LA",
"country=US"};
IHttpRequest request = new PutRequest(gumpURI, params);
IHttpResponse response = httpClient.call(request);
图12:使用PUT方法
如图12和13所示,使用PUT操作来创建一个新的Guest资源。PUT方法将封装好的资源存放在Request-URI之下。该URI是由客户端决定的,当Request-URI指向某现存资源时,该资源将被新资源替换。基于该原因,PUT方法一般用于创建新资源或更新现有资源。然而,通过使用PUT,资源的整个状态都会被改变,若一个请求只需要修改zip域,它不得不包含该资源的其他域,如 firstName,city等。
REQUEST:
PUT Hotel/guest/bc45-9aa3-3f22d HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 94
Content-Type: application/x-www-form-urlencoded
zip=30314&lastName=Gump&street=42+Plantation+Street&firstName=Forest&country=US&
city=Baytown&state=LA
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 36
Content-Type: text/plain; charset=utf-8
Location: http://localhost/guest/bc45-9aa3-3f22d
The guest resource has been updated.
图13:HTTP PUT交互
PUT方法是幂等的,幂等性意味着对于一个成功执行的请求,不管其执行多少次,其结果都是一致的。也就是说,只要你愿意,你可以用PUT方法对Hotel资源进行任意次更新,其结果都一样。如果两个PUT方法同时发生,那么只有其中之一会赢得最后的胜利并决定资源的最终状态。删除操作也是幂等的,如果一个PUT方法和DELETE方法同时发生,那么资源或者被更新,或者被删除,而不可能停留在某个中间状态。
如果你不确定是PUT还是DELETE被成功执行,并且没有得到状态码409 (Conflict)或者 417 (Expectation Failed)的话,那么就重新执行一遍。而不需要附加的可靠性协议来避免重复请求,因为通常重复的请求不会有任何影响。
上述描述对于POST方法就不适用了,因为POST方法不是幂等的,若要两次执行同一个POST请求那就要注意了。POST方法所缺失的幂等性就解释了为什么当你每次重新发送POST请求时浏览器总是弹出警告。POST方法用于创建资源,而不需要由客户端指定实例id,图14展示了通过POST方法创建一个Hotel资源的HTTP交互过程。通常,客户端使用只包含基路径和资源类型名的URI来发送POST请求。
REQUEST:
POST /HotelHTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 35
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Accept: text/plain
classification=Comfort&name=Central
RESPONSE:
HTTP/1.1 201 Created
Server: xLightweb/2.6
Content-Length: 40
Content-Type: text/plain; charset=utf-8
Location: http://localhost/hotel/656bcee2-28d2-404b-891b
the Hotelresource has been created
图14:HTTP POST交互(创建)
POST方法也经常用于更新资源的部分内容,比如,如果我们要通过发送仅包含classification属性的PUT请求去更新Hotel资源的话,这就是违反HTTP的,但是用POST方法则没有问题。POST方法既不是幂等的,也不是安全的。图15展示了一个执行部分更新的POST方法。
REQUEST:
POST /hotel/0ae526f0-9c3d HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 19
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Accept: text/plain
classification=First+Class
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 52
Content-Type: text/plain; charset=utf-8
the Hotelresource has been updated (classification)
图15: HTTP POST交互 (更新)
还可以使用PATCH方法来进行部分更新,PATCH方法是对资源进行部分更新的一个特殊方法。一个PATCH请求包含一个补丁文档,它将应用于由Request-URI所指定的资源。然而PATCH的RFC规范还在草稿中。
使用HTTP缓存
为提高扩展性并降低服务端负载, RESTful的HTTP应用程序可以利用WEB基础设施的缓存机制。HTTP已经意识到缓存是WEB基础设施必不可少的一部分,比如,HTTP协议定义了专门的消息头来支持缓存。如果服务端设置了这个头,客户端(如HTTP客户端或Web缓存代理)就能够有效地支持缓存策略。
HttpClient httpClient = new HttpClient();
httpClient.setCacheMaxSizeKB(500000);
IHttpRequest request = new GetRequest(centralHotelURI + "/classification");
request.setHeader("Accept", "text/plain");
IHttpResponse response = httpClient.call(request);
String classification = response.getBlockingBody.readString();
// ... sometime later re-execute the request
response = httpClient.call(request);
classification = response.getBlockingBody.readString();
图16:客户端缓存交互
图16显示了一个重复的GET调用。通过设置最大缓存大小的值>0激活了HttpClient的缓存功能。如果响应消息中包含了刷新头,比如Expires或Cache-Control: max-age,该响应就会被HttpClient缓存。这些头指明了关联的表示可以保鲜的时间为多久。如果在一段时间内发出了相同的请求,那么HttpClient就会使用缓存为这些请求提供服务,而不需要重复进行网络调用。在网络上总共只有一次HTTP交互,如图17所示。诸如WEB代理之类的缓存中介也实现了相同的功能,而且该缓存还可以在不同客户端之间共享。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: text/plain
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Cache-Control: public, max-age=60
Content-Length: 26
Content-Type: text/plain; charset=utf-8
comfort
图17:包含过期头的HTTP响应
过期模型在静态资源上很好用,可是,对于动态资源(资源状态经常改变且无法预测)则不尽相同。HTTP通过验证头,如Last-Modified以及ETag来支持动态资源的缓存。与过期模型相比,验证模型没有节省网络调用。但是,当执行带条件的GET方法时它会对昂贵的操作节约网络传输,图 18(2.request)显示了带条件的GET操作,它带有一个额外的Last-Modified头,这个头包含了缓存对象最后修改日期。如果该资源未被更改,服务端将会返回一个304 (Not Modified) 响应。
1. REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/Reservation/1 HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
1. RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 252
Content-Type: application/x-www-form-urlencoded
Last-Modified: Mon, 01 Jun 2009 08:56:18 GMT
from=2009-06-01T09%3A49%3A09.718&to=2009-06-05T09%3A49%3A09.718&guestURI=
http%3A%2F%2Flocalhost%2Fguest%2Fbc45-9aa3-3f22d&RoomURI=http%3A%2F%2F
localhost%2Fhotel%2F656bcee2-28d2-404b-891b%2FRoom%2F1
2. REQUEST:
GET /hotel/0ae526f0-9c3d/Reservation/1 HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
If-Modified-Since: Mon, 01 Jun 2009 08:56:18 GMT
2. RESPONSE:
HTTP/1.1 304 Not Modified
Server: xLightweb/2.6
Last-Modified: Mon, 01 Jun 2009 08:56:18 GMT
图18:基于验证的缓存
不要在服务端存储应用状态
RESTful HTTP的交互必须是无状态的,这表明每一次请求要包含处理该请求所需的一切信息。客户端负责维护应用状态。RESTful服务端不需要在请求间保留应用状态,服务端负责维护资源状态而不是应用状态。服务端和中介能够理解独立的请求和响应。Web缓存代理拥有一切正确处理请求所需的信息并管理它的缓存。
这种无状态的方法是实现高扩展和高可用应用的基本原则。通常无状态使得每一个客户请求可以由不同的服务器来响应,当流量增加时,新的服务器可以加进来,而如果某个服务器失败,它也可以从集群中移除。若要了解关于负载均衡以及故障恢复方面的更详细信息,请参考这篇文章服务器负载均衡架构 。
对non-CRED操作的支持
开发者经常想了解如何将non-CRUD(Create-Read-Update-Delete)操作映射到资源。显然,Create、Read、Update和Delete等操作能够很容易地映射到资源的方法。然而, RESTful HTTP还不仅限于面向CRUD的应用。
图19: RESTful HTTP资源
就如图19所示的creditScoreCheck而言,它提供了一个non-CRUD操作creditScore(...),该操作接受一个address,计算出score并返回。这样的操作可以通过CreditScoreResource实现,该资源代表着计算的返回。图20展示了一个GET方法,它传入address,然后提取CreditScoreResource表示,查询参数被用来指定CreditScoreResource。GET方法是安全的,并且可缓存,所提它很适用于CreditScore Check的creditScore(...)方法的非功能性行为。计算的结果可以缓存一段时间,如图20所示,响应包含了一个缓存头,它通知客户端和中介执行响应缓存。
REQUEST:
GET /CreditScore/?zip=30314&lastName=Gump&street=42+Plantation+Street&
firstName=Forest&country=US&city=Baytown&state=LA HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
Cache-Control: public, no-transform, max-age=300
scorecard=Excellent&points=92
图20:Non-CRUD HTTP GET交互
上述例子还显示了GET方法的局限性。尽管HTTP规范并没有指定URL的最大长度,但是实际上客户端,中介以及服务端对URL的长度都有限制。基于此,通过GET的查询参数发送一个很大的实体可能会因为中介和服务器对URL长度的限制而失败。
另一解决方法是使用POST方法,如果作了设置,它也是可缓存的。如图21所示,第一个POST请求的结果是创建了一个虚拟资源CreditScoreResource。输入的address数据用text/card这个mime类型进行编码,在服务端计算得到score之后,它发回一个201(created)响应,该响应包含着所创建的CreditScoreResource资源的URI。 示例中还展示了如果进行了设定,POST响应也可以被缓存。通过一个GET请求就能够取到计算结果。GET响应也包含一个缓存控制头,如果客户端紧接着重新执行这两次请求,那么它们都可由缓存进行响应。
1. REQUEST:
POST /CreditScore/ HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 198
Content-Type: text/x-vcard
Accept: application/x-www-form-urlencoded
BEGIN:VCARD
VERSION:2.1
N:Gump;Forest;;;;
FN:Forest Gump
ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;US
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A30314 Baytown=0D=0ALA US
END:VCARD
1. RESPONSE:
HTTP/1.1 201 Created
Server: xLightweb/2.6
Cache-Control: public, no-transform, max-age=300
Content-Length: 40
Content-Type: text/plain; charset=utf-8
Location: http://localhost/CreditScore/l00000001-l0000005c
the credit score resource has been created
2. REQUEST:
GET /CreditScore/l00000001-l0000005c HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
2. RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
Cache-Control: public, no-transform, max-age=300
scorecard=Excellent&points=92
图21: Non-CRUD HTTP POST交互
还有其他不同的实现方式。比如不返回201响应,而返回301(Moved Permanently)转发响应。该响应缺省是可缓存的。其他避免二次请求的方法是在201响应中增加一个新创建的CreditScoreResource资源的表示。
总结
大多数SOA架构(如SOAP或CORBA)都试图映射如图1所示的类模型,或多或少是一对一的远程访问。通常,这些SOA架构的比较多地关注在编程语言对象的透明映射上,这种映射很容易理解,且易于跟踪。可是,它们把对分布性和扩展性等方面的关注排在第二位。
相反,REST架构风格的最主要驱动是分布性和扩展性。RESTful HTTP接口的设计是由网络因素而非编程语言的绑定驱动的。 RESTful HTTP也没有试图去封装很那些难隐藏的因素,如网络延迟,网络健壮性以及网络带宽等。
RESTful HTTP应用用一种直接的方式使用HTTP协议,而不需任何抽象层,也不存在REST指定的数据域,如错误域,安全令牌域等。RESTful HTTP应用只使用WEB的固有能力。设计RESTful HTTP的接口意味着远程结构的设计者必须在HTTP协议上进行思考。这通常增加了开发周期中额外步骤。
然而,RESTful HTTP使得应用程序实现具有高扩展性,更健壮。特别是为很大用户群提供Web应用的公司,如 WebMailing或SocialNetworking的应用就能从REST架构风格中获益。通常,这些应用要更快更高地扩展,而且,这些公司通常在一些低预算的基础设施(基于广泛使用的标准组件和软件之上)上运行应用。
关于作者
Gregor Roth,xLightweb HTTP库的作者。在United Internet组织担任软件架构师,该组织是最重要的欧洲因特网服务提供商,其产品有GMX, 1&1, and Web.de等。他感兴趣的领域包括软件和系统架构、企业架构管理、面向对象设计、分布式计算和开发方法论等。
========================================================================
原文网址:http://www.infoq.com/cn/articles/designing-restful-http-apps-roth
相关厂商内容
还记得Delphi么?Embarcadero携ER/Studio、RAD Studio XE和HTML5 Builder重装上阵
QCon成都2013大会准备就绪,InfoQ用户免费参加(4.11/12两天)
天猫百度360京东捕鱼达人航班管家等讲师确认参加QCon成都2013
虚拟化技术自动化测试环境部署:EMC高级软件工程师邵育亮
持续集成与持续交付专家乔梁:近十年实战项目案例之深度剖析
相关赞助商
QCon全球软件开发大会2013,北京国际会议中心,4月25~27日,最后报名中详情请点击!
REST是一种风格,而不是标准。因为既没有REST RFC,也没有REST协议规范或者类似的规定。REST架构是Roy Fielding(他也是HTTP和URI规范的主要作者之一)在一篇论文中描述的。像REST这样的架构风格通常会定义一组高层决定让应用程序去实现。所有实现了某种特定架构风格的应用程序,都使用相同的模式,也用相同的方式使用别的架构元素,如缓存,分布式策略等。Roy Fielding把REST定义成一种架构风格,其目标是“使延迟和网络交互最小化,同时使组件实现的独立性和扩展性最大化”
虽然REST受Web技术的影响很深,但是理论上REST架构风格并非绑定在HTTP上。然而,HTTP是唯一与REST相关的实例。基于该原因,本文描述了通过HTTP实现的REST,通常它也被称为RESTful HTTP。
REST并没有创造新的技术,组件或服务,隐藏在RESTful HTTP背后的理念是使用Web的现有特征和能力。RESTful HTTP定义了如何更好地使用现有Web标准中的一些准则和约束。
资源
资源是REST中最关键的抽象概念,它们是能够被远程访问的应用程序对象。一个资源就是一个标识单位,任何可以被访问或被远程操纵的东西都可能是一个资源。资源可以是静态的,也就是该资源的状态永远不会改变。相反,某些资源的状态可能随着时间推移呈现很大的可变性。这两种类型的资源都是有效的。
图1中所显示的这些类都能被很容易地映射成资源。面向对象设计者不容易理解把实体类(如Hotel或者Room)映射成资源,同样他们也不太理解从控制类(如coordination,事务和某个类的控制类)到资源的映射。
图1:分析模型的例子
分析模型是识别资源的一个非常好的“切入点”。然而,并非只能进行1对1映射,比如, <Hotel>.listOccupancy()操作也可以被建模成资源。此外,也有可能某些资源只表示实体的某一部分。资源设计的主要驱动力是网络因素而不是对象模型。
任何重要的资源都应该能够通过一个唯一的标识被访问。RESTful HTTP使用URI来识别资源。URI提供了Web通用的识别机制,它包含了客户端直接与被引用的资源进行交互时需要的所有信息。
如何命名资源标识?
虽然RESTful HTTP并没有明确指出如何构造一个URI路径,但实际上经常被使用的是一些特定的命名模式。URI命名模式有助于应用程序调试和跟踪,通常一个URI包含资源类型及其后面用于定位特定资源的标识。这样的URI不包括指定业务操作的动词(verb),而只用于定位资源。图中a1给出了Hotel资源的一个示例,同一个Hotel资源也可以通过URI(a2)访问。同一资源可以被多个URI引用。
(a1) http://localhost/hotel/656bcee2-28d2-404b-891b
(a2) http://127.0.0.1/hotel/656bcee2-28d2-404b-891b
(b) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4
(c) http://localhost/hotel/656bcee2-28d2-404b-891b/Reservation/15
(d) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4/Reservation/15
(e) http://localhost/hotel/656bcee2-28d2-404b-891b/Room/4/Reservation/15v7
(f) http://localhost/hotel/656bcee2-28d2-404b-891bv12
图2:资源寻址的例子
URI也可以被资源用来在资源表示(representation)之间建立关联。例如,Hotel表示通过URI去引用已分配的Room资源,而不是使用普通的RoomID。使用普通的ID会强制调用者通过对资源的访问去构造URI,而调用者如果没有主机名和基础URI路径等上下文信息是无法访问到该资源的。
超链接常被客户端用于资源导航。RESTful API是超文本驱动的,这表示客户端通过获得一个Hotel表示,就能够导航到已分配的Room表示和Reservation表示。
在实践中,图1所示的这些类经常被映射成某种业务对象,这意味着在业务对象的整个生命周期中URI将保持不变。如果要创建一个新资源,则要为之分配一个新的URI。而一旦这个新资源被删除,相应的URI则跟着失效。如图2中的(a),(b),(c)和(d)就是这种标识的例子。另一方面,URI也可以用来引用资源快照,比如(e)和(f)就是对这类快照的引用,其URI中包含了一个版本标识。
URI还可以定位子资源,如示例中的(b),(c),(d)和(e)。通常,被聚集的对象会被映射成子资源,如Room是被Hotel聚集的。被聚集的对象通常没有自己的生命周期,如果它的父对象被删除,所有的被聚集对象也跟着被删除。
然而,如果一个子资源可以从一个父资源移动到另一个父对象, 那么在它的URI中就不应该包含其父资源的标识。比如图1中的Reservation资源,它就可以被分配给另一个Room资源。如果一个Reservation资源的URI包含了其父资源Room的标识,如(d)所示,则当Room实例标识改变时,如果该Reservation资源又被另一个资源引用的话,这就会出问题。为了避免无效的URI,Reservation应该通过(c)这样的方式进行寻址。
通常,资源的URI是由服务器控制的。客户端访问资源时并不需要理解资源的URI命名空间结构。比如,使用(c)和(d)两个URI结构对客户端而言具有效果相同。
统一资源接口
为了简化整体系统架构,REST架构风格包含了统一接口的概念。统一接口包含一组受限的良定义的操作,由它们进行资源的访问和操作。不论什么资源,都使用相同的接口。客户端与Hotel,Room或CreditScore等资源交互时使用的接口是一样的。统一接口独立于资源的URI,并且也不需要类似IDL的文件去描述可用的操作。
RESTful HTTP的接口非常流行且广为使用。它包含标准的HTTP方法如GET,PUT和POST(浏览器使用它发出请求并提取页面)。不幸的是,很多开发者认为实现RESTful应用就是用一种直接使用HTTP的方式,这种理解是错误的。举个例子,HTTP方法的实现必须要遵循HTTP规范的,而通过GET方法创建或修改对象是不遵守HTTP规范的。
应用统一接口
关于何时以及如何使用不同的HTTP动词(verb),在Fielding的论文中没有任何表格、列表或其他方式的描述。对于大部分方法,如GET或 DELETE,通过阅读HTTP规范就能清楚其含义,而对于POST和部分更新,就不那么容易了。在实践中,对资源进行部分更新有好几种方法,下文将有详细介绍。
表1列出了大部分重要的方法GET,DELETE,PUT和POST的典型用法:
重要
方法
典型用法
典型状态码
安全?
幂等?
GET
- 获取表示
- 变更时获取表示(缓存)
200(OK) - 表示已在响应中发出
204(无内容) - 资源有空表示
301(Moved Permanently) - 资源的URI已被更新
303(See Other) - 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
是
是
DELETE
- 删除资源
200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求t
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
否
是
PUT
- 用客户端管理的实例号创建一个资源
- 通过替换的方式更新资源
- 如果未被修改,则更新资源(乐观锁)
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示/p>
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
否
是
POST
- 使用服务端管理的(自动产生)的实例号创建资源
- 创建子资源
- 部分更新资源
- 如果没有被修改,则不过更新资源(乐观锁)
200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
否
否
表1:统一接口示例
表示
对资源的操纵永远是通过其表示实现的。资源可能永远不会在网络中传输,相反,传输的是资源的表示。资源的表示包括数据和描述数据的元数据,例如,HTTP头“Content-Type” 就是这样一个元数据属性。
图3展示了如何使用Java获取表示。该例程使用了Java HTTP库xLightweb中的HttpClient类,这个库由作者本人维护。
HttpClient httpClient = new HttpClient();
IHttpRequest request = new GetRequest(centralHotelURI);
IHttpResponse response = httpClient.call(request);
图3:获取表示的Java例程
通过调用HTTP客户端的call方法,一个访问Hotel资源表示的HTTP请求就被发送出去。返回的表示如图4所示,它也包含了用于指示实体主体的多媒体类型的Content-Type头。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 277
Content-Type: application/x-www-form-urlencoded
classification=Comfort&name=Central&RoomURI=http%3A%2F%2Flocalhost%2Fhotel%2F
656bcee2-28d2-404b-891b%2FRoom%2F2&RoomURI=http%3A%2F%2Flocalhost%2Fhotel%2F6
56bcee2-28d2-404b-891b%2FRoom%2F1
图4:RESTful HTTP交互
如何支持特定表示?
为了避免传输很大的数据集,有时应该接收表示属性一个子集。在实现时,用于指定部分属性的一种方式就是支持对指定属性的寻址,如图5所示。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 26
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort
图5:属性过滤
图5中所示的GET调用只请求了一个属性(classification),如果要请求多个属性,所请求的属性要用逗号隔开,如图6所示。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification,name HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 43
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort&name=Central
图6:多属性过滤
确定所需属性的另一种方法是使用查询参数,通过它列出所请求的属性,如图7所示。查询参数将用于定义查询条件以及更复杂的过滤或查询准则。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b?reqAttr=classification&reqAttr=name HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 43
Content-Type: application/x-www-form-urlencoded; charset=utf-8
classification=Comfort&name=Central
图7:查询字符串
在上述例子中,服务器总是返回以application/x-www-form-urlencoded编码的媒体类型的表示。该媒体类型将实体编码成键值对列表。键值方法理解起来很容易,但缺点是,它不适用与更加复杂的数据结构。此外,这种媒体类型不支持标量数据类型的绑定,如Integer,Boolean,Date等。基于这个原因,通常使用XML,JSON或Atom来表征资源(JSON也没有定义Data类型的绑定)
HttpClient httpClient = new HttpClient();
IHttpRequest request = new GetRequest(centralHotelURI);
request.setHeader("Accept", "application/json");
IHttpResponse response = httpClient.call(request);
String jsonString = response.getBlockingBody().readString();
JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON(jsonString);
HotelHotel= (Hotel) JSONObject.toBean(jsonObject, Hotel.class);
图8:请求JSON表示
通过设置“Accept”请求头,客户端就可以请求指定的表示编码。图8展示了如何对application/json类型的表示的请求。JSONlib将把图9中显示的返回响应消息映射成Hotel bean。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/json
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 263
Content-Type: application/json; charset=utf-8
{"classification":"Comfort",
"name":"Central",
"RoomURI":["http://localhost/hotel/656bcee2-28d2-404b-891b/Room/1",
"http://localhost/hotel/656bcee2-28d2-404b-891b/Room/2"]}
图9:JSON表示
如何报告错误?
当服务器不支持所请求的表示时怎么办?图10展示了一个请求XML表示资源的HTTP交互,若服务器不支持这种表示,它将返回一个HTTP 406响应,表示拒绝处理该请求。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: text/xml
RESPONSE:
HTTP/1.1 406 No match for accept header
Server: xLightweb/2.6
Content-Length: 1468
Content-Type: text/html; charset=iso-8859-1
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 406 No match for accept header</title>
</head>
<body>
<h2>HTTP ERROR: 406</h2><pre>No match for accept header</pre>
...
</body>
</html>
图10:不支持的表示
RESTful HTTP服务端程序必须根据HTTP规范返回状态码。状态码的第一个数字标识返回类型,1xx表示临时响应,2xx表示成功响应 ,3xx代表转发,4xx表示客户端错误,5xx代表服务端错误。使用错误的响应码,或者总返回200响应,并在消息主体中包含特定应用程序的响应,这两种做法都是不好的实践。
客户代理和中介也要分析返回码。例如,xLightweb HttpClient默认会把持久的HTTP连接保存在连接池中,当一个HTTP交互完成时,持久化HTTP连接就应返回到内部连接池已备重用。而只有完好的连接才能被放回连接池,比如,若返回码是5xx,那该连接就不会重回连接池了。
有时某些特定的客户端要求更简洁的返回码。一种方法是增加一个HTTP头“X-Header”,用它来详细描述HTTP状态码。
REQUEST:
POST /Guest/ HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 94
Content-Type: application/x-www-form-urlencoded
zip=30314&lastName=Gump&street=42+Plantation+Street&firstName=Forest&country=US&
city=Baytown&state=LA
RESPONSE:
HTTP/1.1 400 Bad Request
Server: xLightweb/2.6
Content-Length: 55
Content-Type: text/plain; charset=utf-8
X-Enhanced-Status: BAD_ADDR_ZIP
AddressException: bad zip code 99566
图11:附加状态码
通常只有在进行编程问题诊断时才需要详细的错误码。尽管比起详细的错误码,HTTP状态码的描述性总是要差很多,但是在大多数情况下,它们对于客户端正确处理问题已经足够了。另一种方法是在响应主体中包含详细的错误码。
PUT还是POST?
较之流行的RPC方式,HTTP方法不仅仅在方法名上有所不同,而且HTTP方法中的某些属性(如幂等性,安全性等)也扮演着重要的角色。不同的HTTP方法的幂等性和安全性属性也是不同的。
HttpClient httpClient = new HttpClient();
String[] params = new String[] { "firstName=Forest",
"lastName=Gump",
"street=42 Plantation Street",
"zip=30314",
"city=Baytown",
"state=LA",
"country=US"};
IHttpRequest request = new PutRequest(gumpURI, params);
IHttpResponse response = httpClient.call(request);
图12:使用PUT方法
如图12和13所示,使用PUT操作来创建一个新的Guest资源。PUT方法将封装好的资源存放在Request-URI之下。该URI是由客户端决定的,当Request-URI指向某现存资源时,该资源将被新资源替换。基于该原因,PUT方法一般用于创建新资源或更新现有资源。然而,通过使用PUT,资源的整个状态都会被改变,若一个请求只需要修改zip域,它不得不包含该资源的其他域,如 firstName,city等。
REQUEST:
PUT Hotel/guest/bc45-9aa3-3f22d HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 94
Content-Type: application/x-www-form-urlencoded
zip=30314&lastName=Gump&street=42+Plantation+Street&firstName=Forest&country=US&
city=Baytown&state=LA
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 36
Content-Type: text/plain; charset=utf-8
Location: http://localhost/guest/bc45-9aa3-3f22d
The guest resource has been updated.
图13:HTTP PUT交互
PUT方法是幂等的,幂等性意味着对于一个成功执行的请求,不管其执行多少次,其结果都是一致的。也就是说,只要你愿意,你可以用PUT方法对Hotel资源进行任意次更新,其结果都一样。如果两个PUT方法同时发生,那么只有其中之一会赢得最后的胜利并决定资源的最终状态。删除操作也是幂等的,如果一个PUT方法和DELETE方法同时发生,那么资源或者被更新,或者被删除,而不可能停留在某个中间状态。
如果你不确定是PUT还是DELETE被成功执行,并且没有得到状态码409 (Conflict)或者 417 (Expectation Failed)的话,那么就重新执行一遍。而不需要附加的可靠性协议来避免重复请求,因为通常重复的请求不会有任何影响。
上述描述对于POST方法就不适用了,因为POST方法不是幂等的,若要两次执行同一个POST请求那就要注意了。POST方法所缺失的幂等性就解释了为什么当你每次重新发送POST请求时浏览器总是弹出警告。POST方法用于创建资源,而不需要由客户端指定实例id,图14展示了通过POST方法创建一个Hotel资源的HTTP交互过程。通常,客户端使用只包含基路径和资源类型名的URI来发送POST请求。
REQUEST:
POST /HotelHTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 35
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Accept: text/plain
classification=Comfort&name=Central
RESPONSE:
HTTP/1.1 201 Created
Server: xLightweb/2.6
Content-Length: 40
Content-Type: text/plain; charset=utf-8
Location: http://localhost/hotel/656bcee2-28d2-404b-891b
the Hotelresource has been created
图14:HTTP POST交互(创建)
POST方法也经常用于更新资源的部分内容,比如,如果我们要通过发送仅包含classification属性的PUT请求去更新Hotel资源的话,这就是违反HTTP的,但是用POST方法则没有问题。POST方法既不是幂等的,也不是安全的。图15展示了一个执行部分更新的POST方法。
REQUEST:
POST /hotel/0ae526f0-9c3d HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 19
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Accept: text/plain
classification=First+Class
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 52
Content-Type: text/plain; charset=utf-8
the Hotelresource has been updated (classification)
图15: HTTP POST交互 (更新)
还可以使用PATCH方法来进行部分更新,PATCH方法是对资源进行部分更新的一个特殊方法。一个PATCH请求包含一个补丁文档,它将应用于由Request-URI所指定的资源。然而PATCH的RFC规范还在草稿中。
使用HTTP缓存
为提高扩展性并降低服务端负载, RESTful的HTTP应用程序可以利用WEB基础设施的缓存机制。HTTP已经意识到缓存是WEB基础设施必不可少的一部分,比如,HTTP协议定义了专门的消息头来支持缓存。如果服务端设置了这个头,客户端(如HTTP客户端或Web缓存代理)就能够有效地支持缓存策略。
HttpClient httpClient = new HttpClient();
httpClient.setCacheMaxSizeKB(500000);
IHttpRequest request = new GetRequest(centralHotelURI + "/classification");
request.setHeader("Accept", "text/plain");
IHttpResponse response = httpClient.call(request);
String classification = response.getBlockingBody.readString();
// ... sometime later re-execute the request
response = httpClient.call(request);
classification = response.getBlockingBody.readString();
图16:客户端缓存交互
图16显示了一个重复的GET调用。通过设置最大缓存大小的值>0激活了HttpClient的缓存功能。如果响应消息中包含了刷新头,比如Expires或Cache-Control: max-age,该响应就会被HttpClient缓存。这些头指明了关联的表示可以保鲜的时间为多久。如果在一段时间内发出了相同的请求,那么HttpClient就会使用缓存为这些请求提供服务,而不需要重复进行网络调用。在网络上总共只有一次HTTP交互,如图17所示。诸如WEB代理之类的缓存中介也实现了相同的功能,而且该缓存还可以在不同客户端之间共享。
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/classification HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: text/plain
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Cache-Control: public, max-age=60
Content-Length: 26
Content-Type: text/plain; charset=utf-8
comfort
图17:包含过期头的HTTP响应
过期模型在静态资源上很好用,可是,对于动态资源(资源状态经常改变且无法预测)则不尽相同。HTTP通过验证头,如Last-Modified以及ETag来支持动态资源的缓存。与过期模型相比,验证模型没有节省网络调用。但是,当执行带条件的GET方法时它会对昂贵的操作节约网络传输,图 18(2.request)显示了带条件的GET操作,它带有一个额外的Last-Modified头,这个头包含了缓存对象最后修改日期。如果该资源未被更改,服务端将会返回一个304 (Not Modified) 响应。
1. REQUEST:
GET /hotel/656bcee2-28d2-404b-891b/Reservation/1 HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
1. RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 252
Content-Type: application/x-www-form-urlencoded
Last-Modified: Mon, 01 Jun 2009 08:56:18 GMT
from=2009-06-01T09%3A49%3A09.718&to=2009-06-05T09%3A49%3A09.718&guestURI=
http%3A%2F%2Flocalhost%2Fguest%2Fbc45-9aa3-3f22d&RoomURI=http%3A%2F%2F
localhost%2Fhotel%2F656bcee2-28d2-404b-891b%2FRoom%2F1
2. REQUEST:
GET /hotel/0ae526f0-9c3d/Reservation/1 HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
If-Modified-Since: Mon, 01 Jun 2009 08:56:18 GMT
2. RESPONSE:
HTTP/1.1 304 Not Modified
Server: xLightweb/2.6
Last-Modified: Mon, 01 Jun 2009 08:56:18 GMT
图18:基于验证的缓存
不要在服务端存储应用状态
RESTful HTTP的交互必须是无状态的,这表明每一次请求要包含处理该请求所需的一切信息。客户端负责维护应用状态。RESTful服务端不需要在请求间保留应用状态,服务端负责维护资源状态而不是应用状态。服务端和中介能够理解独立的请求和响应。Web缓存代理拥有一切正确处理请求所需的信息并管理它的缓存。
这种无状态的方法是实现高扩展和高可用应用的基本原则。通常无状态使得每一个客户请求可以由不同的服务器来响应,当流量增加时,新的服务器可以加进来,而如果某个服务器失败,它也可以从集群中移除。若要了解关于负载均衡以及故障恢复方面的更详细信息,请参考这篇文章服务器负载均衡架构 。
对non-CRED操作的支持
开发者经常想了解如何将non-CRUD(Create-Read-Update-Delete)操作映射到资源。显然,Create、Read、Update和Delete等操作能够很容易地映射到资源的方法。然而, RESTful HTTP还不仅限于面向CRUD的应用。
图19: RESTful HTTP资源
就如图19所示的creditScoreCheck而言,它提供了一个non-CRUD操作creditScore(...),该操作接受一个address,计算出score并返回。这样的操作可以通过CreditScoreResource实现,该资源代表着计算的返回。图20展示了一个GET方法,它传入address,然后提取CreditScoreResource表示,查询参数被用来指定CreditScoreResource。GET方法是安全的,并且可缓存,所提它很适用于CreditScore Check的creditScore(...)方法的非功能性行为。计算的结果可以缓存一段时间,如图20所示,响应包含了一个缓存头,它通知客户端和中介执行响应缓存。
REQUEST:
GET /CreditScore/?zip=30314&lastName=Gump&street=42+Plantation+Street&
firstName=Forest&country=US&city=Baytown&state=LA HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/x-www-form-urlencoded
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
Cache-Control: public, no-transform, max-age=300
scorecard=Excellent&points=92
图20:Non-CRUD HTTP GET交互
上述例子还显示了GET方法的局限性。尽管HTTP规范并没有指定URL的最大长度,但是实际上客户端,中介以及服务端对URL的长度都有限制。基于此,通过GET的查询参数发送一个很大的实体可能会因为中介和服务器对URL长度的限制而失败。
另一解决方法是使用POST方法,如果作了设置,它也是可缓存的。如图21所示,第一个POST请求的结果是创建了一个虚拟资源CreditScoreResource。输入的address数据用text/card这个mime类型进行编码,在服务端计算得到score之后,它发回一个201(created)响应,该响应包含着所创建的CreditScoreResource资源的URI。 示例中还展示了如果进行了设定,POST响应也可以被缓存。通过一个GET请求就能够取到计算结果。GET响应也包含一个缓存控制头,如果客户端紧接着重新执行这两次请求,那么它们都可由缓存进行响应。
1. REQUEST:
POST /CreditScore/ HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Content-Length: 198
Content-Type: text/x-vcard
Accept: application/x-www-form-urlencoded
BEGIN:VCARD
VERSION:2.1
N:Gump;Forest;;;;
FN:Forest Gump
ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;US
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A30314 Baytown=0D=0ALA US
END:VCARD
1. RESPONSE:
HTTP/1.1 201 Created
Server: xLightweb/2.6
Cache-Control: public, no-transform, max-age=300
Content-Length: 40
Content-Type: text/plain; charset=utf-8
Location: http://localhost/CreditScore/l00000001-l0000005c
the credit score resource has been created
2. REQUEST:
GET /CreditScore/l00000001-l0000005c HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
2. RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
Cache-Control: public, no-transform, max-age=300
scorecard=Excellent&points=92
图21: Non-CRUD HTTP POST交互
还有其他不同的实现方式。比如不返回201响应,而返回301(Moved Permanently)转发响应。该响应缺省是可缓存的。其他避免二次请求的方法是在201响应中增加一个新创建的CreditScoreResource资源的表示。
总结
大多数SOA架构(如SOAP或CORBA)都试图映射如图1所示的类模型,或多或少是一对一的远程访问。通常,这些SOA架构的比较多地关注在编程语言对象的透明映射上,这种映射很容易理解,且易于跟踪。可是,它们把对分布性和扩展性等方面的关注排在第二位。
相反,REST架构风格的最主要驱动是分布性和扩展性。RESTful HTTP接口的设计是由网络因素而非编程语言的绑定驱动的。 RESTful HTTP也没有试图去封装很那些难隐藏的因素,如网络延迟,网络健壮性以及网络带宽等。
RESTful HTTP应用用一种直接的方式使用HTTP协议,而不需任何抽象层,也不存在REST指定的数据域,如错误域,安全令牌域等。RESTful HTTP应用只使用WEB的固有能力。设计RESTful HTTP的接口意味着远程结构的设计者必须在HTTP协议上进行思考。这通常增加了开发周期中额外步骤。
然而,RESTful HTTP使得应用程序实现具有高扩展性,更健壮。特别是为很大用户群提供Web应用的公司,如 WebMailing或SocialNetworking的应用就能从REST架构风格中获益。通常,这些应用要更快更高地扩展,而且,这些公司通常在一些低预算的基础设施(基于广泛使用的标准组件和软件之上)上运行应用。
关于作者
Gregor Roth,xLightweb HTTP库的作者。在United Internet组织担任软件架构师,该组织是最重要的欧洲因特网服务提供商,其产品有GMX, 1&1, and Web.de等。他感兴趣的领域包括软件和系统架构、企业架构管理、面向对象设计、分布式计算和开发方法论等。
========================================================================
原文网址:http://www.infoq.com/cn/articles/designing-restful-http-apps-roth
发表评论
-
AddressBook iOS读取通讯录信息
2013-04-15 15:26 1649iphone如许app读取通讯录信息,读取通讯录信息时需要加载 ... -
MFMessageComposeViewController发送短信
2013-04-11 13:33 1278MFMessageComposeViewController ... -
IOS使用第三方工具(RegexKitLite)实现正则表达式
2013-04-10 18:19 1883在IOS应用中,经常要输入数据,然输入数据,就要 ... -
iPhone:动态获取UILabel的高度和宽度
2013-04-09 18:10 809在使用UILabel存放字符串时,经常需要获取label的长宽 ... -
获得通讯录中联系人的所有属性ABAddressBookRef
2013-04-09 13:11 1015获得通讯录中联系人的所有属性 ,看代码: ABA ... -
iOS 绘制直线、矩形、文字的方式
2013-04-08 10:35 4315首先,获取上下文 CGContextRef context = ... -
iOS 开发小技巧(二)
2013-04-08 09:36 7111、获取全局的Delegate对象,这样我们可以调用这个对象里 ... -
iOS 开发小技巧(一)
2013-04-08 09:27 6811.取字符串中的某段字符串 [plain] view pla ... -
IOS导航栏的创建方法
2013-04-07 15:45 1016基本思路是: 1.创建一个导航栏(UINavigationB ... -
iOS 绘制UIImage的方式
2013-04-07 13:03 2411一:在绘制到context前通过矩阵垂直翻转坐标系 // ui ... -
理解可变参数va_list、va_start、va_arg、va_end原理及使用方法
2013-04-07 12:59 1750在VC++6.0的include有一个stdarg.h头文件, ... -
iOS 开发小技巧(三)
2013-04-06 09:21 6651.判断邮箱格式是否正确的代码 //利用正则表达式验证 -(B ...
相关推荐
RESTful API 设计最佳实践是构建可扩展、高效且易于使用的Web服务的关键。遵循REST(Representational State Transfer)原则,可以确保API与Web的核心概念——HTTP协议紧密结合,从而实现资源的透明操作。以下是对...
本实践指南将聚焦于如何基于Java的Jersey框架实现RESTful服务,探讨相关的技术和最佳实践。 首先,让我们理解REST的基本概念。REST定义了一组约束条件,这些约束使得系统更加简单、可伸缩,并且符合Web的工作方式。...
RESTful原理与实践 RESTful(Representational State of Resource)是一种软件架构风格,在万物互联的智能世界中,变得越来越重要。它提供了一种简单、灵活、可扩展的方式来构建网络应用程序。RESTful基于HTTP...
通过阅读和分析TinyHttpd.pas源代码,开发者可以深入理解Delphi的网络编程,RESTful API的设计原理,以及如何在实践中实现这些概念。这不仅可以提升Delphi编程技能,还能增进对HTTP服务器工作原理的理解,为构建自己...
这个C#的RESTful服务端和客户端Demo,旨在帮助开发者快速理解和实践RESTful API的设计和实现,是学习和开发相关应用的良好起点。通过实际操作和修改这个Demo,你可以更深入地了解RESTful架构的精髓以及C#在Web服务...
以上就是使用Spring构建RESTful服务并处理JSON的最佳实践。通过这些方法,你可以创建高效、可维护且易于使用的API。对于进一步的学习,可以参考提供的博文链接,了解更多实际示例和具体实现细节。
本文将深入探讨如何使用C和C++来实现HTTP服务,并开发RESTful API服务器。 首先,HTTP(超文本传输协议)是互联网上应用最为广泛的一种网络协议,用于从万维网服务器传输超文本到本地浏览器。它是一个基于请求与...
RESTful API 使用 HTTP 协议定义了四种主要的操作:GET、POST、PUT 和 DELETE,分别对应于资源的获取、创建、更新和删除。 #### 构建 RESTful 风格的 API 构建 RESTful 风格的 API 时,通常会遵循以下几点建议: ...
在开发RESTful接口时,我们需要遵循一定的设计规范来确保接口的一致性、可维护性和易用性。RESTful API(Representational State ...通过综合这些最佳实践和规范,我们可以构建出既强大又易于使用的RESTful API。
Restful.NET是一个基于.NET框架实现RESTful架构风格的Web服务开发工具。REST(Representational State ...在实践中,还需要关注性能优化、API设计的最佳实践以及持续集成和部署等方面,以打造高质量的RESTful服务。
HTTPClient是一个强大的HTTP客户端库,而RESTful是一种轻量级的、基于HTTP协议的软件架构风格,常用于构建Web服务。在分布式系统中,这两种技术常常结合使用,以实现各个节点间的高效数据交互。 **HTTPClient简介**...
RESTful是一种设计Web服务接口的原则,它强调了资源的概念,并通过HTTP方法来操作这些资源。在本文中,我们将深入探讨RESTful架构风格的两个实际应用示例,它们分别是...通过实践,你可以更好地掌握RESTful服务的开发。
RESTful API通常基于HTTP协议,通过GET、POST、PUT、DELETE等HTTP方法来操作资源。在Spring CXF中,我们可以轻松地定义这些资源和操作,实现RESTful服务。 1. **配置Spring CXF**:创建一个Spring配置文件,如`cxf-...
#### 八、RESTful 最佳实践 - **文档化**:编写详细的 API 文档对开发者至关重要,包括请求参数、响应格式等。 - **认证与授权**:使用 JWT(JSON Web Token)等机制确保只有经过认证的用户才能访问敏感资源。 - **...