zoukankan      html  css  js  c++  java
  • Sanic

    Sanic问题

    1.什么是Web框架?

    2.为什么要用Web框架?

    3.在Python中常用的Web框架有

    django flask tornado sanic

    Sanic

    简介

    Sanic是一个类Flask的基于Python3.5以上的Web框架,它除了与Flask功能类似外,它还支持异步请求处理,是一个异步协程框架。这意味着你可以使用async/await语法,编写非阻塞的快速代码,而且使你的程序运行速度加快。

    关于asyncio包的介绍:使用asyncio处理并发

    官方提供的速度对比:

    img

    产生原因:

    使用较早的异步框架是aiohttp,它提供了server端和client端,对asyncio做了很好的封装。但是开发方式和最流行的微框架flask不同,flask开发简单,轻量,高效。将两者结合起来就有了sanic。

    微服务是近几年比较火的开发模式,它解决了复杂性问题,提高开发效率,便于部署等优点,正是结合这些优点, 以Sanic为基础,集成多个流行的库来搭建微服务。

    uvloop

    uvloop 是 asyncio 默认事件循环的替代品,实现的功能完整,切即插即用。uvloop是用CPython 写的,建于libuv之上。uvloop 可以使 asyncio 更快。事实上,它至少比 nodejs、gevent 和其他 Python 异步框架要快两倍 。基于 uvloop 的 asyncio 的速度几乎接近了 Go 程序的速度。

    由于uvloop 还只能在 Linux 平台 和 Python3.5+以上版本使用。所以高性能部分Windows用不了。

    uvloop的安装使用:

    pip install uvloop
    
    import asyncio
    import uvloop
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    

    Flask与Sanic的对比

    架构对比:

    Flask:

    特点对比:

    flask:

    ​ 简单,轻量,高效

    ​ werkzeug(路由模块)Jinja2(模板引擎)

    Werkzeug是一个遵循WSGI协议的python函数库
    其内部实现了很多Web框架底层的东西,比如request和response对象;
    - 与WSGI规范的兼容;支持Unicode;
    - 支持基本的会话管理和签名Cookie;
    - 集成URL请求路由等
    

    ​ Flask-SQLalchemy:操作数据库、

    ​ Flask-script:插入脚本

    ​ Flask-migrate:管理迁移数据库

    ​ Flask-Session:Session存储方式指定

    sanic:

    ​ 简单,轻量,高效,异步

    由werkzeug的DispatcherMiddleware驱动的调度程序

    ​ uvloop为核心引擎,使sanic在很多情况下单机并发甚至不亚于Golang

    ​ asyncpg为数据库驱动,进行数据库连接,执行sql语句执行

    ​ aiohttp为Client,对其他微服务进行访问

    ​ peewee为ORM,但是只是用来做模型设计和migration

    ​ opentracing为分布式追踪系统

    ​ unittest做单元测试,并且使用mock来避免访问其他微服务

    ​ swagger做API标准,能自动生成API文档

    Sanic使用

    1.简单使用

    1.1安装

    pip install sanic
    

    1.2起步

    from sanic import Sanic
    from sanic.response import json
    
    app = Sanic()
    
    @app.route("/")
    async def test(request):
        return json({"hello": "world"})
    
    if __name__ == "__main__":
        app.run(host="0.0.0.0", port=8000)
    

    2.路由

    request路由规则与flask一致,一看就懂,可以使用变量、正则来设置。

    @app.route('/')
    async def index():
        return text('Index Page')
    
    @app.route('/hello')
    async def hello():
        return text('Hello World')
    

    注意:必须使用async def语法定义函数,来保证其可以进行异步处理.

    2.1请求参数

    Sanic的基础路由支持请求参数的操作,使用尖括号<PARAM>将指定参数括起来,请求参数将作为路由函数的关键字参数。

    from sanic.response import text
    
    @app.route('/tag/<tag>')
    async def tag_handler(request, tag):
        return text('Tag - {}'.format(tag))
    

    注意:也可以指定参数的类型,要在参数名字后面添加:type指定参数类型,如果参数与指定的参数类型不匹配,则Sanic会抛出NotFound的异常,从而导致页面出现404: Page not found的错误。

    from sanic.response import text
    
    @app.route('/number/<integer_arg:int>')
    async def integer_handler(request, integer_arg):
        return text('Integer - {}'.format(integer_arg))
    
    @app.route('/number/<number_arg:number>')
    async def number_handler(request, number_arg):
        return text('Number - {}'.format(number_arg))
    
    @app.route('/person/<name:[A-z]>')
    async def person_handler(request, name):
        return text('Person - {}'.format(name))
    
    @app.route('/folder/<folder_id:[A-z0-9]{0,4}>')
    async def folder_handler(request, folder_id):
        return text('Folder - {}'.format(folder_id))
    

    2.2HTTP 请求类型

    @app.route装饰器接受一个可选的参数methods,它允许定义的函数使用列表中任何一个的HTTP方法。

    from sanic.response import text
    
    @app.route('/post', methods=['POST'])
    async def post_handler(request):
        return text('POST request - {}'.format(request.json))
    
    @app.route('/get', methods=['GET'])
    async def get_handler(request):
        return text('GET request - {}'.format(request.args))
    

    2.3 add_route方法

    路由通常使用@app.route装饰器进行添加的。但是,这个装饰器只是app.add_route方法的一个封装。

    from sanic.response import text
    
    # Define the handler functions
    async def handler1(request):
        return text('OK')
    
    async def handler2(request, name):
        return text('Folder - {}'.format(name))
    
    async def person_handler2(request, name):
        return text('Person - {}'.format(name))
    
    # Add each handler function as a route
    app.add_route(handler1, '/test')
    app.add_route(handler2, '/folder/<name>')
    app.add_route(person_handler2, '/person/<name:[A-z]>', methods=['GET'])
    

    3.请求

    当接收端接收到一个HTTP请求的时候,路由函数就会传递一个Request对象

    不像Flask 一样提供一个全局变量 request

    Flask 是同步请求,每次请求都有一个独立的新线程来处理,这个线程中也只处理这一个请求。而Sanic是基于协程的处理方式,一个线程可以同时处理几个、几十个甚至几百个请求,把request作为全局变量显然会比较难以处理

    3.1json格式的数据

    from sanic.response import json
    
    @app.route("/json")
    def post_json(request):
        return json({ "received": True, "message": request.json })
    

    3.2arg(dict类型)-查询字符串变量

    from sanic.response import json
    
    @app.route("/query_string")
    def query_string(request):
        return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
    

    3.3files(File对象的字典)-具有名称,正文和类型的文件列表

    from sanic.response import json
    
    @app.route("/files")
    def post_json(request):
        test_file = request.files.get('test')
    
        file_parameters = {
            'body': test_file.body,
            'name': test_file.name,
            'type': test_file.type,
        }
    
        return json({ "received": True, "file_names": request.files.keys(), "test_file_parameters": file_parameters })
    

    3.4 form(dict)-发送的表单数据

    from sanic.response import json
    
    @app.route("/form")
    def post_json(request):
        return json({ "received": True, "form_data": request.form, "test": request.form.get('test') })
    

    3.5

    ip(str类型)-请求者的IP地址

    app-对正在处理此请求的Sanic应用程序对象的引用

    url:完整的请求URL

    scheme:与请求相关联的URL类型,http或是https

    host:与请求相关联的主机

    path:请求的地址

    4.响应

    使用sanic.response模块中的函数来创建响应

    4.1纯文本

    from sanic import response
    
    @app.route('/text')
    def handle_request(request):
        return response.text('Hello world!')
    

    4.2 HTML

    from sanic import response
    
    @app.route('/html')
    def handle_request(request):
        return response.html('<p>Hello world!</p>')
    

    4.3 json

    from sanic import response
    
    @app.route('/json')
    def handle_request(request):
        return response.json({'message': 'Hello world!'})
    

    4.4 文件

    from sanic import response
    
    @app.route('/file')
    async def handle_request(request):
        return await response.file('/srv/www/whatever.png')
    

    4.5 Streaming

    from sanic import response
    
    @app.route("/streaming")
    async def index(request):
        async def streaming_fn(response):
            response.write('foo')
            response.write('bar')
        return response.stream(streaming_fn, content_type='text/plain')
    

    4.6 重定向

    from sanic import response
    
    @app.route('/redirect')
    def handle_request(request):
        return response.redirect('/json')
    

    5.配置

    Sanic将配置保存在config应用程序对象的属性中。配置的是一个可以使用点运算进行修改或是类似字典类型的对象。

    app = Sanic('myapp')
    app.config.DB_NAME = 'appdb'
    app.config.DB_USER = 'appuser'
    
    也可以:(配置的对象实际上是一个字典)
    db_settings = {
        'DB_HOST': 'localhost',
        'DB_NAME': 'appdb',
        'DB_USER': 'appuser'
    }
    app.config.update(db_settings)
    

    5.1加载配置

    # 环境加载
    app = Sanic(load_vars=False)
    
    # 对象加载
    import myapp.default_settings
    
    app = Sanic('myapp')
    app.config.from_object(myapp.default_settings)
    
    # 文件加载
    通常情况下,你想要从文件中加载配置参数。你可以从from_file(/path/to/config_file)来加载配置参数。然而,这需要程序知道配置文件的位置,所以你可以在环境变量中指定配置文件的路径,并让Sanic寻找配置文件并使用配置文件。
    app = Sanic('myapp')
    app.config.from_envvar('MYAPP_SETTINGS')
    然后你可以在MYAPP_SETTINGS环境设置下运行你的应用程序
    $ MYAPP_SETTINGS=/path/to/config_file python3 myapp.py
    INFO: Goin' Fast @ http://0.0.0.0:8000
        
    配置文件是常规的Python文件,运行它们只是为了加载配置。这允许你使用任何正确的逻辑进行正确的配置。只要uppercase变量被添加到配置中,最常见的配置包括简单的键值对
    # config_file
    DB_HOST = 'localhost'
    DB_NAME = 'appdb'
    DB_USER = 'appuser'
    

    6.异常

    # 抛出异常
    # sanic.exceptions中导入与raise相关的异常
    from sanic.exceptions import ServerError
    
    @app.route('/killme')
    def i_am_ready_to_die(request):
        raise ServerError("Something bad happened", status_code=500)
        
    # 处理异常
    # 需要覆盖Sanic对异常的默认处理,就需要使用@app.exception装饰器,该装饰器期望使用一个异常列表来处理参数。你可以传递一个SanicException来捕捉它们。装饰器异常处理函数必须使用Request和Exception对象来作为参数
    from sanic.response import text
    from sanic.exceptions import NotFound
    
    @app.exception(NotFound)
    def ignore_404s(request, exception):
        return text("Yep, I totally found the page: {}".format(request.url))
    
    常用异常:
    NotFound: 找不到合适的路由请求
    ServerError: 服务器内部出现问题时调用
    

    7.中间件和监听

    7.1 中间件

    中间件是在向服务器请求之前或之后执行的功能。它们可用来修改用户自定义处理函数的请求或响应。

    两种不同类型的中间件:请求request和响应response。 都是使用@app.middleware装饰器进行声明的,利用'request'或'response'字符串来表示其参数类型。

    # 不修改任何信息
    @app.middleware('request')
    async def print_on_request(request):
        print("I print when a request is received by the server")
    
    @app.middleware('response')
    async def print_on_response(request, response):
        print("I print when a response is returned by the server")
    
    app = Sanic(__name__)
    
    @app.middleware('response')
    async def custom_banner(request, response):
        response.headers["Server"] = "Fake-Server"
    
    @app.middleware('response')
    async def prevent_xss(request, response):
        response.headers["x-xss-protection"] = "1; mode=block"
    
    app.run(host="0.0.0.0", port=8000)
    

    7.2 监听器

    如果你想要在服务启动或关闭时执行启动/拆卸代码,可以使用以下的监听器:before_server_start,after_server_start,before_server_stop,after_server_stop,监听器在接收app对象和asyncio循环的函数上实现为装饰器

    @app.listener('before_server_start')
    async def setup_db(app, loop):
        app.db = await db_setup()
    
    @app.listener('after_server_start')
    async def notify_server_started(app, loop):
        print('Server successfully started!')
    
    @app.listener('before_server_stop')
    async def notify_server_stopping(app, loop):
        print('Server shutting down!')
    
    @app.listener('after_server_stop')
    async def close_db(app, loop):
        await app.db.close()
    

    8.蓝图

    和flask中的蓝图一样,用于组织项目结构

    from sanic.response import json
    from sanic import Blueprint
    
    bp = Blueprint('my_blueprint')
    
    @bp.route('/')
    async def bp_root():
        return json({'my': 'blueprint'})
    

    8.1注册蓝图

    from sanic import Sanic
    from my_blueprint import bp
    
    app = Sanic(__name__)
    app.blueprint(bp)
    
    app.run(host='0.0.0.0', port=8000, debug=True)
    

    8.2 蓝图中间件

    使用蓝图可以在全局注册中间件

    from sanic import Blueprint
    bp = Blueprint('my_blueprint')
    
    @bp.middleware
    async def print_on_request(request):
        print("I am a spy")
    
    @bp.middleware('request')
    async def halt_request(request):
        return text('I halted the request')
    
    @bp.middleware('response')
    async def halt_response(request, response):
        return text('I halted the response')
    

    8.3 异常

    蓝图来应用全局的异常

    @bp.exception(NotFound)
    def ignore_404s(request, exception):
        return text("Yep, I totally found the page: {}".format(request.url))
    

    8.4 静态文件

    在蓝图定义下提供给全局的静态文件

    bp.static('/folder/to/serve', '/web/path')
    

    8.5 监听

    bp = Blueprint('my_blueprint')
    
    @bp.listener('before_server_start')
    async def setup_connection(app, loop):
        global database
        database = mysql.connect(host='127.0.0.1'...)
    
    @bp.listener('after_server_stop')
    async def close_connection(app, loop):
        await database.close()
    

    注意:如果多进程模式运行(超过1个进程),这些将在进程fork之后被触发。

    8.6 api版本控制

    当蓝图被初始化时,它可以使用一个可选的url_prefix参数,这个参数将被添加到蓝图上定义的所有路由上.

    # blueprints.py
    from sanic.response import text
    from sanic import Blueprint
    
    blueprint_v1 = Blueprint('v1', url_prefix='/v1')
    blueprint_v2 = Blueprint('v2', url_prefix='/v2')
    
    @blueprint_v1.route('/')
    async def api_v1_root(request):
        return text('Welcome to version 1 of our documentation')
    
    @blueprint_v2.route('/')
    async def api_v2_root(request):
        return text('Welcome to version 2 of our documentation')
    

    9.Cookies

    Cookies是持续保存在用户浏览器中的数据片段。Sanic可以读取和写入Cookies,并以键值对的形式保存。

    9.1 读Cookies

    通过Request对象的cookies字典访问访问用户的cookies

    from sanic.response import text
    
    @app.route("/cookie")
    async def test(request):
        test_cookie = request.cookies.get('test')
        return text("Test cookie set to: {}".format(test_cookie))
    

    9.2 写Cookies

    返回一个响应时,可以在Response对象上设置Cookies

    from sanic.response import text
    
    @app.route("/cookie")
    async def test(request):
        response = text("There's a cookie up in this response")
        response.cookies['test'] = 'It worked!'
        response.cookies['test']['domain'] = '.gotta-go-fast.com'
        response.cookies['test']['httponly'] = True
        return response
    

    9.3 删Cookies

    from sanic.response import text
    
    @app.route("/cookie")
    async def test(request):
        response = text("Time to eat some cookies muahaha")
    
        # This cookie will be set to expire in 0 seconds
        del response.cookies['kill_me']
    
        # This cookie will self destruct in 5 seconds
        response.cookies['short_life'] = 'Glad to be here'
        response.cookies['short_life']['max-age'] = 5
        del response.cookies['favorite_color']
    
        # This cookie will remain unchanged
        response.cookies['favorite_color'] = 'blue'
        response.cookies['favorite_color'] = 'pink'
        del response.cookies['favorite_color']
    
        return response
    

    10.基于类的视图

    基于类的视图只是为了实现对响应行为的请求的简单类。它们提供了在同一端点对不同HTTP请求类型进行区分处理的方法。

    基于类的视图是HTTPMethodView的子类。你可以为每个HTTP请求实现你想要的类方法。如果一个请求没有定义方法,一个405:Method not allowed的响应就会生成。

    要在端点上注册基于类的视图,就需要使用app.add_route方法。它的第一个参数是as_view方法定义的类,第二个参数是URL端点。

    from sanic import Sanic
    from sanic.views import HTTPMethodView
    from sanic.response import text
    
    app = Sanic('some_name')
    
    class SimpleView(HTTPMethodView):
    
      def get(self, request):
          return text('I am get method')
    
      async def post(self, request):	#也可以使用异步async语法
          return text('I am post method')
    
      def put(self, request):
          return text('I am put method')
    
      def patch(self, request):
          return text('I am patch method')
    
      def delete(self, request):
          return text('I am delete method')
    
    app.add_route(SimpleView.as_view(), '/')
    
    
    # 也可加装饰器,设置decorators类变量。当调用as_view方法的时候,会应用于类中。
    class ViewWithDecorator(HTTPMethodView):
      decorators = [some_decorator_here]
    
      def get(self, request, name):
        return text('Hello I have a decorator')
    
    app.add_route(ViewWithDecorator.as_view(), '/url')
    

    11.日志

    Sanic允许你使用python3 的logging API对请求不同类型日志进行记录

    from sanic import Sanic
    from sanic.config import LOGGING
    
    # The default logging handlers are ['accessStream', 'errorStream']
    # but we change it to use other handlers here for demo purpose
    LOGGING['loggers']['network']['handlers'] = [
        'accessSysLog', 'errorSysLog']
    
    app = Sanic('test')
    
    @app.route('/')
    async def test(request):
        return response.text('Hello World!')
    
    if __name__ == "__main__":
      app.run(log_config=LOGGING)
    
    # 关闭日志
    if __name__ == "__main__":
      app.run(log_config=None)
    

    11.2日志配置参数

    默认情况下,使用sanic.config.LOGGING字典来设置log_config参数

    参数 说明
    accessStream 使用logging.StreamHandler 登录控制台的请求信息
    internal 使用logging.StreamHandler 内部信息在控制台输出
    errorStream 使用logging.StreamHandler 控制台的错误信息和追溯信息
    accessSysLog 使用logging.handlers.SysLogHandler 记录到syslog的请求信息
    errorSysLog 使用logging.handlers.SysLogHandler syslog的错误消息和追溯记录
    accessFilter 使用sanic.log.DefaultFilter 只允许DEBUGINFONONE(0)级别的过滤器
    errorFilter 使用sanic.log.DefaultFilter 只允许在WARNINGERRORCRITICAL级别的过滤器。

    默认访问日志

    %(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d
    

    12.run参数

    参数 说明
    host (默认“127.0.0.1”),服务器主机的地址
    port (默认8000), 服务器的端口
    debug (默认False),启用调试(减慢服务器速度)
    ssl (默认None),用于工作者SSL加密的SSLContext
    sock (默认None),服务器接受连接的Socket
    worker 默认值1,生成的工作进程数
    loop 默认None,asyncio`兼容的事件循环。如果没有指定,Sanic会创建自己的事件循环。
    protocol 默认HttpProtocolasyncio.protocol的子类

    12.1 多进程

    Sanic在主进程中只侦听一个CPU内核。要启动其它核心,只需指定run参数中进程的数量。

    app.run(host='0.0.0.0', port=1337, workers=4)	#进程数和CPU核心数一样
    

    12.2 命令运行sanic

    python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4
    

    12.3 异步的支持

    异步支持合适与其他应用程序(特别是loop)共享sanic进程。但是请注意,因为此方法不支持使用多个进程,一般不是运行应用程序的首选方式

    server = app.create_server(host="0.0.0.0", port=8000)
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(server)
    loop.run_forever()
    

    13.扩展

    组件 说明
    Sessions session的支持,允许使用redis,memcache或内存进行存储
    CORS 用于处理跨域资源共享的扩展
    Compress 允许您轻松地压缩Sanic响应。
    Jinja2 Jinja2模板框架。
    OpenAPI/Swagger OpenAPI支持,以及Swagger UI。
    Pagination 简单的分页支持。
    Sanic CRUD 基于peewee 模型的CRUD(创建/检索/更新/删除)REST API自动生成的框架。
    UserAgent 添加user_agent到请求
    Limiter 限制sanic速率。
    Sanic EnvConfig 将环境变量加入sanic配置。
    Babel 借助Babel库,向Sanic应用程序添加i18n/l10n支持。
    Dispatch 由werkzeug的DispatcherMiddleware驱动的调度程序。可以作为Sanic-to-WSGI适配器
    Sanic-OAuth 用于连接和创建自己的token授权的库。
    Sanic-nginx-docker-example 在nginx使用docker-compose的一个简单易用的Sanic例子。
    sanic-prometheus Sanic的Prometheus指标
    Sanic-RestPlus Sanic的Flask-RestPlus端口。基于SwaggerUI的全功能REST API。
    sanic-transmute 可从python函数和类生成API,并自动生成Swagger UI文档。
    pytest-sanic 一个用于Sanic的pytest插件。可以测试异步代码。
    jinja2-sanic 一个用于Sanic的jinja2模板渲染器。
  • 相关阅读:
    HTTP基础
    DHCP(六)
    DOM备忘录
    正则表达式备忘录
    Function与Object的关系
    模块模式浅析
    视频下载
    JSP基础点滴
    迭代输出总结
    中文乱码问题的解决途径
  • 原文地址:https://www.cnblogs.com/huanghongzheng/p/14528494.html
Copyright © 2011-2022 走看看