https://fastapi.tiangolo.comより
API監視による快適生活
以前、Zabbix で某ワクチン接種サイトの更新を拾って、Slack で飛ばす環境を構築した。
その後、この環境を育てていって、今は株価や天気を世の中の公開APIから拾って、Slackへ通知する仕組みを確立したのだけど、ふと私生活の情報をAPI化すればもっと捗るのでは無いかと思いついた。
ZabbixでAPIの監視ができると知ってから、株価や天気などを監視させてるんだけど、財布の残高とか、洗濯物のたまり具合とか、プライベートな情報をAPI化すれば、より捗る気がしてきた。
— kWatanabe (@WWatchin) 2021年12月4日
クラウドは維持費かかるし、ローカルでAPIを作る方法を調べてみるか。Dockerでもいいけど、まずは手作業で。
調べてみたところ、FastAPI という素敵なWebフレームワークがあって、これを使うと Python でちょっとしたスクリプトを書く感覚で API が作れると分かったので、試してみることにした。
検証
検証環境
単純に動かす
APIアプリに特化したPythonフレームワークのFastAPIを導入する。 また、FastAPI で推奨となっている ASGI 対応 AP サーバの uvicornを併せて導入する。
$ sudo apt install python3 python3-fastapi uvicorn python3-uvloop python3-httptools
ちなみに Debian 10 以前では FastAPI は apt で提供されていないので pip で導入する。
$ sudo python3 -m pip install fastapi
任意の場所にサンプルコードを設置。仮に、/srv/uvicorn とする。
$ sudo mkdir -p /srv/uvicorn $ sudo vim /srv/uvicorn/main.py
内容はシンプルに、/ を GET すると Hello, World. が JSON で返ってくる API を作る。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return { "Hello": "World" }
試運転。
$ cd /srv/uvicorn $ uvicorn main:app --host 0.0.0.0 --port 8000
アクセスしてみる。
$ curl http://localhost:8000/
{"Hello":"World"}
以上おわり、楽チン過ぎる。
Uvicorn を Gunicorn のワーカーとして動かす
Uvicorn のドキュメントによると、 WSGI 対応 AP サーバの Gunicorn のワーカーとして動かすことが推奨されているので、Uvicorn を Gunicorn 配下で動くように変更する。
まず、Gunicorn を入れる。
$ sudo apt insatll gunicorn
なお、Debian 10 以前では、Python2 バージョンが入ってしまうので以下のようにする。
$ sudo apt install gunicorn3
Gunicorn と Uvicorn を連携させて、再度試運転。
$ cd /srv/uvicorn $ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
アクセスしてみる。
$ curl http://localhost:8000/
{"Hello":"World"}
いやっほー!
リバースプロキシ配下に置く
実運用では AP サーバ 単体を露出させることは無いだろうし、SSL の終端なども必要だろうということで、リバースプロキシとして nginx をかませる。 静的コンテンツとの区別やAPIバージョンごとにAPサーバを分けたい場合などのため、/api/v1 プレフィックスがついているものは Gunicorn へ飛ばすようにする。
nginx を入れる。
$ sudo apt install nginx
nginx.conf の server ディレクティブに以下を追記する。
...
location ^~ /api/v1/ {
proxy_pass http://GunicornサーバのIPアドレス:8000;
}
...
アプリで FastAPI クラスを作成する際にプレフィックスを指定する。
... app = FastAPI( root_path = "/api/v1/" ) ...
nginx につないでみる。
$ curl http://nginxのIPアドレス/api/v1/
{"Hello":"World"}
完璧すぎる。
CORS に対応させる
調子に乗って、もう一歩。今回のように Zabbix から監視したい程度ならともかく、普通はAPIを素で使うことはあまりないだろうし、大概は何らかのフロントエンドアプリと疎通することになると思うので、CORS へ対処する必要がある。
FastAPI では、オリジンとして許可するドメインを FastAPI() クラスに与えることで解決できる。こんな感じ。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI( root_path = "/api/v1/" )
origins = [
"http://excample.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def read_root():
return { "Hello": "World" }
おわりに
これで FastAPI を使って API アプリを作るための殻ができあがった。あとは好きに API を実装すればいい。
URIとHTTPメソッドによるルーティングはデコレータ (@app.get("/"))、リクエストボディのバリデーションは型ヒント、レスポンスはディクショナリを渡してあげれば (return { "Hello": "World" })、面倒な HTTP 通信やJSONのパースなどは全部やってくれる。