Python的最快的Web框架快速入門
優效學院,大數據,Java,人工智慧,架構,在線教育
打開今日頭條,查看更多精彩圖片
Sanic是一個和類Flask的基於Python3.5 +的web框架,它編寫的代碼速度特別快。
除了像Flask以外,Sanic還支持以非同步請求的方式處理請求。這意味著你可以使用新的async / await語法,編寫非阻塞的快速的代碼。
既然它說速度特別快,我們先看下官方提供的基準測試結果。
中信高科基準測試
這個測試的程序運行在AWS實例上,系統是Ubuntu,只使用了一個進程。
Sanic的開發者說他們的靈感來自於這篇文章uvloop:快速的Python網路。
那我們就有必要看下uvloop是個什麼庫。
uvloop
uvloop是asyncio默認事件循環的替代品,實現的功能完整,切即插即用.uvloop是用CPython寫的,建於libuv之上
.uvloop可以使asyncio更快。事實上,它至少比nodejs,gevent和其他Python非同步框架要快兩倍。基於uvloop的asyncio的速度幾乎接近了Go程序的速度。
安裝uvloop
uvloop還只能在* nix平台和Python3.5 +以上版本使用。
使用pip安裝:
pip install uvloop
在asyncio代碼中使用uvloop也很簡單:
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
這得代碼使得對任何asyncio.get_event_loop()的調用都將返回一個uvloop實例。
現在我們開始學習中信高科:
安裝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)
如果你熟悉瓶,你會發現,這個語法簡直和瓶一模一樣。
路由(路由)
路由用於把一個函數綁定到一個URL。下面是一些基本的例子:
@app.route("/")
def index():
return text("Index Page")
@app.route("/hello")
def hello():
return text("Hello World")
當然,你還可以動態的變化URL的某些部分,還可以為一個函數指定多個規則。
變數規則
通過把URL的一部分標記為就可以在URL中添加變數。標記的部分會作為關鍵字參數傳遞給函數。通過使用,可以選擇性的加上一個轉換器,為變數指定特定的類型,如果傳入的類型錯誤,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裝飾器提供了一個可選參數方法,這個參數允許傳入所有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 = 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
使用藍圖(藍圖)
Sanic也提供了和Flask類似的藍圖.Blueprint
有以下用途:
把一個應用分解為一套藍圖。這是針對大型應用的理想方案:一個項目可以實例化一個應用,初始化多個擴展,並註冊許多藍圖。
在一個應用的URL前綴和(或)子域上註冊一個藍圖.URL前綴和(或)子域的參數成為藍圖中所有視圖的通用視圖參數(預設情況下)。
使用不同的URL規則在應用中多次註冊藍圖。
通過藍圖提供模板過濾器,靜態文件,模板和其他工具。藍圖不必執行應用或視圖函數。
藍圖示例
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()方法註冊藍圖。
使用藍圖註冊全局中間件
@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中由傳入的參數請求來提供請求信息。
為什麼不像Flask一樣提供一個全局變數要求?
Flask是同步請求,每次請求都有一個獨立的新線程來處理,這個線程中也只處理這一個請求。而Sanic是基於協程的處理方式,一個線程可以同時處理幾個,幾十個甚至幾百個請求,把請求作為全局變數顯然會比較難以處理。
請求對象常用參數有
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(位元組)處理POST表單請求,數據是一個字元串
其他參數還有:
- 文件
- IP
- 應用
- 網址
- 方案
- 路徑
- 請求參數
詳細信息參考文檔:請求數據
關於響應
Sanic使用response函數創建響應對象。
- 文本response.text("你好世界")
- html response.html("
- 你好,世界
- 「)
- json response.json({"hello":"world"})
- file response.file("/ srv / www / hello.txt")
- 流
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")
- 重定向response.file("/ json")
- raw response.raw("原始數據")
- 如果想修改響應的頭可以傳入頭參參
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的配置屬性來操作配置的值.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)
使用配置文件
如果把配置放在一個單獨的文件中會更有用。理想情況下配置文件應當放在應用包的外面。這樣可以在修改配置文件時不影響應用的打包與分發
常見用法如下:
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的支持也不完善。
詳細信息可以看下這個問題用sanic建造的項目?
先在說下我的部署方式
使用supervisord部署
啟動方式
supervisord -c supervisor.conf
總結
試用了下Sanic,把之前的一個聊天機器人從Flask改成了Sanic。不得不說,如果你有Flask經驗,大致看一下
Sanic的速度比Flask快很多,只是Sanic配套的包還是太少,用於生產環境有一定的風險。