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模板渲染器。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM