Sanic教程

轉自:https://segmentfault.com/a/1190000012951052

快速開始

在安裝Sanic以前,讓咱們一塊兒來看看Python在支持異步的過程當中,都經歷了哪些比較重大的更新。html

首先是Python3.4版本引入了asyncio,這讓Python有了支持異步IO的標準庫,然後3.5版本又提供了兩個新的關鍵字async/await,目的是爲了更好地標識異步IO,讓異步編程看起來更加友好,最後3.6版本更進一步,推出了穩定版的asyncio,從這一系列的更新能夠看出,Python社區正邁着堅決且穩重的步伐向異步編程靠近。node

安裝

Sanic是一個支持 async/await 語法的異步無阻塞框架,這意味着咱們能夠依靠其處理異步請求的新特性來提高服務性能,若是你有Flask框架的使用經驗,那麼你能夠迅速地使用Sanic來構建出心中想要的應用,而且性能會提高很多,我將同一服務分別用Flask和Sanic編寫,再將壓測的結果進行對比,發現Sanic編寫的服務大概是Falsk的1.5倍。python

僅僅是Sanic的異步特性就讓它的速度獲得這麼大的提高麼?是的,但這個答案並不標準,更爲關鍵的是Sanic使用了uvloop做爲asyncio的事件循環,uvloop由Cython編寫,它的出現讓asyncio更快,快到什麼程度?這篇文章中有介紹,其中提出速度至少比 nodejs、gevent 和其餘Python異步框架要快兩倍,而且性能接近於用Go編寫的程序,順便一提,Sanic的做者就是受這篇文章影響,這纔有了Sanic。git

怎麼樣?有沒有激起你學習Sanic的興趣,若是有,就讓咱們一塊兒開始學習吧,在開始以前,你只須要有一臺安裝了Python的電腦便可。github

說明:因爲Windows下暫不支持安裝uvloop,故在此建議使用Mac或Linux

虛擬環境

程序世界一部分是對應着現實的,在生活中,咱們會在不一樣的環境完成不一樣的任務,好比在廚房作飯、臥室休息,分工極其明確。web

其實用Python編寫應用服務也是如此,它們一樣但願應用服務與開發環境是一對一的關係,這樣作的好處在於,每一個獨立的環境均可以簡潔高效地管理自身對應服務所依賴的第三方庫,如若否則,各個服務都安排在同一環境,這樣不只會形成管理上的麻煩,還會使第三方庫之間產生衝突。typescript

經過上面的敘述,咱們是否是能夠得出這樣一個核心觀點:應該在不一樣的環境下作不一樣的事 ,以此類推,寫項目的時候,咱們也須要爲每一個不一樣的項目構建一個無干擾的的環境,發散思惟,總結一下:shell

不一樣的項目,須要爲其構建不一樣的虛擬環境,以避免互相干擾

構建虛擬環境的工具不少,以下:編程

…...json

以上三個工具均可以快速地幫助咱們構建當前須要的Python環境,若是你以前沒有使用過,可直接點開連接進行下載,若是你正在使用其它的環境管理工具,也沒關係,由於不論你使用哪種方式,咱們最終目的都是針對一個新項目構建一個新的環境。

安裝配置好以後,簡單看看官方提供的使用方法,就能夠開始了,好比我本機使用的是anaconda ,安裝完成後能夠很方便地建立一個虛擬環境,好比這裏使用Python3.6來做爲本書項目的默認環境:

# 新建一個python3.6環境 conda create --name python36 python=3.6 # 安裝好以後 輸入下面命令進入名爲python36的環境 source activate python36

若安裝速度比較慢,能夠考慮換國內源,好比 國內鏡像 ,至於爲何選擇python3.6做爲默認環境,一是由於Sanic只支持Python3.5+,二則是咱們構建的項目最終是要在生產環境下運行的,因此建議最好安裝Python3.6下穩定版本的asyncio

安裝Sanic

Python安裝第三方模塊都是利用pip工具進行安裝,這裏也不例外,首先進入上一步咱們新建的 python3.6 虛擬環境,而後安裝:

# 安裝Sanic,請先使用 source activate python36 進入虛擬環境 pip install sanic # 若是不想使用uvloop和ujson 能夠這樣安裝 SANIC_NO_UVLOOP=true SANIC_NO_UJSON=true pip install sanic

經過上面的命令,你就能夠在 python3.6 虛擬環境中安裝Sanic以及其依賴的第三方庫了,若想查看Sanic是否已經正確安裝,能夠進入終端下對應的虛擬環境,啓動Python解釋器,導入Sanic庫:

# 啓動Python解釋器 python >>> import sanic >>>

若是沒有出現錯誤,就說明你已經正確地安裝了Sanic,請繼續閱讀下一節,瞭解下如何利用Sanic來構建一個Web項目吧。

