快速上手

Welcome

from flask import Flask
from flask_restaction import Api

app = Flask(__name__)
# 创建一个 Api 对象,把 app 作为参数
api = Api(app)

# 创建 Welcome 类,描述欢迎信息(框架可以序列化任意类型的对象)
class Welcome:

    def __init__(self, name):
        self.name = name
        self.message = "Hello %s, Welcome to flask-restaction!" % name

# 创建一个 Hello 类,定义 get 方法
class Hello:
    """Hello world"""

    # 在 get 方法文档字符串中描述输入参数和输出的格式
    def get(self, name):
        """
        Get welcome message

        $input:
            name?str&default="world": Your name
        $output:
            message?str: Welcome message
        """
        return Welcome(name)

# 添加资源
api.add_resource(Hello)
# 配置API文档的访问路径
app.route('/')(api.meta_view)

if __name__ == '__main__':
    app.run(debug=True)

保存为 hello.py, 然后运行:

$ python hello.py
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader

打开浏览器,访问 http://127.0.0.1:5000/hello:

{
  "message": "Hello world, Welcome to flask-restaction!"
}

再访问 http://127.0.0.1:5000/hello?name=kk

你将会看到:

{
  "message": "Hello kk, Welcome to flask-restaction!"
}

访问 http://127.0.0.1:5000 可以查看自动生成的文档。

两个概念

Resource
资源,比如这里的 Hello 类,表示一类资源。
Action
操作,例如 get, post, delete, get_list, post_login。 只要是 HTTP 方法或 HTTP 方法加下划线 _ 开头就行。

校验输入输出

在Api的参数 Api(docs=__doc__) 中用 $shared 描述全局共享的Schema。

在Resource的文档字符串中用 $shared 描述Resource内共享的Schema。

在Action的文档字符串中用 $input, $output 描述输入输出Schema, 用 $error 描述可能返回的错误。

$input
输入格式,如果没有$input,则不校验输入,以无参数的形式调用Action。 实际数据来源取决于HTTP方法,GET和DELETE请求,取自url参数, POST,PUT和PATCH请求,取自请求体,Content-Type为application/json
$output
输出格式,如果没有$output,则不校验输出。
$error

描述可能返回的错误,仅作为API文档,例如:

$error:
    400.InvalidData: 输入参数错误
    403.PermissionDeny: 权限不足

格式为: status.Error: message

请求参数校验失败会返回:

{
    "status": 400,
    "error": "InvalidData",
    "message": "xxx xxxx"
}

响应内容校验失败会返回:

{
    "status": 500,
    "error": "ServerError",
    "message": "xxx xxxx"
}

Schema为YAML格式的字符串,语法见Schema语法

自定义校验器

在Validr的文档中讲述了自定义校验器的用法。

所有自定义的校验器通过Api(validators=validators)进行注册。

更多内容请移步Validr

添加资源

使用 Api.add_resource 方法添加资源,传给 add_resource 的参数都会原封不动的传给Resource的 __init__ 方法。

路由路径是和Resource名称相同的,如果需要指定不同的路径,可以通过创建一个新Resource实现:

api.add_resource(type('NewName', (MyResource,), {}))

一个Resource可能要依赖其他对象,或者是依赖于网络上的另一个API。 使用依赖注入的方式为Resource提供依赖,而不是使用全局变量。

例如,User依赖于其他对象:

class User:

    def __init__(self, dependecy):
        self.dependecy = dependecy

dependecy = Xxx()
api.add_resource(User, dependecy=dependecy)

构建 URL

可以使用 flask 中的 url_for() 函数构建指定 action 的 URL。

endpoint (url_for 的参数) 是 resource@action_name

resource
Resource类名称的小写
action_name
Action的后半部分(下划线分隔)

格式:

url_for("resource@action_name") -> /resource/action_name

示例:

url_for("hello") -> /hello
url_for("hello@login") -> /hello/login

返回错误信息

from flask_restaction import abort

# 函数原型
abort(code, error=None, message=None)

如果没有error参数,效果和 flask.abort(code) 一样。 如果有error是 flask.Response 类型,效果和 flask.abort(code, error) 一样。

其他情况返回内容为:

{
    "status": code,
    "error": error,
    "message": message
}

返回内容会序列化为适当的格式。

权限管理

这里的实现适用于许多不需要非常灵活的权限系统的应用,方便实用为主,角色和权限通过一份JSON文件 配置即可使用。你也可以使用 flask-login 等Flask插件,总之Flask里能用的这里一样都可以使用。

举个栗子

meta.json 设定角色和权限

{
    "$roles": {
        "admin": {
            "hello": ["get", "post"],
            "user": ["post"]
        },
        "guest": {
            "user": ["post"]
        }
    }
}

init.py 根据token确定角色

from flask_restaction import Api, TokenAuth

api = Api(metafile='meta.json')
auth = TokenAuth(api)

@auth.get_role
def get_role(token):
    if token:
        return token["role"]
    else:
        return "guest"

hello.py 业务代码

class Hello:

    def get(self):
        pass

    def post(self):
        pass

user.py 登录接口

from flask import g

class User:

    def __init__(self, api):
        self.api = api

    def post(self, username, password):
        # query user from database
        g.token = {"id": user.id, "role": user.role}
        return user

使用情景

用户直接请求 hello.get 接口,框架收到请求后,从请求头的 Authorization 中取 token , 此时 tokenNone,然后框架调用 get_role(None) ,得到角色 guest ,再判断 meta["$roles"]["guest"]["hello"] 中有没有 get,发现没有,框架直接拒绝此次请求。

