Python3 API 的封装及调用

一、API 的封装

API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

Python3 的接口就是WSGI:Web Server Gateway Interface。

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的Hello, web!

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求信息的dict对象;

  • start_response:一个发送HTTP响应的函数。

在application()函数中,调用:

start_response('200 OK', [('Content-Type', 'text/html')])

就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示。

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

然后,函数的返回值b'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。

有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的bytes也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。

Python3 内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

运行WSGI服务

我们先编写hello.py,实现Web应用程序的WSGI处理函数:

# hello.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h1>Hello, web!</h1>']

然后,再编写一个server.py,负责启动WSGI服务器,加载application()函数:

# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application

# 创建一个服务器,IP地址为空,端口是8080,处理函数是application:
httpd = make_server('', 8080, application)
print('Serving HTTP on port 8080...')
# 开始监听HTTP请求:
httpd.serve_forever()

运行结果:

  • 启动 WSGI

    Geek-Mac:Downloads zhangyi$ python3 server.py 
    Serving HTTP on port 8080...
  • 访问 API

    Geek-Mac:Downloads zhangyi$ curl "localhost:8080/"
    <h1>Hello, web!</h1>Geek-Mac:Downloads zhangyi$

这是最简单的 WSGI 应用,接下里写一个在日常工作中自己测试API的简易程序

实例

api.py

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import json
# 导入 urllib.parse 模块的 parse_qs 方法
from urllib.parse import parse_qs
from wsgiref.simple_server import make_server


def api(app):

    def application(environ, start_response):

        start_response('200 OK', [('Content-Type', 'text/html')])
        # 获取当前get请求的所有数据,返回是string类型
        parse = parse_qs(environ['QUERY_STRING'])
        data = json.dumps(app(parse))

        # 返回值是 UTF-8
        return [data.encode('utf-8')]

    def run():

        httpd = make_server('', 8080, application)
        print("Serving HTTP on port 8080 ...")
        httpd.serve_forever()

    # 外部模块调用才执行
    if __name__ != "__main__":
        run()

app.py

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

# API 调用实例
# http://127.0.0.1:8080/?name=Geek&user=root

# 导入 api 模块的 api 方法,当然也可以将 api 模块做成一个包,方便调用
from api import api


# 使用装饰器的形式调用函数
@api
def app(parse):

    # 使用 urllib.parse 模块的 parse_qs.get 方法获取 url 中字段的值
    name = parse.get('name', [''])[0]
    user = parse.get('user', [''])[0]

    # 将获取的值写到字典中
    data = {'name': name, 'user': user}

    return data

运行结果:

  • 启动 WSGI

    Geek-Mac:API zhangyi$ python3 app.py 
    Serving HTTP on port 8080 ...
  • 访问 API

    Geek-Mac:API zhangyi$ curl "http://127.0.0.1:8080/?name=Geek&user=root"
    {"name": "Geek", "user": "root"}Geek-Mac:API zhangyi$

使用装饰器的方法最大好处是降低了程序的复杂度,我们只需要关心自己写的函数 app 即可,最后在函数上方使用一个 @api 就可以使程序直接运行,大大加快了开发效率。

二、API 的调用

API 的调用可以使用 Python3 自带的模块 urllib,这里介绍一种更为好用的第三方模块 requests

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

# 安装requests ==> sudo pip3 install requests

import requests


# 调用 API 的 GET 请求
URL = "http://127.0.0.1:8080/?name=Geek&user=root"
data = requests.get(url = URL)

# 获取返回状态
print (data.status_code)
# 获取返回URL
print (data.url)
# 获取返回数据
print (data.text)
# 获取返回 json 解析数据
print (data.json())

运行结果:

Geek-Mac:Downloads zhangyi$ python3 API.py 
200
http://127.0.0.1:8080/?name=Geek&user=root
{"name": "Geek", "user": "root"}
{'name': 'Geek', 'user': 'root'}

这只是 requests,最简单的 GET 用法,还有 POST 用法、字典传参等其他更详细的用法请移步官网:http://cn.python-requests.org/zh_CN/latest/

三、API 编写规范 REST

可以总结为一句话:REST是所有Web应用都应该遵守的架构设计指导原则。

Representational State Transfer,翻译是“表现层状态转化”,维基百科有详细说明:https://zh.wikipedia.org/wiki/具象状态传输。 

面向资源是REST最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。(7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)

什么是RESTful API?

符合REST架构设计的API。

总结

符合REST设计标准的API,即RESTful API。REST架构设计,遵循的各项标准和准则,就是HTTP协议的表现,换句话说,HTTP协议就是属于REST架构的设计模式。比如,无状态,请求-响应。

四、RESTful API 设计指南

转载自大神阮一峰的博客:http://www.ruanyifeng.com/blog/2014/05/restful_api

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。

因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。

今天,我将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API。

一、协议

API与用户的通信协议,总是使用HTTPs协议。

二、域名

应该尽量将API部署在专用域名之下。

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/

三、版本(Versioning)

应该将API的版本号放入URL。

https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

四、路径(Endpoint)

路径又称"终点"(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

五、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。

HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

六、过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

七、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里:https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

八、错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{
    error: "Invalid API key"
}

九、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

十、Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

十一、其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

猜你喜欢

转载自blog.csdn.net/onebound_linda/article/details/131914900