10.5. 摄像头的控制 API

拥有者需要从任何地方设置运动检测器的灵敏度——刮风的日子里,风会让树木晃动得更厉害。这意味着需要一些路由,让仪表盘能从中读取当前设置,并向其提交更改。

在模块上放一个小小的共享状态字典就足以保存这些旋钮设置。后面的页面会向其中添加更多键;现在只有一个:

state = {
    'threshold': 12,
    'frame_count': 0,
    'trigger_count': 0,
}

10.5.1. 用 GET 读取,用 POST 写入

一对路由——一个 get、一个 post——让仪表盘对 state 拥有读/写访问权限:

from microdot import abort

@app.get('/config')
async def get_config(request):
    return state

@app.post('/config')
async def set_config(request):
    body = request.json
    if not body or 'threshold' not in body:
        abort(400, 'missing threshold')
    try:
        threshold = int(body['threshold'])
    except (TypeError, ValueError):
        abort(400, 'threshold must be an integer')
    if not 0 <= threshold <= 100:
        abort(400, 'threshold out of range')
    state['threshold'] = threshold
    return {'ok': True, 'threshold': threshold}

microdot.Request.json 会返回解析为 JSON 的请求主体,如果 Content-Type 不是 application/json 则返回 Nonepost 处理函数会逐一检查每种失败模式——缺少键、类型错误、超出范围——并通过 microdot.abort() 退出,后者会抛出 microdot.HTTPException,用给定的状态码和消息使处理函数短路。

10.5.2. GET、POST、PUT、DELETE

get()post() 是我们用得最多的两个。put()delete() 用于遵循 REST 约定的场景——用 PUT /events/42 替换事件 42,用 DELETE /events/42 删除它。处理函数在其他方面是相同的。

10.5.3. 读取查询字符串和表单

仪表盘提交的是 JSON,所以 request.json 正是我们想要的。摄像头还有另外两种接收数据的方式:

  • args——查询字符串。?foo=1&bar=2 会变成一个 microdot.MultiDict,你可以用 request.args.get('foo') 来读取它。

  • form——一个以 application/x-www-form-urlencoded 提交的 HTML 表单。同样是 MultiDict 类型。

MultiDict 与字典类似,但允许一个键携带多个值(?tag=cat&tag=dog 就是两个 tag 值);完整接口请参阅 microdot.MultiDict

10.5.4. 动态 URL 段

路由路径可以声明带类型的占位符,microdot 会把它们作为额外参数传给处理函数:

@app.get('/events/<int:event_id>')
async def get_event(request, event_id):
    return {'id': event_id, 'msg': 'placeholder'}

支持的转换器有:<int:>、用于自定义正则表达式的 <re:>、用于可包含斜杠的段的 <path:>,以及默认(无前缀)的“匹配直到下一个斜杠之前的任意内容”。<int:event_id> 接受 /events/42 而拒绝 /events/abc——被拒绝的请求会变成一个 404,处理函数不会运行。

10.5.5. 自定义错误响应

microdot 默认发送的 404 是纯文本 Not found。仪表盘期望每个响应都是 JSON;请重写 404 处理函数,让它也返回 JSON:

@app.errorhandler(404)
async def not_found(request):
    return {'error': 'not found', 'path': request.path}, 404

errorhandler() 既可以接受一个状态码(捕获该状态的每一个错误),也可以接受一个异常类(捕获每一个抛出该异常的处理函数)。(body, status) 元组会使响应短路,而无需构造一个 Response

现在摄像头对外暴露了它的状态,并接受编辑。