Sanic問題
1.什么是Web框架?
2.為什么要用Web框架?
3.在Python中常用的Web框架有
django flask tornado sanic
Sanic
簡介
Sanic是一個類Flask的基於Python3.5以上的Web框架,它除了與Flask功能類似外,它還支持異步請求處理,是一個異步協程框架。這意味着你可以使用async/await語法,編寫非阻塞的快速代碼,而且使你的程序運行速度加快。
關於asyncio包的介紹:使用asyncio處理並發
官方提供的速度對比:
產生原因:
使用較早的異步框架是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 只允許DEBUG ,INFO 和NONE(0) 級別的過濾器 |
errorFilter | 使用sanic.log.DefaultFilter 只允許在WARNING ,ERROR 和CRITICAL 級別的過濾器。 |
默認訪問日志
%(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 | 默認HttpProtocol :asyncio.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模板渲染器。 |