应用程序编程接口(API)

我为此应用程序构建的所有功能都只适用于特定类型的客户端:Web浏览器。 但其他类型的客户端呢? 例如,如果我想构建Android或iOS APP,有两种主流方法可以解决这个问题。 最简单的解决方案是构建一个简单的APP,仅使用一个Web视图组件并用Microblog网站填充整个屏幕,但相比在设备的Web浏览器中打开网站,这种方案几乎没有什么卖点。 一个更好的解决方案(尽管更费力)将是构建一个本地APP,但这个APP如何与仅返回HTML页面的服务器交互呢?

这就是应用程序编程接口(API)的能力范畴了。 API是一组HTTP路由,被设计为应用程序中的低级入口点。与定义返回HTML以供Web浏览器使用的路由和视图函数不同,API允许客户端直接使用应用程序的资源,从而决定如何通过客户端完全地向用户呈现信息。 例如,Microblog中的API可以向用户提供用户信息和用户动态,并且它还可以允许用户编辑现有动态,但仅限于数据级别,不会将此逻辑与HTML混合。

如果你研究了应用程序中当前定义的所有路由,会注意到其中的几个符合我上面使用的API的定义。 找到它们了吗? 我说的是返回JSON的几条路由,比如第十四章中定义的/translate路由。 这种路由的内容都以JSON格式编码,并在请求时使用POST方法。 此请求的响应也是JSON格式,服务器仅返回所请求的信息,客户端负责将此信息呈现给用户。

虽然应用程序中的JSON路由具有API的“感觉”,但它们的设计初衷是为支持在浏览器中运行的Web应用程序。 设想一下,如果智能手机APP想要使用这些路由,它将无法使用,因为这需要用户登录,而登录只能通过HTML表单进行。 在本章中,我将展示如何构建不依赖于Web浏览器的API,并且不会假设连接到它们的客户端的类型。

本章的GitHub链接为:BrowseZipDiff.

REST API设计风格

REST as a Foundation of API Design

有些人可能会强烈反对上面提到的/translate和其他JSON路由是API路由。 其他人可能会同意,但也会认为它们是一个设计糟糕的API。 那么一个精心设计的API有什么特点,为什么上面的JSON路由不是一个好的API路由呢?

你可能听说过REST API。 REST(Representational State Transfer)是Roy Fielding在博士论文中提出的一种架构。 该架构中,Dr. Fielding以相当抽象和通用的方式展示了REST的六个定义特征。

除了Dr.Fielding的论文外,没有关于REST的权威性规范,从而留下了许多细节供读者解读。 一个给定的API是否符合REST规范的话题往往是REST“纯粹主义者”之间激烈争论的源头,REST“纯粹主义者”认为REST API必须以非常明确的方式遵循全部六个特征,而不像REST“实用主义者”那样,仅仅将Dr. Fielding在论文中提出的想法作为指导原则或建议。Dr.Fielding站在纯粹主义阵营的一边,并在博客文章和在线评论中的撰写了一些额外的见解来表达他的愿景。

目前实施的绝大多数API都遵循“实用主义”的REST实现。 包括来自Facebook,GitHub,Twitter等“大玩家”的大部分API都是如此。很少有公共API被一致认为是纯REST,因为大多数API都没有包含纯粹主义者认为必须实现的某些细节。 尽管Dr. Fielding和其他REST纯粹主义者对评判一个API是否是REST API有严格的规定,但软件行业在实际运用中引用REST是很常见的。

为了让你了解REST论文中的内容,以下各节将介绍Dr. Fielding列举的六项原则。

客户端-服务器

客户端-服务器原则相当简单,正如其字面含义,在REST API中,客户端和服务器的角色应该明确区分。 在实践中,这意味着客户端和服务器都是单独的进程,并在大多数情况下,使用基于TCP网络上的HTTP协议进行通信。

分层系统

分层系统原则是说当客户端需要与服务器通信时,它可能最终连接到代理服务器而不是实际的服务器。 因此,对于客户端来说,如果不直接连接到服务器,它发送请求的方式应该没有什么区别,事实上,它甚至可能不知道它是否连接到目标服务器。 同样,这个原则规定服务器兼容直接接收来自代理服务器的请求,所以它绝不能假设连接的另一端一定是客户端。

这是REST的一个重要特性,因为能够添加中间节点的这个特性,允许应用程序架构师使用负载均衡器,缓存,代理服务器等来设计满足大量请求的大型复杂网络。

缓存

该原则扩展了分层系统,通过明确指出允许服务器或代理服务器缓存频繁且相同请求的响应内容以提高系统性能。 有一个你可能熟悉的缓存实现:所有Web浏览器中的缓存。 Web浏览器缓存层通常用于避免一遍又一遍地请求相同的文件,例如图像。

为了达到API的目的,目标服务器需要通过使用缓存控制来指示响应是否可以在代理服务器传回客户端时进行缓存。 请注意,由于安全原因,部署到生产环境的API必须使用加密,因此,除非此代理服务器terminates SSL连接,或者执行解密和重新加密,否则缓存通常不会在代理服务器中完成。

按需获取客户端代码(Code On Demand)