用户请求 user.post 接口,处理流程同上,请求到达 user.post 方法,验证用户名和密码,如果验证成功,就设置 g.tokentoken 里面保存了用户ID,角色和过期时间。TokenAuth会将 g.token 用JWT进行签名, 然后通过响应头的 Authorization 返回给用户。

用户再次请求 hello.get 接口,在请求头的 Authorization 中带上了刚才得到的 token, 处理流程同上,框架允许此次请求,请求到达 hello.get 方法。

示意图

diagram

分步说明

1. 在 metafile 中设定角色和权限

metafile是一个描述API信息的文件,通常放在应用的根目录下,文件名 meta.json。

在Api初始化的时候通过 Api(metafile="meta.json") 加载。

{
    "$roles": {
        "Role": {
            "Resource": ["Action", ...]
        }
    }
}

请求到来时,根据 Role, Resource, Action 可以快速确定是否许可此次请求。

提示

Flask的Development Server不能检测到python代码文件之外变动,所以修改metafile的内容之后需要手动重启才能生效。

注册 get_role 函数

框架通过URL能解析出Resource, Action,但是无法知道用户是什么角色, 所以需要你提供一个能返回用户角色的函数。

生成 token

为了能够确认用户的身份,需要在用户登录成功后生成一个 token,将 token 通过响应头(Authorization)返回给用户。 token 一般会储存用户ID和过期时间,用户在发送请求时需要将 token 通过请求头发送给服务器。

TokenAuth使用 json web token 作为身份验证工具。

提示

token 会用密钥(app.secret_key)对 token 进行签名,无法篡改。
生成 token 前需要先设置 app.secret_key,或通过 flask 配置。
token 是未加密的,不要把敏感信息保存在里面。

身份/权限验证失败会返回:

{
    "status": 403,
    "error": "PermissionDeny",
    "message": "xxx can't access xxxx"
}

安全性和设置

对安全性要求不同,权限管理的实现也会不同,TokenAuth的实现适用于对安全性要求不高的应用。

当收到请求时,检测到token即将过期,会主动颁发一个新的token给客户端,这样能避免token过期 导致中断用户正常使用的问题。但这样也导致token能够被无限被刷新,有一定的安全隐患。

以下是默认设置:

{
    "$auth": {
          "algorithm": "HS256",     # token签名算法
          "expiration": 3600,       # token存活时间,单位为秒
          "header": "Authorization" # 用于传递token的请求/响应头
          "cookie": null            # 用于传递token的cookie名称, 默认不用cookie
          "refresh": true           # 是否主动延长token过期时间
    }
}

自定义权限管理

Api.authorize(role) 方法能根据 $roles 和请求URL判断该角色是否有权限调用API, 利用它可以简化自定义权限管理实现。

以下是基本结构,具体实现可以参考 flask_restaction/auth.py:

class MyAuth:

    def __init__(self, api):
        self.api = api
        self.config = api.meta["$auth"]
        api.before_request(self.before_request)
        api.after_request(self.after_request)

    def before_request(self):
        """Parse request, check permission"""
        # parse role from request
        self.api.authorize(role)

    def after_request(self, rv, status, headers):
        """Modify response"""
        return rv, status, headers

API文档

文档截图

有两种方式配置API文档的访问路径。

Flask.route

app.route('/')(api.meta_view)

Api.add_resource

这种方式把文档作为一种资源添加到API中,可以方便的控制文档的访问权限。

# 允许用cookie传递token
{
    "$auth": {
          "cookie": "Authorization"
    }
}
# add_resource
api.add_resource(type('Docs', (), {'get': api.meta_view}))

Api.meta_view也能返回JSON格式的API元数据,只需要设置请求头 Acceptapplication/json 即可。

使用蓝图

Api可以放在蓝图中,这样所有的 Resource 都会路由到蓝图中。

from flask import Flask, Blueprint
from flask_restaction import Api

app = Flask(__name__)
bp = Blueprint('api', __name__)
api = Api(bp)
api.add_resource(XXX)
app.register_blueprint(bp)

注意: add_resource 需要在 register_blueprint 之前执行,否则 add_resource 无效。

事件处理

Api提供before_request, after_request, error_handler这3个装饰器用来注册事件处理函数。

@api.before_request
def before_request():
    # 此函数会在在请求到来的第一时间执行
    # 若response不为None,则不再继续处理请求
    return response

@api.after_request
def after_request(rv, status, headers):
    # 此处可以对Action中的返回值进行处理
    return rv, status, headers

@api.error_handler
def error_handler(ex):
    # 处理从before_request到Action过程中抛出的异常
    # 若response不为None,则返回此response给客户端
    return response

自定义响应格式

默认响应格式为JSON,你也可以很方便的添加自定义的响应格式。

from flask import make_response
from flask_restaction import exporter

@exporter('text/html')
def export_text(data, status, headers):
    return make_response(str(data), status, headers)

框架会根据请求头中Accept的值选择合适的响应格式。

使用 res.js

在API文档页面打开控制台即可使用 res.js。

如果API路径不是网站根路径,则需要配置 API_URL_PREFIX,用于生成API文档中显示的URL 和res.js调用API的URL,这项配置不会影响API的真实路径。

例如: http://127.0.0.1:5000/api

app.config["API_URL_PREFIX"] = "/api"

详细用法见resjs

使用 res.py

res.py 的用法类似于 res.js,网络请求用的是Requests库。

>>> from flask_restaction import Res
>>> help(Res)