Last Updated on 2024-07-28 by Clay
在 Python 的程式開發中,我們經常使用 requests
模組進行 HTTP 請求;然而 requests
在真正串連前後端及不同服務時其同步請求方式可能成為瓶頸,我最近正是因為使用 requests
造成 K8s 的探針阻塞、進而導致所開發的服務容器被誤刪 —— 此時,httpx
可能是一個更適合的異步請求操作模組。
httpx 介紹
httpx
其實聽名字就知道,它是一個 HTTP 客戶端的函式庫,無論是同步(sync)還是異步(sync)的 API 調用都原生支援,可以直接兼容於 Python 的 async
和 await
關鍵字進行操作。
除此之外它也同樣具備一些強大的功能,比如支援 proxy、支援 SSL 設定、可以自訂重試策略(不用再自己寫了!)... 等等。以現在的開發環境來說,我個人體感它幾乎可以完全取代 requests
模組。
以下,我們來實際列舉 httpx 的使用方式,不過在那之前,我們可以建立一個假的 FastAPI 服務和原本的 requests
寫法。
當然,如果你已經有現成的程式碼想要修改,也可以直接跳到最下面 httpx
語法的部份,開始修改自己的程式碼。
建立 FastAPI 服務(假)
首先安裝 fastapi
和 uvicorn
。
pip install fastapi uvicorn
之後,我們來實際建立一個服務。
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Item(BaseModel):
id: int
name: str
fake_db: List[Item] = []
@app.get("/items/", response_model=List[Item]])
async def read_items():
return fake_db
@app.post("/items/", response_model=Item])
async def create_item(item: Item):
fake_db.append(item)
return item
緊接著將其啟動:
uvicorn app:app --reload
Output:
INFO: Started server process [560791]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
預設開啟在 http://127.0.0.1:8000 上。
原本 requests 的語法
如果你的系統還沒有安裝 requests
(但是都來看這篇筆記了應該很難沒有安裝吧),可以參考下面指令進行安裝:
pip install requests
之後,我們就可以使用 Python 撰寫程式去對 APIs 發起請求。
import requests
# GET
response = requests.get("http://127.0.0.1:8000/api/fake-db/items")
print("GET:", response.json())
# POST
new_item = {"id": 1, "name": "Item 1"}
response = requests.post("http://127.0.0.1:8000/api/fake-db/items", json=new_item)
print("POST:", response.json())
# GET
response = requests.get("http://127.0.0.1:8000/api/fake-db/items")
print("GET:", response.json())
Output:
GET: []
POST: {'id': 1, 'name': 'Item 1'}
GET: [{'id': 1, 'name': 'Item 1'}]
可以看到,一切都正常運作。
到了這裡,我們已經完成了 FastAPI 建立服務、再使用 requests
發送同步的請求;如果我們只是要兼容同步請求,我們可以在這個地方,把所有的 requests 文字全部替換成 httpx,這樣就是 httpx 的同步請求方式、是完全兼容 requests 的語法,不錯吧?
但 httpx
優秀的地方在於異步請求,我們接下來就來看 httpx
該如何執行異步請求吧!
httpx 的異步請求方式
最簡單的 httpx 異步請求方式,需要使用到 httpx.AsyncClient()
這種類別。
import asyncio
import httpx
async def main():
async with httpx.AsyncClient() as client:
# GET
response = await client.get("http://127.0.0.1:8000/api/fake-db/items")
print("GET:", response.json())
# POST
new_item = {"id": 2, "name": "Item 2"}
response = await client.post("http://127.0.0.1:8000/api/fake-db/items", json=new_item)
print("POST:", response.json())
# GET again
response = await client.get("http://127.0.0.1:8000/api/fake-db/items")
print("GET:", response.json())
# Run
asyncio.run(main())
Output:
GET: [{'id': 1, 'name': 'Item 1'}]
POST: {'id': 2, 'name': 'Item 2'}
GET: [{'id': 1, 'name': 'Item 1'}, {'id': 2, 'name': 'Item 2'}]
我們可以看到請求已經很好地被執行完成了。
結語
在本篇筆記中,我們介紹了如何把 requests
的同步請求方式修改成 httpx
的異步請求方式,進而利用異步編程的優勢提昇應用程式的性能和效率。
如果我們的服務會需要使用到大量的 HTTP 請求操作,那麼使用 httpx
是個再恰當不過的選擇。不過雖然 httpx
再大部份的情況下都可以取代 requests
,還提供了更多的功能,但並不是 100% 完全相同,在某些極端情況下還是會有行為差異的。
所以如果應用程式出現了難以理解的錯誤,不能完全把這種可能性排除掉。祝福大家(包括我)寫的程式都能越來越精簡、效能越來越好、研究的項目也都會出現大幅度的突破!