踏出第一步

咱們將正式使用Sanic來構建一個web項目,讓咱們踏出第一步,利用Sanic來編寫一個返回Hello World!字符串的服務程序。

新建一個文件,名爲 run.py :

#!/usr/bin/env python from sanic import Sanic from sanic.response import text app = Sanic() @app.route("/") async def test(request): return text('Hello World!') if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)

Sanic的目標是讓編寫服務更加簡單易用,請看上面僅用不到10行的代碼,就編寫好了一個簡單的Web服務,運行此文件,在瀏覽器輸入 http://0.0.0.0:8000 ,出現的字符會讓你回想起當年學c的恐懼^_^。

若是你是第一次使用Sanic,上面的代碼可能會讓你產生一些困擾,不用擔憂,接下來,咱們將一塊兒用Sanic編寫一個簡單的資訊閱讀的web服務,在這過程當中,你將逐漸地瞭解到Sanic的一些基本用法,如路由的構建、接受請求數據以及返回響應的內容等。

本次示例的源代碼所有在github上,見examples/demo01/news.py

編寫一個資訊閱讀項目

在開始編寫以前,第一步最好寫一下需求,哪怕是個簡單不過的玩具項目也不能略過這個步驟,好比如今編寫的資訊閱讀項目,需求就一個,在頁面中展現一些資訊新聞。

既然是展現資訊新聞,那麼解決數據來源的問題最爲重要,對於這個問題你也不用擔憂,由於在本次示例的源碼中我編寫了一個名爲get_news()的函數專門用來返回資訊新聞數據,簡化代碼以下:

async def get_news(size=10): """ Sanic是一個異步框架,爲了更好的發揮它的性能,有些操做最好也要用異步的 好比這裏發起請求就必需要用異步請求框架aiohttp 因此使用本服務的時候請先執行: pip install aiohttp 數據使用的是readhub網站的api接口 爲了使這個數據獲取函數正常運行,我會保持更新,因此具體代碼:examples/demo01/news.py """ async with aiohttp.ClientSession() as client: async with client.get(readhub_api, params=params, headers=headers) as response: assert response.status == 200 text = await response.json() return text

這樣各位就能夠只專一於Sanic的代碼實現,而沒必要考慮其餘問題,我會一直維護這個數據獲取函數,以保證數據正常輸出,各位請放心使用。

構建路由

數據的問題解決以後,咱們能夠開始着手於需求的實現了,根據前面的描述,此時的需求是當客戶端(Web瀏覽器)訪問http://0.0.0.0:8000/的時候,瀏覽器會立馬展現服務端響應返回的10條資訊新聞(假設內容由index()函數返回),若瀏覽器訪問的是http://0.0.0.0:8000/2,此時返回的就是第二頁的10條資訊新聞,以此類推......

當Sanic程序實例接收到一個請求,好比前面提到的http://0.0.0.0:8000/,它是怎麼知道這個URL能夠對應到index()函數呢?

Sanic有一個機制來保存URL和函數(通常稱之爲視圖函數)之間的映射關係,就像dictkeyvalue,這樣當服務端接收到請求http://0.0.0.0:8000/,就會立馬知道,接下來須要調用index()函數了,咱們將其稱之爲路由。

Sanic中能夠用app.route修飾器來定義路由,當Sanic服務啓動的時候,app.route就會將其中傳入的參數與裝飾的函數自動註冊好,好比下面這段代碼:

@app.route("/") async def index(request): """當服務端接收到客戶端的/請求時,就會調用此函數""" return text('Hello World!')

此時請求http://0.0.0.0:8000/就會返回Hello World!,很顯然,這不是咱們想要的需求,咱們的需求是展現10條資訊新聞,數據怎麼來?你只須要調用get_news()函數,就會獲取到你想要的資訊數據:

@app.route("/") async def index(request): # html頁面模板 html_tem = """ <div style="width: 80%; margin-left: 10%"> <p><a href="{href}" target="_blank">{title}</a></p> <p>{summary}</p> <p>{updated_at}</p> </div> """ html_list = [] # 獲取數據 all_news = await get_news() # 生成在瀏覽器展現的html頁面 for each_news in all_news: html_list.append(html_tem.format( href=each_news.get('news_info', [{}])[0].get('url', '#'), title=each_news.get('title'), summary=each_news.get('summary'), updated_at=each_news.get('updated_at'), )) return html('<hr>'.join(html_list))

運行此服務:

python run news.py

此時,訪問http://0.0.0.0:8000/,你就會得到Sanic服務程序返回的資訊新聞,以下圖,能夠看到返回服務端提供的最新資訊:

圖片描述

