輕量快速的 Python ASGI 框架 uvicorn


什么是 Uvicorn ?

答:Uvicorn 是基於 uvloop 和 httptools 構建的非常快速的 ASGI 服務器。

什么是 uvloop 和 httptools ?

答: uvloop 用於替換標准庫 asyncio 中的事件循環,使用 Cython 實現,它非常快,可以使 asyncio 的速度提高 2-4 倍。asyncio 不用我介紹吧,寫異步代碼離不開它。

httptools 是 nodejs HTTP 解析器的 Python 實現。

什么是 ASGI 服務器?

答: 異步網關協議接口,一個介於網絡協議服務和 Python 應用之間的標准接口,能夠處理多種通用的協議類型,包括 HTTP,HTTP2 和 WebSocket。

請簡單介紹下 Uvicorn

答:目前,Python 仍缺乏異步的網關協議接口,ASGI 的出現填補了這一空白,現在開始,我們能夠使用共同的標准為所有的異步框架來實現一些工具,ASGI 幫助 Python 在 Web 框架上和 Node.JS 及 Golang 相竟爭,目標是獲得高性能的 IO 密集型任務,ASGI 支持 HTTP2 和 WebSockets,WSGI 是不支持的。

Uvicorn 目前支持 HTTP1.1 和 WebSocket,計划支持 HTTP2。

使用方法:

$ pip install uvicorn
創建一個文件 example.py
async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ]
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })
啟動 Uvicorn
$ uvicorn example:app
你也可以不使用命令行,直接運行你的腳本也是可以的,如下:
import uvicorn

async def app(scope, receive, send):
    ...

if __name__ == "__main__":
    uvicorn.run("example:app", host="127.0.0.1", port=5000, log_level="info")



FastAPI使用uvicorn

import uvicorn
from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/")
async def root():
    return {"message": "Hello World"}
 
if __name__ == '__main__':
    uvicorn.run(app=app)
深入到uvicorn.run()方法里面,看到一個:
def run(app, **kwargs):
    config = Config(app, **kwargs)
    server = Server(config=config)
 
    if (config.reload or config.workers > 1) and not isinstance(app, str):
        logger = logging.getLogger("uvicorn.error")
        logger.warn(
            "You must pass the application as an import string to enable 'reload' or 'workers'."
        )
        sys.exit(1)
 
    if config.should_reload:
        sock = config.bind_socket()
        supervisor = StatReload(config, target=server.run, sockets=[sock])
        supervisor.run()
    elif config.workers > 1:
        sock = config.bind_socket()
        supervisor = Multiprocess(config, target=server.run, sockets=[sock])
        supervisor.run()
    else:
        server.run()
再深入到 config = Config(app, **kwargs)里面,就看到一些很多的相關的配置信息項:
class Config:
    def __init__(
        self,
        app,
        host="127.0.0.1",
        port=8000,
        uds=None,
        fd=None,
        loop="auto",
        http="auto",
        ws="auto",
        lifespan="auto",
        env_file=None,
        log_config=LOGGING_CONFIG,
        log_level=None,
        access_log=True,
        use_colors=None,
        interface="auto",
        debug=False,
        reload=False,
        reload_dirs=None,
        workers=None,
        proxy_headers=True,
        forwarded_allow_ips=None,
        root_path="",
        limit_concurrency=None,
        limit_max_requests=None,
        backlog=2048,
        timeout_keep_alive=5,
        timeout_notify=30,
        callback_notify=None,
        ssl_keyfile=None,
        ssl_certfile=None,
        ssl_version=SSL_PROTOCOL_VERSION,
        ssl_cert_reqs=ssl.CERT_NONE,
        ssl_ca_certs=None,
        ssl_ciphers="TLSv1",
        headers=None,
    ):
