人们在试验REST时,通常会四处寻找样例——而他们往往不仅能找到一大堆自称“符合REST”或标榜为“REST API”的样例,还会发现许多关于某个自称符合REST的特定服务名不副实的讨论。
为什么会这样?HTTP虽不是什么新事物,但人们使用它的方式却五花八门。其中有些做法符合Web设计者的初衷,但许多并非如此。要为你的HTTP应用(无论是面向人类、还是计算机、或同时面向这两者使用的)应用REST原则,意味着你要恰好反过来:尽量“正确地”使用Web,或者说按符合REST的方式使用Web(倘若你不喜欢用对或错来评判的话)。对许多人来说,这的确是一种崭新的方式方法。
我经常在文章里作同样的声明:REST、Web和HTTP是不同的事物;REST可以用多种不同技术来实现,而HTTP只是一种恰好符合REST架构风格的具体架构。所以,其实我应该小心区分“REST”与“REST式HTTP”这两个概念的。但我没有这么做,在本文剩余部分,我们姑且认为它们是相同的事物。
跟任何新的方式方法一样,发掘一些共同的模式是有益的。在本系列的第一和第二篇文章中,我已经讲述了一些基础——比如集合资源的概念、将计算结果转换为资源本身、以及用聚合(syndication)来模仿事件。后续文章将进一步讲述这些及其他模式。不过在本文中,我想主要说说反模式(anti-patterns)——即那些力求符合REST式HTTP、但未能成功而造成问题的典型做法。
首先我们来看看我发掘了哪些反模式:
- 全部采用GET
- 全部采用POST
- 忽视缓存
- 忽视响应代码
- 误用cookies
- 忘记超媒体
- 忽视MIME类型
- 破坏自描述性
下面我们来逐个详细说明。
全部采用GET
在许多人看来,REST仅仅意味着用HTTP暴露一些应用功能。HTTP GET是最重要的基本操作(operation )(严格地讲,用“动词(verb)”或“方法(method)”这样的术语比较好)。GET方法应当用于获取由URI标识的资源的一个表示(representation),而许多(即便谈不上所有)现有的HTTP库和服务器编程API不是将URI视为一种资源标识符(resource identifier),而是将之视为一种传递参数的便利手段。这导致了以下这种URIs的出现:
http://example.com/some-api?method=deleteCustomer&id=1234
实际上,你无法根据构成URI的字符获知关于给定系统的“REST性(RESTfulness)”的任何信息,不过对于上面那个URI,我们可以判断该 GET操作不是“安全的(safe)”——也就是说,调用者很可能要为结果(删除一个客户)负责,尽管规范里说在这种情况下使用GET方法是错误的。
这种做法唯一有利的方面在于它编程起来容易,而且在浏览器中调试也简单——你只要把URI粘贴到浏览器地址栏里、然后调整一些“参数”就行了。这种反模式主要存在以下问题:
- URI没有被用作资源标识符,而是被用于传递操作及其参数了。
- HTTP方法(HTTP method)不一定跟语义相符。
- 这种链接一般不可加入书签。
- 有“爬虫”造成非预期副作用的风险。
注意:符合这一反模式的APIs没准最终碰巧符合REST原则。这里有个例子:
http://example.com/some-api?method=findCustomer&id=1234
这个URI是标识操作及其参数呢,还是标识一个资源呢?两种情况都有可能:它可以是一个完全合法的、可加入书签的URI;对它做GET操作也许是“安全的 ”;它也许会根据Accept报头返回不同的格式,并支持复杂的缓存机制。在很多情况下,这将是偶然的。API经常在刚开始时采用这种方式来暴露一个“读 ”接口,但当开发者要增添“写”功能时就有问题了(因为你无法通过对上述URI做PUT操作来更新一个客户——开发者得构造另一个URI)。
全部采用POST
这一反模式跟前一个颇为相似,只不过这里用的是POST方法而已。POST除了携带一个URI,还携带一个实体主体(entity body)。一个典型的场景是:将单个URI作为POST请求的目标、通过发送不同的消息来表达不同的意图。实际上,SOAP 1.1 Web服务就是这样做的,它把HTTP当作一种“传输协议”来用。服务器根据SOAP消息(可能还包括一些WS-Addressing SOAP报头)决定做什么。
可能有人认为“全部采用POST”跟“全部采用GET”存在的问题完全一样,只是它更难用一些,而且不能利用缓存(甚至连偶尔的机会都没有),且无法支持书签。事实上,它并不是违反了哪条REST原则,而是根本忽视了REST原则。
忽视缓存
即使你按各个动词的原本意图来使用它们,你仍可以轻易禁止缓存机制。最简单的做法就是在你的HTTP响应里增加这样一个报头:
Cache-control: no-cache
这样可以禁止缓存机制发挥作用。当然,这也许正是你想要做的,然而通常这只是你的Web框架规定的一个缺省设置。不过,对高效的缓存与再验证(caching and re-validation)的支持,是采用REST式HTTP的诸多关键优点之一。Sam Ruby表示,在判断是否符合REST原则时的一个关键问题就是“你支持ETag吗”?(ETag是HTTP 1.1里引入的一种机制,它允许客户端通过加密的校验和来验证一个被缓存的表示是否仍然有效)。要生成正确的报头,最简单的做法就是把这个任务交给一个“ 知道”怎样做的基础设施——例如通过在Web服务器(比如Apache HTTPD)的目录里生成一个文件。
当然,这也要涉及到客户端一方:你在为一个REST式服务实现程序客户端时,你应充分利用现有的缓存机制,以免每次都重新获取表示。例如,服务器也许已经发出信息:初次返回的表示在600秒内都可被认为是“新的”(比方说因为后端系统每30分钟才轮询一次)。这样的话,短时间内重复请求同一信息就完全没必要了。在客户端设置一个代理缓存(比如Squid)也许比自行构建相应逻辑更好。
HTTP的缓存机制强大而复杂;Mark Nottingham的《缓存指南(Cache Tutorial)》是一个很好的指南。
忽视响应代码
HTTP提供了一组丰富的应用级状态代码,它们可用于应付不同场合,不过许多Web开发者对此并不知晓。大部分人对200(“OK”)、404(“Not found”)和500(“Internal server error”)这些状态代码是比较熟悉的。但除此以外还有很多其他状态代码,正确使用这些状态代码意味着客户端与服务器可以在一个具备较丰富语义的层次上进行沟通。
例如,201(“Created”)响应代码表明已经创建了一个新的资源,其URI在Location响应报头里。409(“Conflict”)告诉客户端存在冲突,比如随PUT请求发送的是基于老版本资源的数据。再如,412(“Precondition Failed”)表明服务器不能满足客户端的预期。
正确使用状态代码的另一方面涉及客户端:应该根据一种统一的总体方法对不同类别的状态代码(例如所有2xx段代码、所有5xx段代码)作不同处理——例如,即便客户端不具备处理特定代码的逻辑,但至少应把所有2xx段代码视为成功信号。
许多声称符合REST的应用仅仅返回200或500,甚至只返回200(并在响应实体主体里给出错误文本——SOAP就是这样的)。你要是愿意,可以称之为“通过状态代码200传达错误”,但无论你觉得采用哪个术语好,假如你不利用HTTP状态代码丰富的应用语义,那么你将错失提高重用性、增强互操作性和提升松耦合性的机会。
误用cookies
利用cookies来传播某个服务端会话状态的键(key)是另一种REST反模式。
Cookies表明肯定哪个地方不符合REST了。是这样吗?不;不一定。REST的关键思想之一是无状态性(statelessness)——不是说一个服务器不能保存任何数据:倘若是资源状态(resource state)或客户端状态(client state),那是可以的。服务器不能保存的是会话状态(session state),因为那会造成可伸缩性、可靠性及耦合方面的问题。Cookies的最典型的用法是:保存一个跟“某个保存在服务端内存里的数据结构”相关联的键(key)。这意味着,浏览器随各次请求发出去的cookie是被用于构建会话状态的。
如果一个cookie被用于保存一些“服务器不依赖于会话状态即可验证”的信息(比如认证令牌),那么这样的cookies是完全符合REST原则的—— 不过有一点需要注意:如果有其他更为标准的方式来传递一则信息(比如放在URI里、放在某个标准报头里、或较少见地放在消息主体里),那就不应将之放在 cookie里。例如,按REST式HTTP的观点来使用HTTP认证就比较好。
忘记超媒体
最不易接受的REST思想就是标准的方法集合。REST理论并没有规定标准集合由哪些方法组成,它只是规定必须有一组适用于所有资源的方法集合。对于 HTTP来说,这组集合是GET、PUT、POST和DELETE(至少起初是这样),你需要一定适应时间才能掌握如何将所有应用语义投射到这四个动词上。但你一旦适应了,就可以开始运用这个REST的子集——一种基于Web的CRUD(Create、Read、Update、 Delete)架构——了。暴露这种反模式的应用不是真正的“非REST式”应用(假如存在这种事物的话),它们只是未能利用一个REST核心概念——“ 超媒体即应用状态引擎(hypermedia as the engine of application state)”。
超媒体(hypermedia)是一个把事物链接起来的概念,正是它造就了Web这个网——一个互联的资源集合,应用通过跟随链接从一个状态进入另一个状态。这听上去也许有点深奥,不过其实遵从这一原则是有正当理由的。
“忘记超媒体”反模式的首要表现就是:表示(representation)里缺少链接。尽管通常客户端可以根据一定的规则来构造URI,但是因为服务器没有发送任何链接,所以客户端将无法跟随链接。一种较好的做法是:即支持构造URI,又支持跟随链接——这里的链接通常反映了下层数据模型中的关系。但最好的情况是:客户端应该只需知道一个URI;其他URI(各个URI及其构造模式,如:各种查询字符串)应该通过超媒体(作为资源表示里的链接)来传达。 Atom发布协议(Atom Publishing Protocol)就是一个好例子,它有一个服务文档(service documents)的概念,服务文档为它所描述的域内的各个集合提供具名元素(named elements)。最后,应用可能经历的状态迁移应该是动态传播的,客户端应该可以不用掌握多少知识就可以跟随它们。HTML就是一个好榜样,它包含足够的信息,以便浏览器可以向用户提供一个完全动态的接口。
我本想增加一个“人类可读的URI”反模式的。但我没那么做,因为我跟其他人一样也喜欢可读的、好“篡改”的URI。但是当人们采用REST时,他们经常浪费许多时间来讨论“正确的”URI设计,而忘记了超媒体方面。所以,我建议你不要花太多时间来寻找正确的URI设计(毕竟,它们只是字符串而已),而是多花一些精力在表示里寻找提供链接的正确地方。
忽视MIME类型
HTTP有个内容协商(content negotiation)的概念,它允许客户端根据需要获取资源的不同表示(representations)。例如,一个资源也许有不同格式的表示(如 XML、JSON或YAML等)以便于用各种不同语言(如Java、JavaScript及Ruby)实现的消费者所使用。再如,一个资源可能即有面向人类的PDF或JPEG版表示,又有“机器可读的”XML版表示。还有,一个资源可能同时支持v1.1版和v1.2版的自定义表示格式。不管怎样,也许可以为“只有一个表示格式”找到理由,但这常常意味着丢掉某种机会。
显然,若一个服务能为更多未预见到的客户端所用(或重用)那更好。因此,依靠现有、预定义、广为人知的格式,要好过发明私有格式——这会导致本文讲述的最后一个反模式。
破坏自描述性
这种反模式是如此普遍,以至于几乎在每个、甚至那些由所谓的“REST狂热者们”(包括我在内)创建的REST应用里都可以看到:违反自描述性约束(这一努力目标并不像人们最初想象的那样跟人工智能科幻小说有多大牵连)。理想情况下,一个消息(HTTP 请求或HTTP响应,包括报头与主体)应该包含足够信息,以便任何通用客户端、服务器或媒介(intermediary)能够处理它。例如,当你的浏览器获取某个受保护资源的PDF表示(representation)时,你可以看到由标准达成的协定是如何起作用的:有些HTTP认证交换发生,可能会发生一些缓存(caching)和/或再验证(revalidation),服务器发送的content-type报头(application/pdf)触发了你系统里注册的PDF阅读器,最后你得以在自己的屏幕上阅读该PDF。所有用户都可以用他们自己的基础设施来执行同样的请求。若服务器开发者另外增加一种内容类型,那么服务器的客户端(或服务的消费者)只需确保他们安装了正确的阅读器即可。
你要是发明自己的报头、格式或协议,那就一定程度上破坏了自描述性约束。极端地讲,所有没有被某个标准化组织官方标准化的东西都违反此约束,因而可被认为符合本反模式。在实践中,你应努力做到尽可能遵循标准,并懂得“某些协定可能只在一个较小的领域(比方说,你的服务和客户端是专门针对它开发的)中适用” 的道理。
总结
自从“四人组(Gang of Four)”出版了书籍、掀起模式运动的开端以来,许多人误解了它,并试图在尽可能多的场合下应用模式——这已被其他人所取笑。模式应当仅在符合上下文时才被应用。同样地,可能有人会不遗余力地在所有场合下虔诚地努力避免所有反模式。许多时候,你有充分理由违反某一规则,或者按REST的术语放松某一约束。这么做是没问题的——但了解实际情况、作出知情决策是有益的。
但愿本文能有助于你在开始首个REST项目时避免落入这些常见的陷阱。
分享到:
相关推荐
REST API反模式检查器这是一个用于检测API中的REST设计反模式的程序。指示开始吧要运行此程序,需要安装Node.js版本10.0.0(或更高版本)。 使用以下命令安装依赖项: npm i 可以运行REST设计反模式检测功能正确性的...
RESTful API设计简洁,易于理解和使用,是现代Web服务的标准设计模式。 RESTSDK则是实现RESTful服务的工具集,它提供了对HTTP协议的低级别访问,以及对JSON、XML等数据格式的解析和序列化。C++ REST SDK具有以下...
ArcGIS Server REST API就是利用这种设计模式,为开发者提供了与GIS服务交互的接口。 3. **API结构** ArcGIS Server REST API通常由服务目录、服务实例、资源和操作组成。服务目录列出所有可用的服务,服务实例...
本篇文章将深入探讨C#中实现REST协议的相关知识点。 1. **C#与ASP.NET Core** ASP.NET Core是Microsoft推出的跨平台Web框架,支持构建RESTful服务。C#作为ASP.NET Core的主要编程语言,为开发者提供了丰富的API和...
Struts2.1 + REST 是一个结合了Struts2框架与RESTful服务的开发模式,旨在提供更加灵活、轻量级且易于维护的Web应用程序。Struts2是一个强大的MVC(Model-View-Controller)框架,它在Java社区中被广泛应用,而REST...
REST接口测试是软件开发中的重要环节,特别是在API驱动的开发模式下。REST(Representational State Transfer)是一种网络应用程序的设计风格和开发方式,基于HTTP协议,提供了创建、读取、更新和删除(CRUD)资源的...
DRF提供了强大的Serializer类,用于处理数据的序列化和反序列化。你需要掌握如何定义Serializer模型,以及如何在视图中使用它们来处理数据。 2. **视图(Views)**:DRF视图负责处理HTTP请求并返回响应。它们通常...
烛台解决方案 服务器端 用法 mvn clean install mvn spring-boot:run 打包应用程序( mvn package )后,您也可以... 服务器应使用单个方法GET /data (不是/getData因为这是REST反模式)公开REST API。 该方法应返回最
此外,为了提供JSON格式的数据交换,你可能还需要使用如Jackson或Gson这样的库进行序列化和反序列化。 总结来说,"Spring MVC REST Demo"项目展示了如何在Spring MVC框架中利用REST原则创建Web服务。这包括定义...
在DRF中,序列化是将Python对象转换为JSON或其他可传输格式的过程,同时也支持将这些数据反序列化回Python对象。DRF提供了ModelSerializer和Serializer两种类型,前者用于处理Django模型,后者则适用于自定义数据...
这种设计模式使得客户端和服务器之间的通信变得更加简洁和高效。 在Android中,我们通常使用Android的HTTP客户端库,如HttpURLConnection、Apache HttpClient(虽然已被弃用,但仍然广泛使用)或者现代的OkHttp库来...
1. **序列化(Serialization)**:DRF提供了一套强大的序列化系统,能够将复杂的Python对象转换为JSON、XML等常见的数据格式,同时也支持将这些数据反序列化回Python对象,这在处理HTTP请求和响应时非常有用。...
对于基于微服务的新建应用程序而言,这是一种反模式。 异步消息传递微服务设计模式是一种常见的微服务架构设计模式。在这种模式下,微服务使用消息队列代替REST请求/响应,以避免阻塞问题。 微服务架构设计模式的...
而反序列化是将JSON等数据类型转换回Python数据类型的过程。Django REST framework通过提供序列化器(Serializer)类来实现这一功能,并且允许开发者自定义序列化的规则,包括数据的验证和清洗。 请求和响应部分...
在DRF中,我们使用`urls.py`来设置URL模式,将它们映射到视图。`router`类可以简化这一过程,它允许我们基于模型自动配置视图和URL。例如,使用`SimpleRouter`可以轻松地将模型资源暴露为API。 ### 3. **Serializer...
Java REST(Representational State Transfer,表述性状态转移)模式是一种基于HTTP协议的Web服务设计模式,它遵循REST原则,提供简洁、无状态、可缓存的接口,使得客户端与服务器之间的交互变得更加简单和高效。...
9. API路由器:路由器是Django REST framework中的一个组件,它帮助自动配置URLs,将URL模式映射到视图集或视图。 10. 自动文档:通过使用DRF的Swagger或ReDoc集成,可以轻松创建交互式的API文档,便于开发者理解和...
它遵循模型-视图-控制器(MVC)设计模式,使得业务逻辑、数据处理和用户界面分离,提高了代码的可维护性和可测试性。 REST(Representational State Transfer)是一种软件架构风格,常用于构建Web服务。RESTful API...
1. 序列化:DRF提供了强大的序列化能力,能够将Python对象转换为JSON、XML等数据格式,同时也能将这些数据反序列化回Python对象,方便数据交换。 2. 视图:DRF视图基于函数和类,可以轻松地处理HTTP请求并返回响应。...