Flask: 应用编程接口(API)(一)

REST

REST 简介

Roy Fielding论文:Representational State Transfer (REST)

阮一峰:理解RESTful架构

Roy Fielding 在其博士论文中介绍了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 习惯在末端加上一个斜线,代表一种“文件夹”结构。

请求方法

客户端程序在建立起的资源URL 上发送请求,使用请求方法表示期望的操作。若要从博客API 中获取现有博客文章的列表,客户端可以向http://www.exam-ple.com/api/posts/ 发送GET 请求。若要插入一篇新博客文章,客户端可以向同一地址发送POST 请求,而且请求主体中要包含博客文章的内容。若要获取编号为12345 的博客文章,客户端可以向http://www.example.com/api/posts/12345 发送GET 请求。

REST架构API中使用的HTTP请求方法

请求方法目标说明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。

提供多版本支持会增加服务器的维护负担,但在某些情况下,这是不破坏现有部署且能让程序不断发展的唯一方式。

使用Flask提供REST Web服务

使用Flask 创建REST Web 服务很简单。使用熟悉的route() 修饰器及其methods 可选参数可以声明服务所提供资源URL 的路由。处理JSON 数据同样简单,因为请求中包含的JSON 数据可通过request.json 这个Python 字典获取,并且需要包含JSON 的响应可以使用Flask 提供的辅助函数jsonify() 从Python 字典中生成。

创建API蓝本

REST API 相关的路由是一个自成一体的程序子集,所以为了更好地组织代码,我们最好把这些路由放到独立的蓝本中。这个程序API 蓝本的基本结构如示例14-1 所示。

示例1 API 蓝本的结构

|-flasky
    |-app/
        |-api_1_0
            |-__init__.py
            |-users.py
            |-posts.py
            |-comments.py
            |-authentication.py
            |-errors.py
            |-decorators.py

注意,API 包的名字中有一个版本号。如果需要创建一个向前兼容的API 版本,可以添加一个版本号不同的包,让程序同时支持两个版本的API。

在这个API 蓝本中,各资源分别在不同的模块中实现。蓝本中还包含处理认证、错误以及提供自定义修饰器的模块。蓝本的构造文件如示例2 所示。

示例2 app/api_1_0/__init__.py:API 蓝本的构造文件

from flask import Blueprint

api = Blueprint('api', __name__)

from . import authentication, posts, users, comments, errors

注册API 蓝本的代码如示例3 所示。

示例14-3 app/_init_.py:注册API 蓝本

def create_app(config_name):
    # ...
    from .api_1_0 import api as api_1_0_blueprint
    app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
    # ...

错误处理

REST Web 服务将请求的状态告知客户端时,会在响应中发送适当的HTTP 状态码,并将额外信息放入响应主体。客户端能从Web 服务得到的常见状态码如表2 所示。

表2 API返回的常见HTTP状态码

HTTP状态码名称说明
200OK(成功)请求成功完成
201Created(已创建)请求成功完成并创建了一个新资源
400Bad request(坏请求)请求不可用或不一致
401Unauthorized(未授权)请求未包含认证信息
403Forbidden(禁止)请求中发送的认证密令无权访问目标
404Notfound(未找到)URL 对应的资源不存在
405Method not allowed(不允许使用的方法)指定资源不支持请求使用的方法
500Internal server error(内部服务器错误)处理请求的过程中发生意外错误

处理404 和500 状态码时会有点小麻烦,因为这两个错误是由Flask 自己生成的,而且一般会返回HTML 响应,这很可能会让API 客户端困惑。

为所有客户端生成适当响应的一种方法是,在错误处理程序中根据客户端请求的格式改写响应,这种技术称为内容协商。示例4 是改进后的404 错误处理程序,它向Web 服务客户端发送JSON 格式响应,除此之外都发送HTML 格式响应。500 错误处理程序的写法类似。

示例4 app/main/errors.py:使用HTTP 内容协商处理错误

@main.app_errorhandler(404)
def page_not_found(e):
    if request.accept_mimetypes.accept_json and \
        not request.accept_mimetypes.accept_html:
        response = jsonify({'error': 'not found'})
        response.status_code = 404
        return response
    return render_template('404.html'), 404

这个新版错误处理程序检查Accept 请求首部(Werkzeug 将其解码为request.accept_mimetypes),根据首部的值决定客户端期望接收的响应格式。浏览器一般不限制响应的格式,所以只为接受JSON 格式而不接受HTML 格式的客户端生成JSON 格式响应。

其他状态码都由Web 服务生成,因此可在蓝本的errors.py 模块作为辅助函数实现。示例5 是403 错误的处理程序,其他错误处理程序的写法类似。

示例5 app/api_1_0/errors.py:API 蓝本中403 状态码的错误处理程序

def forbidden(message):
    response = jsonify({'error': 'forbidden', 'message': message})
    response.status_code = 403
    return response
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值