python 最快 web 框架 Sanci 快速入門

簡介

Sanic 是一個和類Flask 的基於Python3.5+的web框架,它編寫的代碼速度特別快。
除了像Flask 之外,Sanic 還支持以異步請求的方式處理請求。這意味着你可使用新的 async/await 語法,編寫非阻塞的快速的代碼。html

關於 asyncio 包的介紹,請參考以前的一篇文章 python併發2:使用asyncio處理併發node

Github 地址 是 https://github.com/channelcat/sanic,感興趣的能夠去貢獻代碼。python

既然它說速度特別快,咱們先看下官方提供的 基準測試結果。git

Sanic基準測試

sanic benchmarks

這個測試的程序運行在 AWS 實例上,系統是Ubuntu,只使用了一個進程。github

Sanic 的開發者說他們的靈感來自於這篇文章 uvloop: Blazing fast Python networkingweb

那咱們就有必要看下uvloop是個什麼庫。json

uvloop

uvloop 是 asyncio 默認事件循環的替代品,實現的功能完整,切即插即用。uvloop是用CPython 寫的,建於libuv之上。
uvloop 可使 asyncio 更快。事實上,它至少比 nodejs、gevent 和其餘 Python 異步框架要快兩倍 。基於 uvloop 的 asyncio 的速度幾乎接近了 Go 程序的速度。segmentfault

安裝 uvloop

uvloop 還只能在 *nix 平臺 和 Python3.5+以上版本使用。
使用pip安裝:瀏覽器

pip install uvloop

在 asyncio 代碼中使用uvloop 也很簡單:bash

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

這得代碼使得對任何asyncio.get_event_loop() 的調用都將返回一個uvloop實例。

詳細的uvloop 介紹能夠看下原文:uvloop: Blazing fast Python networking

uvloop的github地址是https://github.com/MagicStack/uvloop

如今咱們開始學習Sanic:

安裝 Sanic

pip install sanic

建立第一個 sanic 代碼

from sanic import Sanic
from sanic.response import text

app = Sanic(__name__)

@app.route("/")
async def test(request):
    return text('Hello world!')

app.run(host="0.0.0.0", port=8000, debug=True)

運行代碼: python main.py, 如今打開瀏覽器訪問 http://0.0.0.0:8000,你會看到 hello world!

若是你熟悉Flask,你會發現,這個語法簡直和Flask如出一轍。

路由(Routing)

路由用於把一個函數綁定到一個 URL。下面是一些基本的例子:

@app.route('/')
def index():
    return text('Index Page')

@app.route('/hello')
def hello():
    return text('Hello World')

固然,你還能夠動態的變化URL的某些部分,還能夠爲一個函數指定多個規則。

變量規則

經過把 URL 的一部分標記爲 <variable_name> 就能夠在 URL 中添加變量。標記的 部分會做爲關鍵字參數傳遞給函數。經過使用 <converter:variable_name> ,能夠 選擇性的加上一個轉換器,爲變量指定特定的類型,若是傳入的類型錯誤,Sanic會拋出NotFound異常。請看下面的例子:

from sanic.response import text

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

@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))

HTTP 請求類型

默認狀況下,咱們定義的URL只支持GET 請求,@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))

也能夠簡寫爲:

from sanic.response import text

@app.post('/post')
async def post_handler(request):
    return text('POST request - {}'.format(request.json))

@app.get('/get')
async def get_handler(request):
    return text('GET request - {}'.format(request.args))

add_route 方法

除了@app.route裝飾器,Sanic 還提供了 add_route 方法。

@app.route 只是包裝了 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'])

URL 構建

若是能夠匹配URL,那麼Sanic能夠生成URL嗎?固然能夠,url_for() 函數就是用於構建指定函數的URL的。它把函數名稱做爲第一個參數,其他參數對應URL中的變量,例如:

@app.route('/')
async def index(request):
    # generate a URL for the endpoint `post_handler`
    url = app.url_for('post_handler', post_id=5)
    # the URL is `/posts/5`, redirect to it
    return redirect(url)


@app.route('/posts/<post_id>')
async def post_handler(request, post_id):
    return text('Post - {}'.format(post_id))

未定義變量會做爲URL的查詢參數:

url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
# /posts/5?arg_one=one&arg_two=two

# 支持多值參數
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two

使用藍圖(Blueprint)

Sanic也提供了和Flask 相似的 Blueprint。
Blueprint有如下用途:

  • 把一個應用分解爲一套藍圖。這是針對大型應用的理想方案:一個項目能夠實例化一個 應用,初始化多個擴展,並註冊許多藍圖。

  • 在一個應用的 URL 前綴和(或)子域上註冊一個藍圖。 URL 前綴和(或)子域的參數 成爲藍圖中全部視圖的通用視圖參數(缺省狀況下)。

  • 使用不一樣的 URL 規則在應用中屢次註冊藍圖。

  • 經過藍圖提供模板過濾器、靜態文件、模板和其餘工具。藍圖沒必要執行應用或視圖 函數。

blueprint 示例

from sanic import Sanic
from sanic.response import json
from sanic import Blueprint

