三、显式地使用 HTTP 方法 基于 REST 的 Web 服务的主要特征之一是以遵循 RFC 2616 定义的协议的方式显式使用 HTTP 方法。 例如,HTTP GET 被定义为数据产生方法,旨在由客户端应用程序用于检索资源以从 Web 服务器获取数据, 或者执行某个查询并预期 Web 服务器将查找某一组匹配资源然后使用该资源进行响应。
REST 要求开发人员显式地使用 HTTP 方法,并且使用方式与协议定义一致。 这个基本 REST 设计原则建立了创建、读取、更新和删除(create, read, update, and delete,CRUD)操作与 HTTP 方法之间的一对一映射。 根据此映射: . 若要在服务器上创建资源,应该使用 POST 方法。 . 若要检索某个资源,应该使用 GET 方法。 . 若要更改资源状态或对其进行更新,应该使用 PUT 方法。 . 若要删除某个资源,应该使用 DELETE 方法。
许多 Web API 中所固有的一个令人遗憾的设计缺陷在于将 HTTP 方法用于非预期用途。 例如,HTTP GET 请求中的请求 URI 通常标识一个特定的资源。 或者,请求 URI 中的查询字符串包括一组参数,这些参数定义服务器用于查找一组匹配资源的搜索条件。 至少,HTTP/1.1 RFC 是这样描述 GET 方法的。 但是在许多情况下,不优雅的 Web API 使用 HTTP GET 来触发服务器上的事务性操作——例如,向数据库添加记录。 在这些情况下,GET 请求 URI 属于不正确使用,或者至少不是以基于 REST 的方式使用。 如果 Web API 使用 GET 调用远程过程,则应该类似如下: GET /adduser?name=Robert HTTP/1.1
这不是非常优雅的设计,因为上面的 Web 方法支持通过 HTTP GET 进行状态更改操作。 换句话说,该 HTTP GET 请求具有副作用。 如果处理成功,则该请求的结果是向基础数据存储区添加一个新用户——在此例中为 Robert。 这里的问题主要在语义上。 Web 服务器旨在通过检索与请求 URI 中的路径(或查询条件)匹配的资源, 并在响应中返回这些资源或其表示形式,从而响应 HTTP GET 请求,而不是向数据库添加记录。 从该协议方法的预期用途的角度看,然后再从与 HTTP/1.1 兼容的 Web 服务器的角度看,以这种方式使用 GET 是不一致的。
除了语义之外,GET 的其他问题在于,为了触发数据库中的记录的删除、修改或添加,或者以某种方式更改服务器端状态, 它请求 Web 缓存工具(爬网程序)和搜索引擎简单地通过对某个链接进行爬网处理,从而意外地做出服务器端更改。 克服此常见问题的简单方法是将请求 URI 上的参数名称和值转移到 XML 标记中。 这样产生的标记是要创建的实体的 XML 表示形式,可以在 HTTP POST 的正文中进行发送, 此 HTTP POST 的请求 URI 是该实体的预期父实体(请参见清单 1 和 2):
清单 1. 之前 GET /adduser?name=Robert HTTP/1.1
清单 2. 之后 POST /users HTTP/1.1 Host: myserver Content-Type: application/xml
Robert
上述方法是基于 REST 的请求的范例: 正确使用 HTTP POST 并将有效负载包括在请求的正文中。 在接收端,可以通过将正文中包含的资源添加为请求 URI 中标识的资源的从属资源,从而处理该请求; 在此例下,应该将新资源添加为 /users 的子项。 POST 请求中指定的这种新实体与其父实体之间的包含关系类似于某个文件从属于其父目录的方式。 客户端设置实体与其父实体之间的关系,并在 POST 请求中定义新实体的 URI。
然后客户端应用程序可以使用新的 URI 获取资源的表示形式,并至少逻辑地指明该资源位于 /users 之下, 如清单 3 所示。
清单 3. HTTP GET 请求 GET /users/Robert HTTP/1.1 Host: myserver Accept: application/xml
以这种方式使用 GET 是显式的,因为 GET 仅用于数据检索。 GET 是应该没有副作用的操作,即所谓的等幂性 属性。 当支持通过 HTTP GET 执行更新操作时,也需要应用类似的 Web 方法重构,如清单 4 所示。
清单 4. 通过 HTTP GET 进行更新 GET /updateuser?name=Robert&newname=Bob HTTP/1.1
这更改了资源的 name 特性(或属性)。 虽然可以将查询字符串用于此类操作,清单 4 就是一个简单的例子, 但是在用于较复杂的操作时,这种将查询字符串作为方法签名的模式往往会崩溃。 由于您的目标是显式使用 HTTP 方法,鉴于上述的相同原因(请参见清单 5), 更符合 REST 的方法是发送 HTTP PUT 请求以更新资源,而不是发送 HTTP GET。
清单 5. HTTP PUT 请求 PUT /users/Robert HTTP/1.1 Host: myserver Content-Type: application/xml
Bob
使用 PUT 取代原始资源可以提供更清洁的接口,这样的接口与 REST 的原则以及与 HTTP 方法的定义一致。 清单 5 中的 PUT 请求是显式的,因为它通过在请求 URI 中标识要更新的资源来指向该资源, 并且它在 PUT 请求的正文中将资源的新表示形式从客户端传输到服务器, 而不是在请求 URI 上将资源属性作为参数名称和值的松散集合进行传输。 清单 5 还具有将资源从 Robert 重命名为 Bob 的效果,这样做会将其 URI 更改为 /users/Bob。 在 REST Web 服务中,使用旧的 URI 针对该资源的后续请求会产生标准的 404 Not Found 错误。
作为一般设计原则,通过在 URI 中使用名词而不是动词,对于遵循有关显式使用 HTTP 方法的 REST 指导原则是有帮助的。 在基于 REST 的 Web 服务中,协议已经对动词(POST、GET、PUT 和 DELETE)进行了定义。 在理想的情况下,为了保持接口的通用化,并允许客户端明确它们调用的操作, Web 服务不应该定义更多的动词或远程过程,例如 /adduser 或 /updateuser。 这条通用设计原则也适用于 HTTP 请求的正文,后者旨在用于传输资源状态,而不是用于携带要调用的远程方法或远程过程的名称。
四、无状态 REST Web 服务需要扩展以满足日益提高的性能要求。 具有负载平衡和故障转移功能、代理和网关的服务器集群通常以形成服务拓扑的方式进行组织, 从而允许根据需要将请求从一个服务器路由到另一个服务器,以减少 Web 服务调用的总体响应时间。 要使用中间服务器扩大规模,REST Web 服务需要发送完整、独立的请求; 也就是说,发送的请求包括所有需要满足的数据,以便中间服务器中的组件能够进行转发、路由和负载平衡, 而不需要在请求之间在本地保存任何状态。
完整、独立的请求不要求服务器在处理请求时检索任何类型的应用程序上下文或状态。 REST Web 服务应用程序(或客户端)在 HTTP Header 和请求正文中包括服务器端组件生成响应所需要的所有参数、上下文和数据。 这种意义上的无状态可以改进 Web 服务性能,并简化服务器端组件的设计和实现,因为服务器上没有状态, 从而消除了与外部应用程序同步会话数据的需要。
第一个路径片段是四个数字的年份,第二个路径片断是两个数字的日期,第三个片段是两个数字的月份。 这样解释它可能有点愚蠢,但这就是我们追求的简单级别。 人类和计算机能够容易地生成类似如此的结构化 URI,因为这些 URI 基于规则。 在语法的空隙中填入路径部分就大功告成了, 因为存在用于组合 URI 的明确模式: http://www.myservice.org/discussion/{year}/{day}/{month}/{topic}
在考虑基于 REST 的 Web 服务的 URI 结构时,需要指出的一些附加指导原则包括: . 隐藏服务器端脚本技术文件扩展名(.jsp、.php、.asp)——如果有的话,以便您能够移植到其他脚本技术而不用更改 URI。 . 将所有内容保持小写。 . 将空格替换为连字符或下划线(其中一种或另一种)。 . 尽可能多地避免查询字符串。 . 如果请求 URI 用于部分路径,与使用 404 Not Found 代码不同,应该始终提供缺省页面或资源作为响应。
URI 还应该是静态的,以便在资源发生更改或服务的实现发生更改时,链接保持不变。 这可以实现书签功能。 URI 中编码的资源之间的关系与在存储资源的位置表示资源关系的方式无关也是非常重要的。
六、传输 XML、JSON 或同时传输这两者 资源表示形式通常反映了在客户端应用程序请求资源时的资源当前状态及其属性。 这种意义上的资源表示形式只是时间上的快照。 这可以像数据库中的记录表示形式一样简单,其中包括列名称与 XML 标记之间的映射,XML 中的元素值包含行值。 或者,如果系统具有数据模型,那么根据此定义,资源表示形式是系统的数据模型中的对象之一的属性快照。 这些对象就是您希望您的 REST Web 服务为客户端提供的资源。
基于 REST 的 Web 服务设计中的最后一组约束与应用程序和服务在请求/响应有效负载或 HTTP 正文中交换的数据的格式有关。 这是真正值得将一切保持简单、可读和连接在一起的方面。 数据模型中的对象通常以某种方式相关,应该以在将资源传输到客户端应用程序时表示资源的方式,反映数据模型对象(资源)之间的关系。 在讨论线程服务中,连接的资源表示形式的示例可能包括根讨论主题及其属性,以及指向为该主题提供的响应的嵌入链接。 清单 6. 讨论线程的 XML 表示形式