Web APIを作るために必要な知識をまとめてみる

# はじめに

「新人プログラマ応援」としていますが、私が新しいAPIを書く際に「あれ？これってどこらへん見れば書いてあるっけ？」というのをまとめておきたかったというのが主な目的です。一応、プログラミング言語やフレームワークに依存しないWebに関する知識を一通り得られるようにしていますのでよろしければお読みください。

なお、「フレームワークに依存しない」とは言うものの実装例を示したいものもあるので実装例にはFlaskを使います。

# 各論に入る前に

Web APIというかAPIについて考えてみましょう。APIには次の3つが必要です。

- APIの名前
- APIの引数
- APIの結果

Web APIの場合、これらが次のように対応します。

- APIの名前：パス
- APIの引数：クエリストリングやらボディーで渡すJSONやら
- APIの結果：ステータスコードとボディー

では各論に入りましょう。

# パス

APIの名前に相当するのがパスです。まだHTTPの話はしていませんがHTTPでAPIにアクセスする際には以下のように指定します。

```http
GET /messages HTTP/1.1
```

Flaskでこのリクエストに応答する処理を書くと以下のようになります。

```py
@app.route('/messages', methods=['GET'])
def get_messages():
    # 何処かからmessagesを取得
    return jsonify(messages)
```

個人的にはこのようなリクエストと処理関数が一対一に書けるマイクロフレームワークが最近のお気に入りです。

## パスパラメータ

引数についてはもう少し後で説明しますがパスに関連してパスパラメータの説明をします。パスパラメータとは`/messages/1`のように指定された`1`を引数として受け取る方法です。例えばFlaskでは以下のようになります。

```py
@app.route('/messages/<id>', methods=['GET'])
def get_message(id):
    # idに対応するmessageを取得
    return jsonify(message)
```

パスパラメータの指定方法はフレームワークにより異なっています。Railsは`/messages/:id`ですし、Springは`/messages/{id}`のようです。DjangoはFlaskと同じ、SinatraはRailsと同じなので同一言語の場合はどちらかがどちらかに寄せてる感じなのかな？

APIを設計する際はパスパラメータも含めて「きれいに」パスを設計するべきと言われています。
初めのうちはともかく機能追加していくと徐々に汚いパスになってしまうのが悩みどころです。


# HTTP

さてそれでは引数、とは残念ながらなりません。引数の前にHTTPについて順番に理解していく必要があります。

## メソッド

HTTPでサーバにリクエストを送るときに指定されるのがメソッドです。動詞（verb）とも呼ばれるようです。情報を取得するときは`GET`で、情報を送信するときは`POST`です。GETとPOST以外にもメソッドが定義されており、APIを作るときには「適切なメソッドで」呼ばれるようにすべきです。一般的には以下のようになります。

- GET：取得
- POST：作成
- PUT：更新
- DELETE：削除

Flaskの例だとこのようになります。

```py
@app.route('/messages/<id>', methods=['GET'])
def get_message(id):
    # idに対応するmessageを取得

@app.route('/messages', methods=['POST'])
def create_message():
    # 渡されたデータから新しいmessageを作成
    # ※メッセージ本体は「関数引数」とは別に取得

@app.route('/messages/<id>', methods=['PUT'])
def update_message(id):
    # idに対応するmessageを更新

@app.route('/messages/<id>', methods=['DELETE'])
def delete_messages(id):
    # idに対応するmessageを削除
```

## ステータスコード

引数の前に結果の話です。HTTPのリクエストを送ると三桁のステータスコードが返されます。大きく分けると200系が正常、400系が異常（エラー）です。

ステータスコードは[一覧](https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89)を見て考えます。
正常終了の場合は`200`、APIにより`201`や`204`を返すこともあります（まあ呼ぶ側も自分で書いているので細かく見てないですけど・・・）
「渡された引数がおかしい」場合は`400`、「指定された内容がない」場合は`404`を返します。認証・認可が必要なAPIを作る場合は`401`や`403`を返すこともあります。認証については後で説明します。

なお400系のステータスコードは正確には「クライアントエラー（クライアントの指定が何かおかしい）」でこれとは別に500系のサーバエラーがあります。ただし500系のステータスコードを自分で返すことはまずなく、APIやWebアプリケーションでこのステータスコードが返された場合はバグ（自分の書いたコードで例外が起きてフレームワークがキャッチし`500`を返した）ということになります。

もう一つステータスコードについて。「APIを呼び出す側」が400系もしくは500系のステータスコードを受け取ったときにどのような動作をするかも注意が必要です。エラーのステータスコードを受けてもそのまま返すライブラリもあれば例外を投げるライブラリもあります。

## ヘッダ

リクエストやレスポンスには以下の形式でヘッダ（header）を付けることができます。

```
Name: Value
```

ヘッダも様々なものが定義されているのですが、APIでの使用という点では`Content-Type`、後述するCORSや認証のためのヘッダぐらいをまずは覚えればいいと思います。

# クエリストリング

さてようやく引数にたどり着きました。まずはクエリストリングです。クエリストリングというのは、`/search?q=HTTP`のようなパスの`q=HTTP`の部分です。複数の引数を指定する場合は&で区切って、`/search?q=HTTP&date_from=20200901&date_to=20200930`みたいにします。普通のフレームワークであれば自力で解析処理を書く必要はなく個々の引数を取得するための方法が提供されているでしょう。Flaskの場合は以下のように書きます。

```py
from flask import request

@app.route('/search', methods=['GET'])
def search():
    query = request.args.get('q', '')
```

# JSON

構造化されたデータをやり取りする（サーバに送る、サーバから受け取る）場合には[JSON](https://www.json.org/json-ja.html)を使います。配列やオブジェクト（連想配列）を使うことができます。

```json
{
  "id": "2f7c42b2a88dc08d533e",
  "title": "Web APIを作るために必要な知識をまとめてみる",
  "tags": ["WebAPI", "新人プログラマ応援", "HTTP"]
}
```

データを送る際にはHTTPヘッダで`Content-Type: application/json`と指定します。普通のフレームワークであればこのContent-Typeが指定されていれば自動的に言語が提供するデータ構造に変換してくれるでしょう。例えばFlaskであれば以下のようになります。

```py
@app.route('/messages', methods=['POST']
def create_message():
    message = request.get_json()
```

また、結果をJSONで返す方法も提供されているでしょう。例によってFlaskでの書き方です。

```py
from flask import jsonify

@app.route('messages', methods=['GET'])
def get_messages():
    return jsonify(messages)
```

# CORS

ぼくがかんがえたさいこうのえーぴーあいをブラウザJSから呼び出してみるとよく引っかかるのがこれです。というかこのまとめを作ろうとしたそもそもの動機がこのCORS（を含めたWeb APIを作るときに気を付けるべきまとまった確認項目がほしいという理由）です。

セキュリティ上の理由から「JSを取得したドメインと別ドメインにJSでリクエストを送る（非同期通信をする）」とブラウザによりはねられます。理由はもちろんわかりますよね。

この制限を緩めるのがCORSです。CORSは以下のように動作します。[^1]

[^1]: 実際にはもう少し違う動作をすることもありますがここまでで説明したAPIであれば大体この流れになると思います。

1. ブラウザJSがリクエストを送ろうとしたら
2. 本体の送信前にブラウザがサーバに対してOPTIONSメソッドを送る（プリフライトリクエスト）
3. サーバが返してきたHTTPヘッダを調べて「送ろうとしているリクエスト」が許可されているかチェック
4. 許可されてなかったらエラー終了
5. 許可されてたら本体のリクエストを送る

OPTIONSメソッドのレスポンスとして使われるのが次の3つのHTTPヘッダです。他にもありますがまずはこの3つを覚えればいいでしょう。

<dl>
<dt>Access-Control-Allow-Origin</dt>
<dd>どのドメインからのリクエストは許可するか。<code>*</code>で全ドメイン</dd>
<dt>Access-Control-Allow-Methods</dt>
<dd>どのメソッドは許可するか</dd>
<dt>Access-Control-Allow-Headers</dt>
<dd>どのHTTPヘッダは許可するか</dd>
</dl>

なるほど、というわけでこの仕様（OPTIONSメソッド）を実装するわけですがまあめんどくさいです。ここで手抜きをするには例によってFlaskだと以下のようになります。

```py
from flask_cors import CORS
CORS(app)
```

超簡単:grinning:
と言いたいところですがデフォルトの設定はかなりざるです。このようなセキュリティチェックの機能がある意味をよく考え適切な設定を行うようにしましょう。

# 認証

最後に伏線しておいた認証です。というか認証自体の処理はおいといて「認証されていること」をどうサーバに渡すかの話です。HTTPヘッダの`Authorization`を使います。AuthorizationヘッダはBasic認証とかでも利用されますがAPIでもこのヘッダを利用して認証トークンを送ることができます。[^2]

[^2]: 正確には`Authorization`を使って認証トークンをやり取りすることが「合意されている」ということになります。

トークン自体は「ぼくのかんがえたさいきょうのとーくん」ではなくちゃんと[JWT](https://jwt.io/)などのように安全なものを使いましょう。いつも通りFlask、といきたいところですが、FlaskにJWT機能を提供するFlask-JWTはどうにも中身が把握しきれてなくて使ったことがありません[^3]。
オレオレトークン使っちゃったりもしてます。


[^3]: というかFlask-JWTメンテナンスされてなさそうだしFlask-JWT-Extendedの方がいい？

# あとがき

以上、Web APIを作る際に考えるべきこと、バックグラウンドとなる知識、CORSや認証情報の送信などについて説明してきました。APIを作る際に参考にしていただけたら幸いです。

# 参考文献

[Webを支える技術](https://gihyo.jp/magazine/wdpress/plus/978-4-7741-4204-3)
もはや読んでいることが必須レベルの名著ですね。数年前の「ちょいとWebについて体系的に学び直したいな」という学習衝動が起こった時に読みました。ただ、改めて見てみるとCORSの話がない[^4]など、「実際にAPI書くにはもう少し＋αが必要」という感じでもあります。

[^4]: そもそも当時CORSというものがなかったのだと思います。

[HTTP - Wikipedia](https://ja.wikipedia.org/wiki/Hypertext_Transfer_Protocol)
HTTPのこれってどうだっけというときに調べるサイトその1。

[HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP)
HTTPのこれってどうだっけというときに調べるサイトその2。