最近、仕事ではバックエンド側で API(当ページの文脈では Web API1 を指す)を実装し、それをフロントエンド側で利用するという行為をひたすら続けている。
基本的によく使う API のリファレンス(Stripe APIやShopify Admin APIなど)や本を参考に API を組み立てている。 しかし、毎回参照するのは非効率的なので自分用に備忘録としてまとめることにした。
今回ベースとなるのはオライリーから出版されている「Web API: The Good Parts」である。 発売日は 2014 年と古いが Web API の性質上、古くなって使えないという箇所はほぼなく、特に違和感なく読み進められる。 基本が網羅されており、名著だと思う。
API は単にインタフェースに過ぎないため、その設計にあまり時間をかけるべきではない、可能な限りビジネスロジックの実装に時間を費やしたいと思っていた。 しかし、最初の設計を誤まると後々その負債が増大していくことを体験したので軽視できない。 このページでは今後そういったことがないように、自戒を込めて書き留めてゆく。
URI とメソッドの関係は、操作するものと操作方法の関係である。 1 つの URI のエンドポイントに異なるメソッドでアクセスすることで、リソースをどう扱うかをきちんと分離できる。 HTML は Form において POST と GET しか使用できない2。 その名残のせいなのか API においても更新・削除で POST を使っているケースがたまに見受けられるが、可能であればそれ以外(以下を参照)も積極的に使用したい。
メソッド名
説明
GET
リソースの取得
POST
リソースの新規登録
PUT
既存リソースの更新
DELETE
リソースの削除
PATCH
リソースの一部変更
HEAD
リソースのメタ情報の取得
※ PUT は送信したデータで元々のリソースを置き換えるものであるのに対し、PATCH ではその一部だけを更新したい場合に使用する。
HTML の Form では GET と POST しか使えないが、他にもクライアントによっては HTTP メソッドの利用が制限される場合がある。 その場合は API 側で GET/POST 以外のメソッドを POST を使って表現することを許可できる。
LSUDs 向けの API ではなるべく汎用的でわかりやすく使いやすい API の設計が最も重要である。 SSKDs 向けの場合でも基本原則は同じであるが、エンドユーザにとってのユーザ体験も考える必要がある。
例えば、トップページに新着商品や人気の商品、ユーザ情報などのデータを使う場合にそれぞれ API を投げるのは非効率である。 これは良いユーザ体験とは言えない。したがって、こういった場合ではトップページ表示用 API を作ってそれに 1 回アクセスするだけで全ての情報を取得できるようにする方が利便性は高くなる。
なお複数のクライアントアプリケーションに API を提供する場合はユースケースが多すぎて管理が大変になってしまうことは予測できる。 その場合は後述するオーケストレーション層を挟む方法がある。
LEVEL2 までは理解できるが、LEVEL3 の HATEOAS(hypermedia as the engine of application state)とはあまり聞き馴染みのない用語である。 HATEOAS とは設計方法のことで、 API の返すデータの中に、次に行う行動、取得するデータ等の URI をリンクとして含めるようにする。 そうすることで、そのデータを見れば次にどのエンドポイントにアクセスすれば良いかがわかる。
面白い概念であるが HATEOAS を使った API に今まで出会ったことがない。 HATEOAS を使うことの最大のメリットはクライアントがあらかじめ URI を知る必要がないため、URI の変更がしやすくなる点があげられる。 まだ、エンドポイントを人間が理解できる形式にする必要はないためセキュリティなどの観点から URI を想像しにくい形にできる。 LSUDs 向けの API では HATEOAS の概念が全然広まっていないことを考えるとこの先使われることはほぼないと思うが、SSKDs 向けの API ではニーズ次第で採用が可能かもしれない。
データフォーマットに関してはJSON にデフォルトとして対応して、需要や必要があれば XML に対応するというのが最も理に適っている。 XML の方が名前空間やスキーマ定義の仕様がきちんと決まっていたりと JSON に比べて表現力は豊かであるが大抵は JSON の構文でシンプルに表現できるので XML を使わなければならない理由はあまり存在しない。
その他に検討すべきデータフォーマットとしてはMessagePackがある。 MessagePack は効率の良いバイナリ形式のオブジェクト・シリアライズフォーマットであり、JSON の置き換えとして利用できる。 SSKDs 向けの API であればパフォーマンスを重視して採用するのもありかもしれない。
API で返すレスポンスデータを決定する際にまず考えるべきことは、API のアクセス回数がなるべく減るようにすることである(→ Chatty API を避ける)。 API のアクセス回数が増えると HTTP のオーバーヘッドが上がり、アプリケーションの速度が低下する。 まだ、サーバ側の負荷も増加してしまうなどメリットは何もない。
API のバックエンドのデータ構造から考えるとテーブルの内容をただそのまま返すだけというのは何らかの問題がある可能性があるため設計の見直しを行う。Web API は単なるデータベースのアクセスインタフェースではなく、アプリケーションのインタフェースであるということを意識する。
300 番台のステータスコードはリダイレクトを伝えるために利用するステータスコードである。 API の場合はウェブサイトのように URI の変更やサイトの移転に伴うリダイレクトを行うことはあまり好ましくない。 なぜなら、リダイレクトをどのように行うかはクライアントの実装次第であり、将来起こるか起こらないかわからないリダイレクトをクライアント側が実装している可能性はあまりないからである。
クライアントのリクエストに問題があった場合は 400 番台のステータスコードを返す。
ステータスコード
名前
説明
400
Bad Request
リクエストが正しくない
401
Unauthorized
アクセスが禁止されている
403
Forbidden
認証が必要
404
Not Found
指定したリソースが見つからない
405
Method Not Allowed
指定されたメソッドは使うことができない
406
Not Acceptable
Accept 関連のヘッダに受理できない内容が含まれている
408
Request Timeout
リクエストが時間以内に完了しなかった
409
Conflict
リソースが矛盾した
410
Gone
指定したリソースは消滅した
413
Request Entity Too Large
リクエストボディが大きすぎる
414
Request-URI Too Long
リクエストされた URI が長すぎる
415
Unsupported Media type
サポートしていないメディアタイプが指定された
429
Too Many Requests
リクエスト回数が多すぎる
API 開発を行う際は最低でも上記の不正なリクエストに対応するステータスコードとエラーメッセージを返すようにしておきたい(大抵は時間がなくて 400 エラーとしてまとめてしまうけれども……)。
CORS にはプリフライトリクエストという特別なサーバへの問い合わせ方法が定義されている。 プリフライトリクエストを行うことで生成元をまたいだリクエストを行う前にそのリクエストが受け入れられるかどうかを事前にチェックできる。 CORS に対応したブラウザでは単純リクエストではない場合、プリフライトリクエストを自動的に行う。
API が公開終了した際にはステータスコード 410(Gone)を返すようにしておく。 さらに公開終了した旨を知らせるエラーメッセージとそれらについての説明を API ドキュメントに書いておくとユーザからすればありがたい。 そうすることでクライアント側で 410 が返ってきたときの処理をあらかじめ書いておける(スマートフォンの実装であれば強制アップデートさせるなど)。
LSUDs 向けの API はなるべく汎用性のある設計にすることが求められる。 そのため、1 つのアクションを行うのに複数の API にアクセスしなければならなかったり、不要なデータも受け取らなければならなかったりするが、これはある程度仕方ないことである。 このような API を洋服のフリーサイズになぞらえて one-size-fits-all(OSFA)アプローチと呼ぶ人もいる。
一方で SSKDs 向けの API はそういった汎用性に縛られることはない。 その利用者のユースケースに合わせた使いやすい API を提供できる。 ただ、使い方が 1 つではなくいくつにも別れている場合はそれぞれに合わせて API を用意したり、維持することは大変になってくる。
その場合はサーバ側の汎用的な API とクライアントの間にオーケストレーション層を挟むことで様々なユースケースに対応できる。 BFF(Backend For Frontend)アーキテクチャで検索すると色々と事例が出てくるので参考にする。
XSS(Cross Site Scripting)とはユーザの入力を受け取ってそれをページの HTML に埋め込んで表示する際に、攻撃者から送られてきた悪意のある JavaScript などを実行させてしまう攻撃のことである。 XSS は Web アプリケーションだけではなく API でも実行され得るため対策が必要になる。
したがってユーザからの入力はどのような使われ方をするにしてもチェックが必要であるし¸、レスポンスの際にもデータの内容をチェックして、異常な値は削除しなければならない。 これらは Web アプリケーションを構築する場合は当然考慮しなければならないことだが、JSON などの形式でデータを返す場合はブラウザの挙動によって発生してしまう XSS が存在するため注意する。
例えば、JSON を返す API のContent-Type の値がtext/html だった場合、この JSON を返す URI に直接ブラウザでアクセスすると、このデータは HTML として解釈されるため script 要素に含まれる JavaScript が実行されてしまう。
XSRF(Cross Site Request Forgery)とはユーザが悪意のあるページにアクセスした際に、その中に埋め込まれたリンクなどを経由して、全く別のサイトへのリクエストが行われ、それによってそのユーザの意図しない処理が行われることである。 API において XSRF を避けるための方法は Web アプリケーションと大きくは変わらない。
まず 1 つ目はサーバ側のデータが変化するようなアクセスに関しては GET メソッドを利用しないということである。 これにより img 要素などに攻撃用のコードを埋め込むことができなくなる。
広く一般に公開している API であればユーザは API が行う HTTP 通信を見ることができる。 ユーザの中には、サーバに対して本来の使われ方とは違うアクセスを行うことでサーバの脆弱性を突いて自分に有利な情報や状況を作り出そうと企てる者がいてもおかしくはない(ここではハッカーなどの第三者ではなく正常に登録している利用者を指す)。
一般に公開している API であれば Dos 攻撃のような悪意を持ったハッカーからだけではなく、善良だが未熟な開発者が不注意で無限ループを書いてしまい結果的に Dos 攻撃になってしまうケースも考えられる。 そうならないための最も現実的な方法はユーザごとのアクセス数を制限することである。 その際は以下のようなことを決める必要がある。
何を使ってユーザを識別するか
ex. ユーザ / IP / アプリケーション
リミット値をいくつにするか
ex. 15 回 / 100 回 / 10000 回
どういう単位でリミット値を設定するか
ex. リクエスト回数
リミットのリセットをどういうタイミングで行うか
アクセス回数を制限する期間の開始時間を毎時 0 分のように決まった時間にするか、最初に API にアクセスしたタイミングにするか