这是一项可选要求,规定服务器可以提供可执行代码以响应客户端,这样一来,就可以从服务器上获取客户端的新功能。 因为这个原则需要服务器和客户端之间就客户端能够运行的可执行代码类型达成一致,所以这在API中很少使用。 你可能会认为服务器可能会返回JavaScript代码以供Web浏览器客户端执行,但REST并非专门针对Web浏览器客户端而设计。 例如,如果客户端是iOS或Android设备,执行JavaScript可能会带来一些复杂情况。

无状态

无状态原则是REST纯粹主义者和实用主义者之间争论最多的两个中心之一。 它指出,REST API不应保存客户端发送请求时的任何状态。 这意味着,在Web开发中常见的机制都不能在用户浏览应用程序页面时“记住”用户。 在无状态API中,每个请求都需要包含服务器需要识别和验证客户端并执行请求的信息。这也意味着服务器无法在数据库或其他存储形式中存储与客户端连接有关的任何数据。

如果你想知道为什么REST需要无状态服务器,主要原因是无状态服务器非常容易扩展,你只需在负载均衡器后面运行多个服务器实例即可。 如果服务器存储客户端状态,则事情会变得更复杂,因为你必须弄清楚多个服务器如何访问和更新该状态,或者确保给定客户端始终由同一服务器处理,这样的机制通常称为粘性会话

再思考一下本章介绍中讨论的/translate路由,就会发现它不能被视为RESTful,因为与该路由相关的视图函数依赖于Flask-Login的@login_required装饰器, 这会将用户的登录状态存储在Flask用户会话中。

统一接口

最后,最重要的,最有争议的,最含糊不清的REST原则是统一接口。 Dr. Fielding列举了REST统一接口的四个特性:唯一资源标识符,资源表示,自描述性消息和超媒体。

唯一资源标识符是通过为每个资源分配唯一的URL来实现的。 例如,与给定用户关联的URL可以是/api/users/,其中是在数据库表主键中分配给用户的标识符。 大多数API都能很好地实现这一点。

资源表示的使用意味着当服务器和客户端交换关于资源的信息时,他们必须使用商定的格式。 对于大多数现代API,JSON格式用于构建资源表示。 API可以选择支持多种资源表示格式,并且在这种情况下,HTTP协议中的内容协商选项是客户端和服务器确认格式的机制。

自描述性消息意味着在客户端和服务器之间交换的请求和响应必须包含对方需要的所有信息。 作为一个典型的例子,HTTP请求方法用于指示客户端希望服务器执行的操作。 GET请求表示客户想要检索资源信息,POST请求表示客户想要创建新资源,PUTPATCH请求定义对现有资源的修改,DELETE 表示删除资源的请求。 目标资源被指定为请求的URL,并在HTTP头,URL的查询字符串部分或请求主体中提供附加信息。

超媒体需求是最具争议性的,而且很少有API实现,而那些实现它的API很少以满足REST纯粹主义者的方式进行。由于应用程序中的资源都是相互关联的,因此此要求会要求将这些关系包含在资源表示中,以便客户端可以通过遍历关系来发现新资源,这几乎与你在Web应用程序中通过点击从一个页面到另一个页面的链接来发现新页面的方式相同。理想情况下,客户端可以输入一个API,而不需要任何有关其中的资源的信息,就可以简单地通过超媒体链接来了解它们。但是,与HTML和XML不同,通常用于API中资源表示的JSON格式没有定义包含链接的标准方式,因此你不得不使用自定义结构,或者类似JSON-APIHAL JSON-LD这样的试图解决这种差距的JSON扩展之一。

实现API Blueprint

为了让你体验开发API所涉及的内容,我将在Microblog添加API。 我不会实现所有的API,只会实现与用户相关的所有功能,并将其他资源(如用户动态)的实现留给读者作为练习。

为了保持组织有序,并遵循我在第十五章中描述的结构, 我将创建一个包含所有API路由的新blueprint。 所以,让我们从创建blueprint所在的目录开始:

1 (venv) $ mkdir app/api

在blueprint的__init__.py文件中创建blueprint对象,这与应用程序中的其他blueprint类似:

app/api/__init__.py: API blueprint 构造器。

1 from flask import Blueprint
2 
3 bp = Blueprint('api', __name__)
4 
5 from app.api import users, errors, tokens

你可能会记得有时需要将导入移动到底部以避免循环依赖错误。 这就是为什么app/api/users.pyapp/api/errors.pyapp/api/tokens.py模块(我还没有写)在blueprint创建之后导入的原因。

API的主要内容将存储在app/api/users.py模块中。 下表总结了我要实现的路由:

HTTP 方法 资源 URL 注释
GET /api/users/ 返回一个用户
GET /api/users 返回所有用户的集合
GET /api/users//followers 返回某个用户的粉丝集合
GET /api/users//followed 返回某个用户关注的用户集合
POST /api/users 注册一个新用户
PUT /api/users/ 修改某个用户

现在我要创建一个模块的框架,其中使用占位符来暂时填充所有的路由:

app/api/users.py:用户API资源占位符。

 1 from app.api import bp
 2 
 3 @bp.route('/users/<int:id>', methods=['GET'])
 4 def get_user(id):
 5     pass
 6 
 7 @bp.route('/users', methods=['GET'])
 8 def get_users():
 9     pass
10 
11 @bp.route('/users/<int:id>/followers', methods=['GET'])
12 def get_followers(id):
13     pass
14 
15 @bp.route('/users/<int:id>/followed', me
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值