`
lovnet
  • 浏览: 6704549 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

RESTful HTTP的实践

 
阅读更多

本文对RESTful HTTP的基础原理做了一个概览,探讨了开发者在设计RESTful HTTP应用时所面临的典型问题,展示了如何在实践中应用REST架构风格,描述了常用的URI命名方法,讨论了如何使用统一接口进行资源交互,何时使用PUT或POST以及如何支持非CURD操作等。

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等。他感兴趣的领域包括软件和系统架构、企业架构管理、面向对象设计、分布式计算和开发方法论等。

参考文献

Architectural Styles and the Design of Network-based Software Architectures

REST Eye for the SOA Guy

Presentation: Steve Vinoski on REST, Reuse and Serendipity

A Brief Introduction to REST

Fallacies of Distributed Computing

Server load balancing architectures

Asynchronous HTTP and Comet architectures

JSON-lib

xLightweb

查看英文原文RESTful HTTP in practic


分享到:
评论

相关推荐

    RESTful API 设计最佳实践

    RESTful 原则提供了 HTTP methods 映射作为策略来处理 CRUD actions,如下: GET /tickets - 获取 tickets 列表 GET /tickets/12 - 获取一个单独的 ticket POST /tickets - 创建一个新的 ticket PUT /tickets/12 - ...

    RESTfulHTTP的实践

    本文对RESTfulHTTP的基础原理做了一个概览,探讨了开发者在设计RESTfulHTTP应用时所面临的典型问题,展示了如何在实践中应用REST架构风格,描述了常用的URI命名方法,讨论了如何使用统一接口进行资源交互,何时使用...

    restful:RESTful的Drupal最佳实践

    该模块允许使用安全性,性能和可用性的最佳实践,通过RESTful HTTP请求来操作Drupal。 概念 以下是RESTful和其他模块(例如RestW和服务实体)之间的区别: RESTful需要明确声明公开的API。 启用模块时,插件不会对...

    RESTful Web Services Cookbook 中文免费版

    现在说起REST(表述性状态转移),相信大家一定都不会觉得陌生,因为人们对它的认识早已经过了WHAT和WHY的阶段。...相信《RESTful Web Services Cookbook》一定能在你实践REST的道路上助你一臂之力。

    SpringBoot RESTful API 架构风格实践.docx

    Rest 是一种规范,符合 Rest 的 Api...简单的说就是可联网设备利用 HTTP 协议通过 GET、POST、DELETE、PUT、PATCH 来操作具有URI标识的服务器资源,返回统一格式的资源信息,包括 JSON、XML、CSV、ProtoBuf、其他格式。

    RESTful Web Services 中文版.rar

    RESTful Web Services中文版 1,3,4章 缺第二章和其他章节,源码网无色会在第一时间补齐,敬请关注本页。 本身完整目录: 目录 序.......................................I. --------------------------...

    mock-restful-express:由 Express.js 提供支持的 RESTful 模拟服务器

    模拟宁静表达由 Express.js 提供支持的 RESTful 模拟服务器目录目的该项目的目的是提供基于模拟响应的通用 RESTful API 服务器,以编程方式定义并支持许多功能,例如: 基本认证饼干支持 GET、POST、PUT 和 DELETE ...

    大型分布式网站架构设计与实践.带目录书签.完整版.rar

    曾在程序员上发表过《漫谈基于http协议的SOA架构》《浅析HTTP平台的安全稳定性架构》两篇文章,对基于HTTP协议的SOA架构有深入研究,在排查解决线上问题和故障方面有丰富的实践经验,擅于利用数据分析解决实际问题,...

    api-strategy:RESTful API的API策略

    本文档提供有关开发非平凡的面向资源和RESTful HTTP API的指南。 免责声明:本策略文档不是发明,而是代表了我们在网络上发现的最佳实践和见解的集合。 许多博客和论文为该策略中的内容提供了基础。 此策略中的...

    HCIP-Datacom-Network Automation Developer培训V1.0视频.rar

    3.3 iMaster NCE RESTful API调用实践.mp4 3.4.1 CloudCampus解决方案开放能力概述.mp4 3.4.2 开放典型场景开放能力介绍.mp4 3.5.1 智简园区无线定位实践.mp4 3.5.2 智简园区第三方认证实践.mp4 3.6.1 iMaster NCE-...

    rails_api_example:一个示例 Rails 应用程序,它展示了构建 RESTful API 的最佳实践

    自述文件 设置开发 编辑您的/etc/hosts文件以将 127.0.0.1 (localhost) 映射到开发域的 api 子域,如下所示: ... 现在,启动 Rails 服务器,打开浏览器 ,您应该会看到 Rails 欢迎页面。... curl http://api.est

    Blog:充当日常笔记或者个人博客吧

    Blog充当日常笔记或者个人博客吧翻译与教程[手写]常用在线站点实践与操作[java]使用Spring实现读写分离( MySQL实现主从复制)RESTful API 设计指南RESTful 良好的API设计风格跟着 Github 学习 Restful HTTP API ...

    大型分布式网站架构与实践

     1.2.6 RESTful和RPC 20  1.2.7 基于HTTP协议的RPC的实现 22  1.3 服务的路由和负载均衡 30  1.3.1 服务化的演变 30  1.3.2 负载均衡算法 33  1.3.3 动态配置规则 39  1.3.4 ZooKeeper介绍与环境搭建 40 ...

    张文钿 Rails Best Practices 幻灯片

    RESTful Conventions * Overuse route customizations * Needless deep nesting * Not use default route Lesson 3. Model * Keep Finders on Their Own Model * Love named_scope * the Law of Demeter ...

    nbaRoutes:AngularJS 路由实践

    Nba航线 *对于此项目,您将需要通过服务器提供文件。 打开你的终端,(如果你有 Node/NPM),运行“sudo npm install -g... 在这个 repo 中,您将继续实践您学到的基本 Angular 原则,如控制器、服务、承诺和从 RESTful

    devSpec:最佳实践开发规范和相关工具的集合

    devSpec |最佳实践开发规范和相关工具的集合。内容项目管理标准自动化集成标准代码格式检查标准高朗文件格式检查标准自动化测试标准测试流程标准测试计划标准测试文件标准单元测试标准高朗整合测试标准高朗端到端...

    《PHP Web Services (English)》是一本关于使用 PHP 构建 Web 服务的书籍 在这本书中,你可以学习

    RESTful API 设计: 深入探讨如何设计和实现符合 REST 架构风格的 API,包括资源、URI 设计、HTTP 方法等。 SOAP 服务: 讨论如何使用 PHP 创建和部署基于 SOAP 协议的 Web 服务,并介绍 SOAP 的工作原理和基本概念...

    微服务测试的思考与实践

    微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的进程中,服务间采用轻量级通信机制互相沟通(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体的业务进行构建...

Global site tag (gtag.js) - Google Analytics