摘自《Flask-Web开发:基于Python的Web应用开发实战》
前言
最近几年, Web 程序有种趋势,那就是业务逻辑被越来越多地移到了客户端一侧,开创出了一种称为富互联网应用(Rich Internet Application, RIA)的架构。在 RIA 中,服务器的主要功能(有时是唯一功能)是为客户端提供数据存取服务。在这种模式中,服务器变成了 Web 服务或应用编程接口(Application Programming Interface, API)。
RIA 可采用多种协议与 Web 服务通信。远程过程调用(Remote Procedure Call, RPC)协议,例如 XML-RPC,及由其衍生的简单对象访问协议(Simplified Object Access Protocol, SOAP),在几年前比较受欢迎。最近,表现层状态转移(Representational State Transfer, REST)架构崭露头角,成为 Web 程序的新宠,因为这种架构建立在大家熟识的万维网基础之上。Flask 是开发 REST 架构 Web 服务的理想框架,因为 Flask 天生轻量。在本章,你将学到如何使用 Flask 实现符合 REST 架构的 API。
REST简介
Roy Fielding 在其博士论文(http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)中介绍了 Web 服务的 REST 架构方式,并列出了 6 个符合这一架构定义的特征。
- 客户端-服务器
客户端和服务器之间必须有明确的界线。 - 无状态
客户端发出的请求中必须包含所有必要的信息。服务器不能在两次请求之间保存客户端的任何状态。 - 缓存
服务器发出的响应可以标记为可缓存或不可缓存,这样出于优化目的,客户端(或客户
端和服务器之间的中间服务)可以使用缓存。 - 接口统一
客户端访问服务器资源时使用的协议必须一致,定义良好,且已经标准化。 REST Web
服务最常使用的统一接口是 HTTP 协议。 - 系统分层
在客户端和服务器之间可以按需插入代理服务器、缓存或网关,以提高性能、稳定性和
伸缩性。 - 按需代码
客户端可以选择从服务器上下载代码,在客户端的环境中执行。
资源就是一切
资源是 REST 架构方式的核心概念。在 REST 架构中, 资源是程序中你要着重关注的事物。例如,在博客程序中,用户、博客文章和评论都是资源。
每个资源都要使用唯一的 URL 表示。还是以博客程序为例,一篇博客文章可以使用 URL /api/posts/12345 表示, 其中 12345 是这篇文章的唯一标识符,使用文章在数据库中的主键表示。 URL 的格式或内容无关紧要,只要资源的 URL 只表示唯一的一个资源即可。
某一类资源的集合也要有一个 URL。 博客文章集合的 URL 可以是 /api/posts/,评论集合的URL 可以是 /api/comments/。
API 还可以为某一类资源的逻辑子集定义集合 URL。 例如,编号为 12345 的博客文章,其中的所有评论可以使用 URL /api/posts/12345/comments/ 表示。表示资源集合的 URL 习惯在末端加上一个斜线,代表一种“文件夹”结构。
注意, Flask 会特殊对待末端带有斜线的路由。如果客户端请求的 URL 的末端没有斜线, 而唯一匹配的路由末端有斜线, Flask 会自动响应一个重定向,转向末端带斜线的 URL。反之则不会重定向。
请求方法
客户端程序在建立起的资源 URL 上发送请求,使用请求方法表示期望的操作。 若要从博客 API 中获取现有博客文章的列表,客户端可以向 http://www.exam-ple.com/api/posts/ 发送 GET 请求。若要插入一篇新博客文章,客户端可以向同一地址发送 POST 请求,而且请求主体中要包含博客文章的内容。 若要获取编号为 12345 的博客文章,客户端可以向 http://www.example.com/api/posts/12345 发送 GET 请求。表 列出了 REST 架构 API 中常用的请求方法及其含义。
请求方法 | 目标 | 说明 | HTTP状态码 |
---|---|---|---|
GET | 单个资源的 URL | 获取目标资源 | 200 |
GET | 资源集合的 URL | 获取资源的集合(如果服务器实现了分页,就是一页中的资源) | 200 |
POST | 资源集合的 URL | 创建新资源, 并将其加入目标集合。服务器为新资源指派 URL,并在响应的 Location 首部中返回 | 201 |
PUT | 单个资源的 URL | 修改一个现有资源。 如果客户端能为资源指派 URL,还可用来创建新资源 | 200 |
DELETE | 单个资源的 URL | 删除一个资源 | 200 |
DELETE | 资源集合的 URL | 删除目标集合中的所有资源 | 200 |
REST 架构不要求必须为一个资源实现所有的请求方法。如果资源不支持客户端使用的请求方法, 响应的状态码为 405,返回“不允许使用的方法”。Flask 会自动处理这种错误。
请求和响应主体
在请求和响应的主体中,资源在客户端和服务器之间来回传送,但 REST 没有指定编码资源的方式。 请求和响应中的 Content-Type 首部用于指明主体中资源的编码方式。使用HTTP 协议中的内容协商机制,可以找到一种客户端和服务器都支持的编码方式。
REST Web 服务常用的两种编码方式是 JavaScript 对象表示法(JavaScript Object Notation,JSON)和可扩展标记语言(Extensible Markup Language, XML)。对基于 Web 的 RIA 来说, JSON 更具吸引力,因为 JSON 和 JavaScript 联系紧密,而 JavaScript 是 Web 浏览器使用的客户端脚本语言。 继续以博客 API 为例,一篇博客文章对应的资源可以使用如下的JSON 表示:
{
“url”: “http://www.example.com/api/posts/12345”,
“title”: “Writing RESTful APIs in Python”,
“author”: “http://www.example.com/api/users/2”,
“body”: “… text of the article here …”,
“comments”: “http://www.example.com/api/posts/12345/comments”
}
注意,在这篇博客文章中, url、 author 和 comments 字段都是完整的资源 URL。这是很重
要的表示方法,因为客户端可以通过这些 URL 发掘新资源。
在设计良好的 REST API 中,客户端只需知道几个顶级资源的 URL,其他资源的 URL 则从响
应中包含的链接上发掘。这就好比浏览网络时,你在自己知道的网页中点击链接发掘新网页。
版本
在传统的以服务器为中心的 Web 程序中,服务器完全掌控程序。更新程序时,只需在服务器上部署新版本就可更新所有的用户,因为运行在用户 Web 浏览器中的那部分程序也是从服务器上下载的。
但升级 RIA 和 Web 服务要复杂得多,因为客户端程序和服务器上的程序是独立开发的,有时甚至由不同的人进行开发。 你可以考虑一下这种情况,即一个程序的 REST Web 服务被很多客户端使用, 其中包括 Web 浏览器和智能手机原生应用。服务器可以随时更新Web 浏览器中的客户端, 但无法强制更新智能手机中的应用,更新前先要获得机主的许可。即便机主想进行更新, 也不能保证新版应用上传到所有应用商店的时机都完全吻合新服务器端版本的部署。
基于以上原因, Web 服务的容错能力要比一般的 Web 程序强,而且还要保证旧版客户端能继续使用。 这一问题的常见解决办法是使用版本区分 Web 服务所处理的 URL。例如,首次发布的博客 Web 服务可以通过 /api/v1.0/posts/ 提供博客文章的集合。
在 URL 中加入 Web 服务的版本有助于条理化管理新旧功能,让服务器能为新客户端提供新功能,同时继续支持旧版客户端。博客服务可能会修改博客文章使用的 JSON 格式,同时通过 /api/v1.1/posts/ 提供修改后的博客文章,而客户端仍能通过 /api/v1.0/posts/ 获取旧的JSON 格式。在一段时间内,服务器要同时处理 v1.1 和 v1.0 这两个版本的 URL。提供多版本支持会增加服务器的维护负担, 但在某些情况下,这是不破坏现有部署且能让程序不断发展的唯一方式。