頁面成功地呈現出咱們想要的結果,實在是使人興奮,等等,不能高興太早,咱們還有一個需求,要根據瀏覽器輸入的頁數來展現內容,如:http://0.0.0.0:8000/2,思考一下,應該怎樣優雅地完成這個需求,或許你會想,再構建一對URL與視圖函數的映射關係,像下面這樣:

@app.route("/2") async def page_2(request):

不得不說,這是一個糟糕的解決方案,這樣無法解決接下來的第3頁、第4頁、甚至第n頁(雖然目前這個服務程序只展現到第2頁),最佳實踐應該是把頁數當作變量來獲取,Sanic的路由機制天然提供了獲取動態請求參數的功能,以下:

@app.route("/<page:int>") @app.route("/") async def index(request, page=1): """ 支持/請求與/page請求方式 具體的代碼邏輯也會有一點改變,可參考:examples/demo01/news.py """

再次運行此服務:

python run news.py

不管是請求http://0.0.0.0:8000/或者http://0.0.0.0:8000/2,都是咱們想要的結果。

請求數據

細心的你可能會發現,每次編寫一個視圖函數的時候,老是有一個request參數:

async def index(request, page=1):

爲何必須定義這個參數,它從哪來?它有什麼做用,下面我將一一爲你解答。

若是你在客戶端請求http://0.0.0.0:8000/的時候,順手在視圖函數裏面打印下參數request,會有以下輸出:

<Request: GET />

看終端的輸出能夠了解到request參數其實是一個名爲Request的實例對象,每當服務端接收到一個請求,Sanic的handle_request函數一定會接收一個Request實例對象,這個實例對象包含了一系列請求信息。

前面說到,每一個URL對應一個視圖函數,而Sanic的handle_request接下來會將接收的Request實例對象做爲參數傳給URL對應的視圖函數,也就是上面indexrequest參數,這樣一來,就必須定義request來接收Request實例對象,其中包含的一些請求信息對視圖函數來講很是重要,目前Request對象提供瞭如下屬性:

  • json
  • token
  • form
  • files
  • args
  • raw_args
  • cookies
  • ip
  • port
  • socket
  • remote_addr
  • path
  • url

上面只是列出了一部分屬性,若是你想了解更多,可查看request.py源碼文件瞭解。

爲了能夠實際使用下request,咱們能夠再加一個需求,好比增長一個GET請求的接口http://0.0.0.0:8000/json,若是請求不設置參數nums的值,則默認返回一條資訊新聞,若是設置了nums參數,則該接口返回的新聞數量由參數值決定,參數最大值爲10:

@app.route('/json') async def index_json(request): """ 默認返回一條資訊,最多十條 """ nums = request.args.get('nums', 1) # 獲取數據 all_news = await get_news() try: return json(random.sample(all_news, int(nums))) except ValueError: return json(all_news)

運行此服務:

python run news.py

此時視圖函數index_json就能夠根據接受的參數nums來返回對應數量的新聞,訪問http://0.0.0.0:8000/json?nums=2,效果以下:

圖片描述

響應

不論哪一個Web框架,都是須要構建響應對象的,Sanic天然也不例外,它用的是sanic.response來構建響應對象,像上面的代碼中能夠看到:

from sanic.response import html, json

這表示咱們目前構建的資訊閱讀服務,分別返回了body格式爲html以及json的響應對象,除了這兩種格式,Sanic還提供了下面幾種格式:

  • json
  • text
  • raw
  • html
  • file
  • file_stream
  • stream

更多屬性請看response.py,咱們能夠根據實際需求來構建響應對象,最後再返回給客戶端。

繼續深刻

不要覺得如今編寫的資訊服務已經很完善了,其實還有許多問題須要咱們解決,好比訪問http://0.0.0.0:8000/html這個URL會返回:

Error: Requested URL /html not found

服務程序爲何會拋出這個錯誤?由於程序中並路由沒有註冊html,而且沒有進行錯誤捕捉(好比此時的404),解決這個問題也很方便,好比把這個錯誤所有跳轉到首頁,代碼以下:

@app.exception(NotFound) def ignore_404s(request, exception): return redirect('/')

此時訪問一些沒有註冊於路由的URL,好比此時的http://0.0.0.0:8000/html都會自動跳轉到http://0.0.0.0:8000/

如今,咱們已經用Sanic編寫了一個簡單的資訊閱讀服務,在編寫的過程當中使用了路由、數據請求、處理以及響應對象,這些基礎知識足夠你編寫一些基本的服務,但這還遠遠不夠,好比模板引、引入靜態文件等,這些都等着咱們在實踐中繼續深刻了解。

總結

本章介紹了Sanic的安裝以及基本的使用,目標是但願諸位能夠迅速的瞭解並掌握Sanic的基本使用方法,併爲閱讀接下來的章節打一下基礎。

文檔以及代碼:

相關文章
相關標籤/搜索