bp = Blueprint('my_blueprint')

@bp.route('/')
async def bp_root(request):
    return json({'my': 'blueprint'})
    
app = Sanic(__name__)
app.blueprint(bp)

app.run(host='0.0.0.0', port=8000, debug=True)

Sanic 使用 app.blueprint() 方法註冊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')

使用藍圖處理異常

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

使用藍圖處理靜態文件

第一個參數指向當前的Python包
第二個參數是靜態文件的目錄

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

使用url_for

若是要建立頁面連接,能夠和一般同樣使用 url_for() 函數,只是要把藍圖名稱做爲端點的前綴,而且用一個點( . )來 分隔:

@blueprint_v1.route('/')
async def root(request):
    url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
    return redirect(url)


@blueprint_v1.route('/post/<post_id>')
async def post_handler(request, post_id):
    return text('Post {} in Blueprint V1'.format(post_id))

操做請求數據

對於web 應用來講對客戶端向服務器發送的數據作出相應很重要,在Sanic中由傳入的參數 request來提供請求信息。

爲何不像Flask 同樣提供一個全局變量 request?

Flask 是同步請求,每次請求都有一個獨立的新線程來處理,這個線程中也只處理這一個請求。而Sanic是基於協程的處理方式,一個線程能夠同時處理幾個、幾十個甚至幾百個請求,把request做爲全局變量顯然會比較難以處理。

Request 對象經常使用參數有

json(any) json body

from sanic.response import json

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

args(dict) URL請求參數

?key1=value1&key2=value2 將轉變爲

{'key1': ['value1'], 'key2': ['value2']}

raw_args(dict) 和args 相似

?key1=value1&key2=value2 將轉變爲

{'key1': 'value1', 'key2': 'value2'}

form(dict)處理 POST 表單請求,數據是一個字典

body(bytes)處理POST 表單請求,數據是一個字符串

其餘參數還有:

  • file

  • ip

  • app

  • url

  • scheme

  • path

  • query_string

詳細信息參考文檔: Request Data

關於響應

Sanic使用response 函數建立響應對象。

  • 文本 response.text('hello world')

  • html response.html('<p>hello world</p>')

  • json response.json({'hello': 'world'})

  • file response.file('/srv/www/hello.txt')

  • 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')
  • redirect response.file('/json')

  • raw response.raw('raw data')

  • 若是想修改響應的headers能夠傳入headers 參數

from sanic import response

@app.route('/json')
def handle_request(request):
    return response.json(
        {'message': 'Hello world!'},
        headers={'X-Served-By': 'sanic'},
        status=200
    )

配置管理

應用老是須要必定的配置的。根據應用環境不一樣,會須要不一樣的配置。好比開關調試 模式、設置密鑰以及其餘依賴於環境的東西。
Sanic 的設計思路是在應用開始時載入配置。你能夠在代碼中直接硬編碼寫入配置,也可使用配置文件。

無論你使用何種方式載入配置,均可以使用 Sanic 的 config 屬性來操做配置的值。 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)

從對象導入配置

import myapp.default_settings

app = Sanic('myapp')
app.config.from_object(myapp.default_settings)

這裏是我寫的聊天機器人的真實配置示例:https://github.com/gusibi/momo/

使用配置文件

若是把配置放在一個單獨的文件中會更有用。理想狀況下配置文件應當放在應用包的 外面。這樣能夠在修改配置文件時不影響應用的打包與分發
常見用法以下:

app = Sanic('myapp')
app.config.from_envvar('MYAPP_SETTINGS')

首先從 myapp.default_settings 模塊載入配置,而後根據 MYAPP_SETTINGS 環境變量所指向的文件的內容重載配置的值。在 啓動服務器前,在 Linux 或 OS X 操做系統中,這個環境變量能夠在終端中使用 export 命令來設置:

$ export MYAPP_SETTINGS=/path/to/config_file
$ python myapp.py

部署

Sanic 項目還不是特別成熟,如今部署比較簡陋。對Gunicorn的支持也不完善。
詳細信息能夠 看下這個問題 Projects built with sanic?

先在說下個人部署方式

使用 supervisord 部署

supervisord 配置文件: https://github.com/gusibi/momo/blob/master/supervisord.conf

啓動 方式

supervisord -c supervisor.conf

總結

試用了下Sanic,把以前的一個聊天機器人從Flask 改爲了 Sanic。不得不說,若是你有Flask經驗,大體看一下Sanic文檔就能夠直接上手了。
而且Sanic 的速度比Flask 快不少,只是Sanic配套的包仍是太少,用於生產環境有必定的風險。

最後對聊天微信聊天機器人感興趣的能夠看下https://github.com/gusibi/momo

預告

下一篇將介紹如何使用 Sanic 一步一步建立一個 聊天機器人。

參考連接


最後,感謝女友支持。

>歡迎關注 >請我喝芬達
歡迎關注 請我喝芬達

彩蛋

魔魔是咱們家巴哥的名字
貼一張魔魔的照片結束本篇文章。

相關文章
相關標籤/搜索