granian:讓你的 Web 應用程序快如閃電

本篇文章推薦個好東西,它就是 granian,不過在介紹之前,需要先補充一些背景知識。在學習 Python Web 開發時,我們肯定會接觸到以下幾個概念。

那麼它們之間有什麼區別呢?這裏總結一下。

WSGI

WSGI 的全稱是 Web Server Gateway Interface(Web 服務器網關接口),是一種描述 Web 服務器和 Web 應用程序(使用 Web 框架,如 Django、Flask 編寫的程序)進行通信的規範、協議。

由於任何一個框架編寫的服務都必須運行在 Web 服務器上,所以這兩者必須遵循相同的通信規範,而這個規範就是 WSGI。實現了 WSGI 協議的框架,我們稱之爲 WSGI 框架,實現了 WSGI 協議的服務器,我們稱之爲 WSGI 服務器。

uWSGI

一個實現了 WSGI 協議的 Web 服務器,將使用 Web 框架編寫好的應用部署在 uWSGI 服務器上是可以直接對外提供服務的。當然 WSGI 服務器除了 uWSGI 之外,還有 Gunicorn 等等,它們不僅實現了 WSGI 協議,還實現了 uwsgi 協議和 HTTP 協議。

Nginx

同樣是一個 Web 服務器,但它相比 uWSGI 可以提供更多的功能,比如反向代理、負載均衡、緩存靜態資源、對 HTTP 請求更加友好,這些都是 uWSGI 所不具備或者不擅長的。

所以我們將 Web 服務部署在 uWSGI 之後,還要在前面再搭一層 Nginx。此時 uWSGI 就不再暴露 HTTP 服務了,而是暴露 TCP 服務,因爲它是和 Nginx 進行通信,使用 TCP 會更快一些,Nginx 來對外暴露 HTTP 服務。

uwsgi

Nginx 和 uWSGI 通信所採用的協議,因爲 uWSGI 是和 Nginx 對接,所以這兩者也要遵循相同的協議,這個協議就是 uwsgi。對於一個 HTTP 請求,Nginx 在收到之後,會進行判斷。

Nginx 內部有一個 HttpUwsgiModule 模塊,它的作用就是讓 Nginx 具備和 uWSGI 通信的功能。

在 WSGI 之前,選擇一個框架可能會限制 Web 服務器的可用接口的種類,因爲兩者之間沒有標準化的接口,而 WSGI 則通過提供一個簡單的 API 解決了這個問題。2004 年,隨着 PEP-333 的出現,現已成爲 Web 應用程序部署的實際標準。

我們來構建一個最簡單的 WSGI 應用程序。

# main.py
def application(env, start_response):
    print(env)
    start_response("200 OK"[("Content-Type""text/html")])
    return [b"WSGI hello!"]

然後命令行輸入 gunicorn main 即可啓動。

可能有人好奇,只定義一個 application 函數就可以啓動了嗎?答案是的,WSGI 協議要求 Python 應用程序必須實現一個名爲 application 的可調用對象,並且接收兩個參數:一個是包含 HTTP 請求信息的字典對象,另一個是用於發送響應的回調函數。應用程序需要從請求字典中獲取請求信息,然後使用回調函數將響應返回。

而服務器會自動調用 application,這是雙方約定好的,由 WSGI 協議所規範。我們訪問本地的 8000 端口,看看是否會正常輸出。

結果正常,再來看看程序中的參數 env 的打印結果。

裏面是一些請求信息,而從這些信息中可以看出,WSGI 只支持請求 / 響應生命週期。這意味着 WSGI 不能與長生命週期的連接協議(如 WebSocket)一起工作,並且也不支持異步工作負載。

所以又有了 ASGI,它的定位和 WSGI 是相同的,但通過重新設計 API,使用協程解決了這個問題。ASGI 是 Python 異步 Web 應用程序和服務器之間的接口規範,是對 WSGI 規範的擴展和補充,旨在解決 WSGI 無法有效處理異步 IO 操作的問題。

因此相比 WSGI,ASGI 支持異步的應用程序和服務器,可以處理非阻塞式 IO 操作,包括長輪詢、WebSockets、HTTP/2 等。它提供了一種標準化的接口,使得 Web 服務器和 Python 框架之間能以異步方式進行通信。

此外 ASGI 還規定了一些標準化的環境變量和協議,以確保不同的 Web 服務器和 Python 框架可以協同工作。與 WSGI 類似,ASGI 允許開發人員選擇不同的 Web 服務器和 Python 框架,並且它們仍然可以互相兼容。

總之 ASGI 和 WSGI 乾的事情是一樣的,但 ASGI 在 WSGI 之上做了很多的擴展,讓我們將上面的 WSGI 示例轉換爲 ASGI。

