LoginSignup
5
1

More than 1 year has passed since last update.

FastAPI で レスポンスをgzip圧縮する

Posted at

tl;dr

FastAPI で responseをgzip圧縮で返却したい場合は

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()
app.add_middleware(GZipMiddleware, minimum_size=1000)

と記述する。

概要

HTTPでフロントエンドとの通信量を削減する方法の1つとして、コンテンツを特定の圧縮方式で圧縮して通信する方法がありますが、FastAPIでgzipによる圧縮が簡単にできるのでメモしておきます。

gzipでの通信のためには、

  • リクエスト側がhttp headerのAccept-encoding 属性に、gzipを指定する
  • サーバー側がgzipでの返却に対応する

の両方が必要です。

How To

公式ドキュメント にあるように、ミドルウェアを挟むだけでOKです。

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

BIG_CONTENT =
"""
返却するコンテンツ
"""

app = FastAPI()

# この行を追加する
app.add_middleware(GZipMiddleware, minimum_size=1000)

@app.get("/")
async def main():
    return BIG_CONTENT

minimum_sizeで、圧縮を適用するコンテンツの最低サイズを指定することができます。

試してみる

上記のmiddlewareを追加した状態でAPIプロセスを起動させ、エンドポイントにcurlでリクエストしてみます。contentの内容はBible in Englishを利用しました。

まずはgzipをacceptしないでリクエストしてみます。

❯ curl -vv  localhost:8000  > out 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< date: Mon, 18 Apr 2022 08:41:39 GMT
< server: uvicorn
< content-length: 4457912
< content-type: application/json
<
{ [102270 bytes data]
100 4353k  100 4353k    0     0   158M      0 --:--:-- --:--:-- --:--:--  202M
* Connection #0 to host localhost left intact

4457912 bytes返って来ているのがわかります。

それでは gzipを許可した上でリクエストしてみます

❯ curl -vv --compressed localhost:8000 > out2  
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.77.0
> Accept: */*
> Accept-Encoding: deflate, gzip
>
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< date: Mon, 18 Apr 2022 08:31:23 GMT
< server: uvicorn
< content-length: 1393083
< content-type: application/json
< content-encoding: gzip
< vary: Accept-Encoding
<
{ [102223 bytes data]
100 1360k  100 1360k    0     0  3924k      0 --:--:-- --:--:-- --:--:-- 3966k
* Connection #0 to host localhost left intact

Accept-Encoding: deflate, gzip が指定されています。結果、レスポンスとして

  • content-encoding: gzip
  • content-length: 1393083

と変わっています。
この場合、4457912 bytes → 1393083bytes とおよそ70%ほどデータサイズが削減されていることがわかります。

余談: gzipにするべきか

通信容量だけ見ればgzipにすべきですが、当然圧縮・解凍のためのCPUリソースとはトレードオフになります。そのため、コンテンツのサイズが一定以上のもののみ圧縮対象とすると良い(minimum_sizeの指定)と思います。

また、コンテンツ内容によっても圧縮率はだいぶ変わります。今回のテスト対象では70%ほど削減できましたが、画像等では圧縮の意味はほとんどないでしょう。

例えば「Can gzip Compression Really Improve Web Performance?」では、

  • 事前に圧縮したファイルを配信しても、オンラインで圧縮してもそれほどパフォーマンスには大きな差はなかった
  • HTMLやcombined JSなどは圧縮効果が高いものの、画像等では圧縮効果は小さい
  • 容量が77%程度削減された結果、レスポンスタイムは15%ほど改善された

などの結果が出ていますので、特にサイズの大きなテキストコンテンツを扱うAPIで利用するとよさそうです。また APIではないですが、JSなど静的リソースもあらかじめ圧縮しておくとよさそうですね。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1