....
所以還可以添加的參數可以看上面的幾個配置的選項的信息來填:
於是乎還可以修改為:
uvicorn.run(app=app, host="127.0.0.1", port=8000, reload=True, debug=True)
發現本來想熱更新代碼,結果吶?有告警信息提示:
WARNING:  You must pass the application as an import string to enable 'reload' or 'workers'.
翻譯過來就是說: 警告:必須將應用程序作為導入字符串傳遞,才能啟用“重新加載” 然后呢: 我修改為:
  uvicorn.run(app='app', host="127.0.0.1", port=8000, reload=True, debug=True)
又提示:
ERROR:    Error loading ASGI app. Import string "app" must be in format "<module>:<attribute>".
好吧,我再看看官方文檔說是:
在命令行下是需要:模塊加app名稱:剛好上面的錯誤提示也是說需要:
    uvicorn.run(app='main:app', host="127.0.0.1", port=8000, reload=True, debug=True)
這樣之后就可以啟動熱更新重啟服務了!

使用命令行時,你可以使用 uvicorn --help 來獲取幫助。

Usage: uvicorn [OPTIONS] APP

Options:
  --host TEXT                     Bind socket to this host.  [default:
                                  127.0.0.1]
  --port INTEGER                  Bind socket to this port.  [default: 8000]
  --uds TEXT                      Bind to a UNIX domain socket.
  --fd INTEGER                    Bind to socket from this file descriptor.
  --reload                        Enable auto-reload.
  --reload-dir TEXT               Set reload directories explicitly, instead
                                  of using the current working directory.
  --workers INTEGER               Number of worker processes. Defaults to the
                                  $WEB_CONCURRENCY environment variable if
                                  available. Not valid with --reload.
  --loop [auto|asyncio|uvloop|iocp]
                                  Event loop implementation.  [default: auto]
  --http [auto|h11|httptools]     HTTP protocol implementation.  [default:
                                  auto]
  --ws [auto|none|websockets|wsproto]
                                  WebSocket protocol implementation.
                                  [default: auto]
  --lifespan [auto|on|off]        Lifespan implementation.  [default: auto]
  --interface [auto|asgi3|asgi2|wsgi]
                                  Select ASGI3, ASGI2, or WSGI as the
                                  application interface.  [default: auto]
  --env-file PATH                 Environment configuration file.
  --log-config PATH               Logging configuration file.
  --log-level [critical|error|warning|info|debug|trace]
                                  Log level. [default: info]
  --access-log / --no-access-log  Enable/Disable access log.
  --use-colors / --no-use-colors  Enable/Disable colorized logging.
  --proxy-headers / --no-proxy-headers
                                  Enable/Disable X-Forwarded-Proto,
                                  X-Forwarded-For, X-Forwarded-Port to
                                  populate remote address info.
  --forwarded-allow-ips TEXT      Comma separated list of IPs to trust with
                                  proxy headers. Defaults to the
                                  $FORWARDED_ALLOW_IPS environment variable if
                                  available, or '127.0.0.1'.
  --root-path TEXT                Set the ASGI 'root_path' for applications
                                  submounted below a given URL path.
  --limit-concurrency INTEGER     Maximum number of concurrent connections or
                                  tasks to allow, before issuing HTTP 503
                                  responses.
  --backlog INTEGER               Maximum number of connections to hold in
                                  backlog
  --limit-max-requests INTEGER    Maximum number of requests to service before
                                  terminating the process.
  --timeout-keep-alive INTEGER    Close Keep-Alive connections if no new data
                                  is received within this timeout.  [default:
                                  5]
  --ssl-keyfile TEXT              SSL key file
  --ssl-certfile TEXT             SSL certificate file
  --ssl-version INTEGER           SSL version to use (see stdlib ssl module's)
                                  [default: 2]
  --ssl-cert-reqs INTEGER         Whether client certificate is required (see
                                  stdlib ssl module's)  [default: 0]
  --ssl-ca-certs TEXT             CA certificates file
  --ssl-ciphers TEXT              Ciphers to use (see stdlib ssl module's)
                                  [default: TLSv1]
  --header TEXT                   Specify custom default HTTP response headers
                                  as a Name:Value pair
  --help                          Show this message and exit.

使用進程管理器

使用進程管理器確保你以彈性方式運行運行多個進程,你可以執行服務器升級而不會丟棄客戶端的請求。

一個進程管理器將會處理套接字設置,啟動多個服務器進程,監控進程活動,監聽進程重啟、關閉等信號。

Uvicorn 提供一個輕量級的方法來運行多個工作進程,比如 --workers 4,但並沒有提供進行的監控。

使用 Gunicorn

Gunicorn 是成熟的,功能齊全的服務器,Uvicorn 內部包含有 Guicorn 的 workers 類,允許你運行 ASGI 應用程序,這些 workers 繼承了所有 Uvicorn 高性能的特點,並且給你使用 Guicorn 來進行進程管理。

這樣的話,你可能動態增加或減少進程數量,平滑地重啟工作進程,或者升級服務器而無需停機。

在生產環境中,Guicorn 大概是最簡單的方式來管理 Uvicorn 了,生產環境部署我們推薦使用 Guicorn 和 Uvicorn 的 worker 類:

gunicorn example:app -w 4 -k uvicorn.workers.UvicornWorker

執行上述命令將開戶 4 個工作進程,其中 UvicornWorker 的實現使用 uvloop 和httptools 實現。在 PyPy 下運行,你可以使用純 Python 實現,可以通過使用UvicornH11Worker 類來做到這一點。

gunicorn -w 4 -k uvicorn.workers.UvicornH11Worker

Gunicorn 為 Uvicorn 提供了不同的配置選項集,但是一些配置暫不支持,如--limit-concurrency 。

使用 Supervisor

要supervisor用作流程管理器,您應該:

使用其文件描述符將套接字移交給uvicorn,supervisor始終將其用作0,並且必須在本fcgi-program節中進行設置。

或為每個uvicorn進程使用UNIX域套接字。

一個簡單的主管配置可能看起來像這樣: administratord.conf:

[supervisord]

[fcgi-program:uvicorn]
socket=tcp://localhost:8000
command=venv/bin/uvicorn --fd 0 example:App
numprocs=4
process_name=uvicorn-%(process_num)d
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
然后運行supervisord -n

使用 Circus

要circus用作流程管理器,您應該:

使用其文件描述符將套接字移交給uvicorn,馬戲團可將其用作$(circus.sockets.web)。
或為每個uvicorn進程使用UNIX域套接字。

使用 Circus 與 Supervisor 很類似。配置文件 circus.ini 如下:

[watcher:web]
cmd = venv/bin/uvicorn --fd $(circus.sockets.web) example:App
use_sockets = True
numprocesses = 4

[socket:web]
host = 0.0.0.0
port = 8000

然后運行circusd circus.ini

與 Nginx 部署

Nginx 作為 Uvicorn 進程的代理並不是必須的,你可以使用 Nginx 做為負載均衡。推薦使用 Nginx 時配置請求頭,如 X-Forwarded-For,X-Forwarded-Proto,以便 Uvicorn 識別出真正的客戶端信息,如 IP 地址,scheme 等。這里有一個配置文件的樣例:

http {
  server {
    listen 80;
    client_max_body_size 4G;

    server_name example.com;

    location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_redirect off;
      proxy_buffering off;
      proxy_pass http://uvicorn;
    }

    location /static {
      # path for static files
      root /path/to/app/static;
    }
  }

  upstream uvicorn {
    server unix:/tmp/uvicorn.sock;
  }

}

使用 HTTPS

要使用https運行uvicorn,需要證書和私鑰。推薦的獲取方法是使用Let's Encrypt

對於使用https進行本地開發,可以使用mkcert 生成有效的證書和私鑰。

$ uvicorn example:app --port 5000 --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem

使用 Gunicorn 也可以直接使用證書。

也可以與uvicorn的工人一起使用證書來獲取gunicorn

$ gunicorn --keyfile=./key.pem --certfile=./cert.pem -k uvicorn.workers.UvicornWorker example:app


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM