- 浏览: 734952 次
- 性别:
- 来自: 重庆
文章分类
- 全部博客 (173)
- JAVA (54)
- ORACLE (3)
- JAVASCRIPT (7)
- STRUCT2 (4)
- SPRING (12)
- EXTJS (7)
- JQUERY (4)
- SQLSERVER (10)
- IBATIS (1)
- MYSQL (6)
- JS (1)
- HTML5 (1)
- TOMCAT (3)
- JSP (1)
- HTML (1)
- 数据库 (2)
- HTTP (3)
- MAVEN (2)
- MyBatis (2)
- IDEA (1)
- spring boot (24)
- spring security (1)
- RestEasy (1)
- KAFKA (4)
- MongoDB (2)
- NODEJS (1)
- 非技术 (3)
- logback (1)
- DSL (1)
- thymeleaf (1)
- RedisTemplate (12)
- spring cloud (39)
- fastdfs (1)
- Disruptor (2)
- JAVAFX (1)
- exe4j (1)
- JVM (1)
- ZOOKEEPER (4)
- activiti (1)
- GRADLE (1)
- spring cloud config (7)
- poi (1)
- spring cloud eureka (7)
- Rabbit MQ (1)
- MQ (1)
- Centos (1)
- nifi (1)
- dubbo (1)
最新评论
-
yangganboy:
yangganboy 写道可以看我简书写的 java 运行时修 ...
java动态添加外部jar包到classpath -
yangganboy:
可以看我简书写的 java 运行时修改classpath 添加 ...
java动态添加外部jar包到classpath -
neufjava:
感谢您的分享!参考您的文章解决了困扰我一天的配置问题。再次感谢 ...
FastDFS文件系统的安装使用及代码的调用(单个节点) -
wo_niu:
写的非常
RedisTemplate常用集合使用说明-boundSetOps(九) -
wangyudong:
新版本 Wisdom RESTClienthttps://gi ...
部分介绍使用IDEA的rest client
摘自:http://blog.csdn.net/voyage_mh1987/article/details/7389359
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的典型用法:
表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等。他感兴趣的领域包括软件和系统架构、企业架构管理、面向对象设计、分布式计算和开发方法论等。
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的典型用法:
表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等。他感兴趣的领域包括软件和系统架构、企业架构管理、面向对象设计、分布式计算和开发方法论等。
发表评论
-
如何保证在Java多线程中的原子性操作?
2019-08-27 18:46 915在单线程的模 ... -
Java内存模型简单介绍
2019-08-19 19:52 789说到Java内存 ... -
java中线程挂起的几种方式
2019-08-16 18:43 1114在Java中使用线程的时候肯定会有线程挂起的 ... -
自定义线程池简单介绍
2019-08-16 11:37 910线程池的相关概念就不在这里说明了,百度一下有很 ... -
实现自己的spring boot starter
2019-04-04 14:59 930在使用spring boot开发的时候,我 ... -
使用POI实现合并行的导入
2019-03-01 17:24 2681在我们进行导入的时候难免不会遇到合并行的导入 ... -
Spring IOC设计原理解析
2019-01-08 14:02 1038摘自https://www.cnblogs.com/ITt ... -
SpringBoot集成自定义HandlerMethodArgumentResolver参数初始值解析
2018-12-13 09:25 2244大部分摘抄自https://www.cnblogs.com/ ... -
关于spring cloud config加密EncryptionTooWeakException异常说明
2018-11-05 16:00 2147今天在使用spring cloud confi ... -
gradle使用mybatis-generator生成user表相关文件只生成mysql..user的原因说明
2018-07-23 15:42 4167在gradle中使用 ... -
SpringBoot+Activiti的serviceTask注入异常问题
2018-07-13 11:51 7542在使用springboot+activiti ... -
16进制与字符串的互转及转换为byte数组
2018-05-25 14:42 3242import java.io.ByteArrayOutput ... -
JVM学习总结
2018-01-29 17:00 1082JVM作为java的重要组成部分,在j ... -
利用JavaFX创建一个可以订阅发布的MQTT客户端桌面程序
2017-12-08 11:34 2100利用JavaFX我们可以创建出像Swing那样 ... -
java8新特性time类相关介绍
2017-10-30 15:46 1669java8已经出来 ... -
java动态添加外部jar包到classpath
2017-08-17 16:38 3153在项目开发过程中我们有时候需要动态的添加 ... -
java 10进制转16进制带ox格式输出
2017-07-14 16:36 2929摘自https://yq.aliyun.com/wenzha ... -
SpringBoot+thymeleaf简单实现登陆注册及记账功能
2017-06-26 10:58 12532本项目主要是使用了SpringBoot及其 ... -
Java 8的新特性—终极版
2017-05-25 16:42 925前言: Java 8 已经发 ... -
对\\uxx转换为unicode编码的\uxx
2017-03-10 16:17 1467在业务过程中难免会遇到字符串转换为16进制的u ...
相关推荐
一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如何开始搭建REST服务时,却很少有人能够清晰地说出它到底是什么,需要遵守什么样的准则。 博文...
**ActiveMQ与REST API实践** ActiveMQ是Apache软件基金会开发的一款开源消息中间件,它遵循开放消息中间件标准——Java Message Service(JMS)。ActiveMQ以其高性能、稳定性和丰富的特性在分布式系统中广泛使用,...
在IT行业中,API(应用程序接口)测试和调试是软件开发过程中的重要环节,尤其是对于Web服务的开发和维护。...通过实践和探索,你可以充分利用这个工具的强大功能,确保你的REST API能够正常工作并满足设计需求。
在这个“kettle生成经纬度,REST Client、Http Client组件例子”中,我们将探讨如何利用Kettle处理地理位置数据,并通过HTTP客户端进行RESTful API调用。 首先,让我们深入了解Kettle中的经纬度生成。在数据处理中...
- **实例结构**:由于压缩包文件名为"RestPro",我们可以推断这可能是一个关于REST实践的项目,可能包含服务器端代码(如Java、Python或Node.js)、客户端代码(如JavaScript或命令行工具)以及相关的配置文件。...
- **具体实践**:作者们通过一家虚构公司的成长历程,展示如何随着业务规模的扩大,逐步采用REST技术和模式来解决不断出现的需求和挑战。 #### 三、关键技术点 - **超媒体驱动的流程**:书中提到了利用超媒体作为...
在本项目中,我们将深入探讨如何使用C++ REST SDK来开发一个HTTP服务器。 首先,C++ REST SDK的核心组件之一是`http_server`模块,它为构建自定义的HTTP服务器提供了基础。要创建一个基本的HTTP服务器,我们需要...
这样的实践与Roy Fielding博士提出REST的初衷是不相符的。 总结来说,REST是一种架构风格,它定义了一系列的约束条件,使得系统能够更好地适应Web的特性和环境。理解并正确应用这些约束条件是构建RESTful服务的关键...
cpprest库提供了轻量级、高效且易于使用的API,使得开发者可以方便地处理HTTP请求、响应、JSON序列化等工作。这个实例工程就是基于cpprest库进行的基础功能测试,主要关注文件数据的拉取操作。 在"cpprest实例工程...
REST是设计分布式网络服务或API时遵循的架构原则以及设计风格, 前后端分离最佳实践的开发标准或规范。本文为资料收藏的.md笔记,选取比较重要的资料,收集了以下内容: 重要概念介绍,如前述的第2-第4个关键词。 ...
《Rest框架及实践.ppt》可能是对RESTful服务开发的实践指南,包括如何设计RESTful API,使用HTTP动词来表示资源的操作,以及如何处理资源的标识和版本控制。这份资料可能还会介绍一些常见的RESTful框架,如Spring ...
这本书“REST in Practice”深入探讨了REST原则和最佳实践,旨在帮助开发者更好地理解和实施RESTful服务。 REST的核心概念是资源(Resource)、URI(统一资源标识符)和表现层(Representation)。资源是REST架构中...
WCF REST服务允许开发者创建基于HTTP协议的、符合REST原则的服务。REST服务通常包括以下关键元素: 1. **URI(Uniform Resource Identifier)**: 服务资源的唯一标识,通过HTTP请求的URL来访问。 2. **HTTP方法**: ...
7. **测试和部署**:在Eclipse中,你可以使用内置的Tomcat服务器启动应用,然后通过浏览器或者HTTP客户端(如cURL)来测试REST接口。一旦验证无误,你可以将应用打包成WAR文件,部署到生产环境的Tomcat服务器上。 ...
同时,"CodeSample"文件可能包含了实际的代码示例,帮助读者更好地理解和实践REST服务器的开发。 总之,通过Delphi XE的DataSnap框架,开发者可以快速构建功能丰富的RESTful服务,与各种客户端应用程序进行通信。...
在GIS领域,REST地图服务允许客户端通过HTTP协议请求地图资源,如地图图层、图元等,并返回JSON或图片格式的数据。SuperMap iClient是一个强大的GIS平台,它支持REST服务,可以提供高效、灵活的地图操作和数据分析...
学习和理解REST Web Service的基本概念和实践,以及如何使用JAX-RS进行开发,对于构建可扩展、易于维护的Web服务至关重要。通过实际动手编写和运行提供的示例,你可以更好地理解和掌握这些知识。
总之,"RestDemo"项目提供了一个实践REST服务的实例,使用了Jersey框架,涵盖了REST服务的基本概念和操作。通过学习和运行这个项目,你将深入理解RESTful服务的设计原则,以及如何在Java环境中实现它们。同时,这也...