Python让你的Web应用程飞起来全家桶之Sanic

2,428 阅读3分钟

一、前言

为什么要用Sanic,请见 让你的Python(Web应用)飞起来,(异步/协程)全家桶

废话不多讲,直接进入正题,我会按照正常的Web应用程序架构来讲Sanic的用法

二、进入正题

基本操作

  1. 安装Sanic

pip install sanic

  1. 导入Sanic
from sanic import Sanic
app = Sanic(__name__)

中间件(Middleware And Listeners)

服务启动之前

@app.listener('before_server_start')
async def start_connection(app, loop):
    # 执行一些初始化任务
    # 例如初始化 Redis 连接池
    await RedisPool.init_redis_conn_pool(loop)

服务停止之前

@app.listener('after_server_stop')
async def start_connection(app, loop):
    # 执行一些备份、应急、关闭任务
    # 例如关闭连接池
    await RedisPool.close_redis_conn_pool()
    await close_connection(app, loop)

请求(Request)中间件

@app.middleware('request')
async def add_tokrn_to_request(request):
    # 每次请求会经过此方法,可以做一些对request的处理
    # 例如添加token,secret,token
    await session_interface.open(request)

响应(Response)中间件

@app.middleware('response')
async def save_session(request, response):
    logger = logging.getLogger('response')
    # 每次服务器响应会经过此方法,可以做一些异常,cookie,logger等的处理
    # 例如写入cookie,记录用户日志
    await session_interface.save(request, response)

蓝图(Blueprint)

类似Flask,用来组织应用程序,也就是大多数我们mvc模式中的controller,

  1. 首先声明一个BP
order_bp = Blueprint('orders', url_prefix='v1/api/order')
# url_prefix:定义路由修正,
# 例如当前蓝图下你定义了一个 query/id 的路由,那么你应该这样访问它 v1/api/order/query/id
  1. 在主程序中(main.py)引用
from controller.order_controller import order_bp
app.blueprint(order_bp)
# 这样order_bp就被注册到sanic中,可以通过浏览器 http://example.com/v1/api/order/query/id 访问
  1. 使用蓝图处理异常
@bp.exception(NotFound)
def ignore_404(request, exception):
    return text("Yep, I totally found the page: {}".format(request.url))

路由(Routing)

# GET 路由参数的方式
@order_bp.route('/delete/<oid:int>', methods=['GET'])
async def delete_order(request, oid):
    result = await OrderModel().del_order_by_id(oid)
    if result:
        return json({'status': 'ok', 'result': result})
    return json({'status': 'fail', 'errmsg': '获取数据失败'})
# Get params的方式
@service_bp.route('/message/send', methods=['GET'])
async def send_message(request):
    uuid = request.args['uuid'][0]
    page = request.args['page'][0]
    result = await MessageService().send_message_2_user(uuid,page)
    if result:
        return json({'status': 'ok', 'result': result})
    return json({'status': 'fail', 'errmsg': '获取数据失败'})

    return raw(result)
# 修改价格
@order_bp.route('/price/modify', methods=['POST'])
async def price_modify(request):
    order_id = request.json['order_id']
    price = request.json['price']
    postage = request.json['postage']
    show_price = request.json['show_price']
    is_shipping = request.json['is_shipping']
    result = await OrderModel().modify_price(order_id, price, postage, show_price, is_shipping)

    if result:
        return json({'status': 'ok', 'result': result})
    return json({'status': 'fail', 'errmsg': '获取数据失败'})

响应类型

from sanic.response import json
# text
response.text('hello world')
# html
response.html('<p>hello world</p>')
# json
response.json({'hello': 'world'})
# file
response.file('/srv/www/hello.txt')
# stream
response.stream(stream_ctx, content_type='text/plain')

如果我想在Sanic启动之后额外运行任务怎么办?

app.add_task(notify_server_started())
app.add_task(notify_html_spider())

三、启动Sanic服务

if __name__ == "__main__":
    app.add_task(notify_server_started())
    app.add_task(notify_html_spider())
    app.run(host="0.0.0.0", port=settings.PORT, workers=settings.workers, debug=settings.DEBUG, access_log=True)

在启动时可以传入一些参数,其他的都是字面意思,很好理解,这里讲一下 Workers

workers 接受 integer 类型,默认为1,传入4意味着Sanic会为你复制四份,创建四个进程来运行你的Sanic App

如图

多进程状态下,路由会进行自动分配,从而增加吞吐量,workers的个数依据自身服务器性能而定,如果创建太多会出现占用情况,同样多进程模式下,你的数据库连接或者数据库连接池的数量也要适当调大,否则会出现连接数过多而拒绝连接的情况

四、获奖感言

Sanic的介绍就这么多,只是一个基本入门,至于其他类似订阅发布监听等等功能大家可以移步 Sanic官网 进行学习和查阅

下期我可能会联合Sanic讲解关于异步Redis(aioredis)的使用,例子打算用类似订单倒计时的功能,结合redis的订阅发布通知来讲