FastAPI 色々なレスポンスまとめ

# この記事は？

自分の備忘録も兼ねて、FastAPI のレスポンスについてまとめました。FastAPI の基本的な記述方法、HTTP の基礎的な知識については、ここでは説明していません。チートシート的なものとしてご活用ください。

以下のドキュメントを元に (というか、ほぼコレの二番煎じを) 作成しました。

https://fastapi.tiangolo.com/ja/advanced/custom-response/

(6/27 追記) リクエストの受信についてはこちら:

https://qiita.com/aKuad/items/e4d89b24a717c955d701

# JSON レスポンス (単純な方法)

FastAPI では、デフォルトでは JSON 形式のレスポンスをするようになっています。`return` に辞書オブジェクトを配置すると、JSON に変換されてレスポンスされます。

```py
from fastapi import FastAPI
app = FastAPI()

@app.get("/")
def index():
  return {"mes": "hello"}
```

プレーンテキストを記述しても、MIME タイプは `application/json` としてレスポンスされてしまうので注意。

```py
@app.get("/")
def index():
  return "hello"
# クライアントには "content-type: application/json" で届く
```

MIME タイプを調整した上でレスポンスするには、後述の方法を利用。

# JSON レスポンス (丁寧な方法)

FastAPI では `Response` オブジェクトを `return` することで、ヘッダやステータスコード等を指定したレスポンスをすることができます。

`Response` を継承したクラスで `JSONResponse` 等があり、これらは各種レスポンスに対応した初期値が設定されています。例えば `JSONResponse` の場合、ヘッダに初期値として `content-type: application/json; chareset: utf-8` が指定されています。

```py
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()

@app.get("/")
def index():
  return JSONResponse(content={"mes": "hello"})
  # 最初の引数に指定時 content= 省略可, 他レスポンスも同様
  # 例: return JSONResponse({"mes": "hello"})
```

:::note
同様の働きをするもので `ORJSONResponse` があり、ドキュメントによるとこちらの方が高速らしいです。`UJSONResponse` というクラスもありますが、ドキュメントを見る限り使う意味は無さそうです。
:::

# HTML レスポンス (直書き)

デフォルトの `content-type` は `text/html` です。

```py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()

@app.get("/")
def index():
  return HTMLResponse(content="<h1>hello</h1>")
```

# HTML レスポンス (Jinja2Template 使用)

十分大規模な HTML を返す場合、直書きはソースコードの可読性を落とすので良くありません。別ファイルから HTML を読み込んでレスポンスをする場合、Jinja2Template を使うのがスマートです。

Jinja2Template を使用するには、pip からインストールが必要です。

```sh
pip install jinja2
```

```py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
app = FastAPI(request: Request)
templates = Jinja2Templates(directory="templates")

@app.get("/")
def index(request: Request):
  return templates.TemplateResponse("page.html", {"request": request})
```

# PlainTextResponse

デフォルトの `content-type` は `text/plain` です。

```py
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()

@app.get("/")
def index():
  return PlainTextResponse(content="hello")
```

# RedirectResponse

ステータスコードは、デフォルトで 307 (Temporary Redirect) が設定されます。
必要に応じ、別のステータスコードを指定します。

```py
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()

@app.get("/")
def index():
  return RedirectResponse(url="http://example.com")
  # 最初の引数に指定時 url= 省略可
  # status_code= でステータスコード指定
  # 例: return RedirectResponse("http://example.com", status_code="308")
```

# FileResponse

`media_type` や `headers` で特に指定をしなかった場合、`content-type` は推測されて設定されます。

```py
from fastapi import FastAPI
from fastapi.responses import FileResponse
app = FastAPI()

@app.get("/")
def index():
  return FileResponse(path="path-to/file.txt", media_type="text/plain")
  # 最初の引数に指定時 path= 省略可
  # media_type の代わりにヘッダを自力で記述することも可
  # 例: return FileResponse("path-to/file.txt", headers={"content-type": "text/plain; charset: utf-8"})
```

# カスタムした任意のレスポンス

`Response` クラスで、ヘッダ等の初期値が指定されていない状態のレスポンスオブジェクトを作成できます。

```py
from fastapi import FastAPI
from fastapi.responses import Response
app = FastAPI()

@app.get("/")
def index():
  res = Response(content="hello",
                 media_type="text/plain",
                 #headers={"content-type": "text/plain; charset: utf-8"},
                 # ヘッダを自力で記述しても良い
                 status_code=200)
  return res
```

適切な `media_type` (MIME タイプ) を設定した上で、`content` に `bytes` 型データを入れると、バイナリデータを送ることができるようです。
`FileResponse` と違い、ディスク上のファイルでなく、変数に入れられているファイル (バイナリデータ) を送ることができます。

# その他メモ

## レスポンスクラスの指定方法

### 書き方1: `response_class` を指定

`return` には、直接ボディの内容を書きます。

```py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()

@app.get("/", response_class=HTMLResponse)
def index():
  return """
    <h1>hello</h1>
  """
```

### 書き方2: `Response` オブジェクトを `return`

本記事で書いてきた方法です。

```py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()

@app.get("/")
def index():
  return HTMLResponse(content="<h1>hello</h1>")
```

## インスタンス化後に値の変更

`Response` オブジェクトの作成で、インスタンス化するときに各値を指定する方法と、インスタンス化後に各値を設定する方法があります。

```py
# ※※※ 推奨できない書き方 ※※※
@app.get("/")
def index():
  res = Response()
  res.body = b"hello"
  res.raw_headers = [(b'content-type', b'text/plain; charset: utf-8')]
  res.status_code = 200
  return res
```

後者の方法 (上記ソースコードの方法) はあまり推奨できません。理由としては･･･

* `content-length` 等のデータは、インスタンス化時に自動で設定してくれる
* ヘッダとボディのデータは `bytes` 変換しないと代入できないため、書き方が面倒

## デフォルトのレスポンスクラスの指定

デフォルトでは JSON がレスポンスされるようになっていますが、`FastAPI` をインスタンス化する際に、デフォルトのレスポンスクラスを指定することができます。

```py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI(default_response_class=HTMLResponse)

@app.get("/")
def index():
  return "<h1>hello</h1>"
```

## ステータスコード指定の書き方

FastAPI には `status` というモジュールが用意されており、その中には各種 HTTP ステータスコードが定数 (変数) で定義されています。

```status.py
(略)
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
(略)
```

```py
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
app = FastAPI()

@app.get("/")
def index():
  res = JSONResponse(content={"mes": "hello"},
                     status_code=status.HTTP_200_OK)
  return res
```

ステータスコードの意味が記述できるので、コードの可読性の向上につながるでしょう。