# main.py
async def application(scope, receive, send):
    await send(
        {"type""http.response.start",
         "status": 200,
         "headers"[(b"Content-Type", b"text/html")]}
    )
    await send({"type""http.response.body""body": b"ASGI hello!"})

ASGI 的 application 函數接收三個參數:

那麼如何啓動上面這個程序呢?和啓動 WSGI 程序需要 WSGI 服務器一樣,要啓動 ASGI 程序,自然也需要 ASGI 服務器。

目前最流行的 ASGI 服務器是 uvicorn,它建立在 uvloop 和 httptools 之上,其中 uvloop 是 asyncio 事件循環的更高效實現,httptools 是一個高性能的 HTTP 解析庫。

我們可以通過運行 pip install uvicorn 命令來安裝它,另外由於 uvloop 不支持 Windows 平臺,所以如果在 Windows 上使用 uvicorn,那麼事件循環會退化爲 asyncio。

然後通過 uvicorn main:application 即可運行應用程序。

到目前爲止我們就瞭解了 ASGI 和 WSGI 是什麼。然後 uvicorn 是一個 ASGI 服務器(實現了 ASGI 協議),而我們還需要一個 ASGI 框架,而不是在文件裏面只寫一個 application 協程(當然 ASGI 框架也是在此之上一點點搭起來的)。

至於 ASGI 框架有很多,像 FastAPI、BlackSheep,它們都是 ASGI 框架,實現了 ASGI 協議。至於用這些框架編寫的服務,也必須運行在實現 ASGI 協議的服務器上,而 ASGI 服務器有 uvicorn、hypercorn 等等。

舉個例子:

不管是 FastAPI 還是 BlackSheep,它們都支持 ASGI 協議,所以它們都可以跑在 uvicorn 服務器上面,甚至啓動方式都是不變的。當然 uvicorn 既可以在命令行中啓動,也可以在代碼中調用 run 方法啓動。

然後再補充一點,對於一個最簡單的 ASGI 程序,只需定義一個協程函數便可以啓動了。這就說明使用 FastAPI 創建的 app 對象,內部一定實現了 call 方法,否則它無法像函數一樣進行調用。

我們分析得沒錯,所謂的框架都是基於這個簡單的協程函數一點點擴展得到的。

好,扯了這麼多,下面要回歸主題了,我們的主題是啥來着?對,granian,這位老兄乾的事情和 uvicorn 是類似的,所以它也是一個 ASGI 服務器。

我的老天爺,一句話就能說完的事情,愣是扯了這麼多,像不像網絡上那些水視頻的,視頻過了一半纔開始說重點。

那麼問題來了,granian 相比 uvicorn 有啥優勢嗎?很明顯,優勢當然是有的,但即便拋開優勢不談,我們也應該把目光轉向 granian,因爲它是使用編程語言界的原神 Rust 編寫的。

衆所周知,Rust 就如同當年的黃埔軍校,從這裏面出來的都自帶光環。畢竟有了 Rust,性能和安全就有了保證,信 Rust,無 Bug【doge】。

不扯皮了,下面說一說 granian 的特點。

下面我們來安裝,直接 pip install granian 即可,然後測試一下。

from blacksheep import Application, Request

app = Application()

@app.router.get("/index")
async def index(request: Request):
    return {"message""Hello World"}

文件名爲 main.py,我們在命令行中輸入 granian main:app --interface asgi 即可啓動服務。

因爲 granian 實現了 ASGI 協議,所以 BlackSheep 依舊可以跑在 granian 上面。經過羣友測試,ASGI 框架選擇 BlackSheep,ASGI 服務器選擇 granian,速度快到飛起。

然後再看一下 granian 的啓動命令,裏面有一個 --interface asgi,這就說明除了 ASGI 之外還支持其它的。沒錯,granian 支持 WSGI、ASGI、RSGI。

我們演示一下這三者的區別:

# WSGI
def wsgi_application(env, start_response):
    start_response("200 OK"[("Content-Type""text/html")])
    return [b"WSGI hello!"]

# ASGI
async def asgi_application(scope, receive, send):
    await send(
        {"type""http.response.start",
         "status": 200,
         "headers"[(b"Content-Type", b"text/html")]}
    )
    await send({"type""http.response.body""body": b"ASGI hello!"})

# RSGI
async def rsgi_application(scope, proto):
    assert scope.proto == "http"
    proto.response_str(
        status=200,
        headers=[
            ("content-type""text/html")
        ],
        body="RSGI Hello!"
    )

啓動命令如下:

由於 RSGI 是 granian 自創的,目前的框架還不支持,因此使用 ASGI 就好。

除了 --interface 之外,granian 還支持很多其它參數,比如:

具體的參數可以通過 granian --help 查看,註釋很詳細。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g8y1oRXJgRohQcg1GUa6qA