Web API The Good Parts の読書メモ

https://www.oreilly.co.jp/books/9784873116860/

# 1章　Web APIとは何か

- > 20世紀に現実世界のビジネスが直接販売から小売を通じた間接販売に移行したのと同様、ウェブの世界も個々のサービスが自らのウェブサイトで直接サービスを行う "直接販売" から、 API を提供し、それを複数組み合わせたアプリケーションがユーザーにサービスを行う "間接販売" モデルになった
- 設計の美しい Web API は
    - 使いやすい
    - 変更しやすい
    - 頑強である
    - 恥ずかしくない
- 原則
    - 仕様が決まっているものに関しては仕様に従う
    - 仕様が存在していないものに関してはデファクトスタンダードに従う
- 漫然と「ほかがそうだから」という理由で API を設計するのではなく、それがなぜそういう仕様になっているかを理解することで、より美しい API を設計できるでしょう
- REST における URI はリソースを表すため、動詞が API の中に入ることをよしとしない
    - しかし例えば検索の際に search という単語を出した方が、わかりやすい場合がある

# 2章　エンドポイントの設計とリクエストの形式

- 公開した API がどのように使われるのか、ユースケースを考える
- 良い URI: 覚えやすく、どんな機能を持つ URI なのかがひと目で分かる
    - 入力しやすい
        - bad: http://api.example.com/service/api/search
        - good: http://api.example.com/search
    - 人間が理解しやすい
        - bad: http://api.example.com/sv/u/
        - 適切な英語をよく調べる
    - 大文字小文字が混在していない
        - 基本小文字に統一
    - Hackable: 類推しやすい
    - サーバー側のアーキテクチャが反映されていない
        - cgi とか php とかないように
    - ルールが統一されている
- メソッドとエンドポイント
    - 理想
        - URI およびエンドポイントでリソースを表す
        - メソッドでリソースに対する処理を表す
    - エンドポイント
        - 複数形の名詞を使う
        - 単語に気をつける
        - スペースやエンコードが不要になるようにする
        - 単語を繋げなくて済むようにする
        - 繋げる場合はハイフンを使う
    - メソッド
        - `X-HTTP-Method-Override` ヘッダで本当は利用したいメソッドを記述する
            - DELETE や PUT が使えない時
        - `_method` を `application/x-www-form-urlencoded` コンテンツのデータの一部として送信する
            - `user=foo&_method=PUT`
- 相対位置での取得
    - limit と offset
    - データ量が増えると処理が遅くなる
    - 更新の速いデータだと同期ズレする
- 絶対位置での取得
    - id や日時を基準にする
- OAuth
    - Grant Type
        - Authorization Code: サーバーサイドで多くの処理を行うアプリ向け
        - Implicit: クライアントサイドで多くの処理を行うアプリ向け
        - Resource Owner Password Credentials: サーバーサイドを利用しないアプリ向け
            - `grant_type=password`
            - `username`
            - `password`
            - `scope`
            - `application/x-www-form-urlencoded` で送信する
            - 以下レスポンス
            - `access_token: asdf`
            - `token_type: bearer` (RFC 6750), ヘッダやボディに入れて使う
            - `expires_in: 123`
            - `refresh_token: `qwer`
        - Client Credentials: ユーザー単位での認可を行わないアプリ向け
- `self` や `me` のエンドポイントで自分の情報を取得する
- ホスト名
    - api.example.com: 第一候補
    - exampleapi.com: 第二候補
    - example.com/api: 微妙
- 1スクリーン1APIコール、1セーブ1APIコール
- Hypermedia As The Engine Of Application State
    - リンクによって複数のメディアを繋げる
    - `resp: {"x": 1, "link": "next.org"}`
    - クライアントが URI を知る必要がない
    - URI の変更がしやすくなる
    - Hackable にする必要がなくなる
    - SSKD なら現状価値がある

# 3章　レスポンスデータの設計

- フォーマットは基本 JSON
    - 稀に XML
- 指定方法
    - クエリパラメータ: `?format=json` 推奨
    - 拡張子: `.json`, ほとんど使わない
    - ヘッダ: `Accept: application/json`
- JSONP: JSON + JavaScript
    - `callback` クエリパラメータで指定する
    - script 要素は400や500が返るとスクリプトが止まる
    - 200でクライアントへ返せるようにする
    - レスポンスボディに本来のステータスなどを返す
    - 理由がなければ JSONP に対応しない
- 内部構造
    - API アクセス数が減るように設計する
    - API がバックエンドテーブルの構造を反映する必要はない
    - ユーザーが必要な情報を選べるようにする
        - `fields=a,b,c`
    - HTTP の仕様を活用する e.g. ヘッダ, ステータス
    - 基本はフラットな構造にする
        - 必要に応じて階層を作る
        - https://google.github.io/styleguide/jsoncstyleguide.xml
    - JSON はオブジェクトと配列をトップレベルにできる
    - オブジェクトがやや便利
        - データが何を示しているか分かり易い
        - オブジェクトに統一できる
        - セキュリティリスク低下 (JSON インジェクション)
    - 全体の件数が本当に必要か考える
        - 21件取っておけば、1ページ20件であれば次があるとわかる
- データ名
    - 多くのAPIで使われている一般的な単語を使う
        - 他と違う意味で使わないこと
    - 少ない単語数で表現する
    - 単語の連結方法はAPIを通して統一する
    - 無闇に省略しない
    - 単数系、複数形に気をつける
    - スネークケースの方が読みやすいらしい
        - キャメルケースが JavaScript / JSON では一般的
    - 性別: sex, gender
    - 日付: RFC3339 を使おう YYYY-MM-DDThh:mm:ss+09:00
    - 大きな整数: 文字列を使う
- レスポンスデータ
    - 再: API がバックエンドテーブルの構造を反映する必要はない
    - API のユースケースを考え、ユーザーが最もシンプルに扱うことができる設計にする
- エラー表現
    - ステータスコードを活用する
        - リクエストがちゃんと成功した時に200を返す
    - エラーの詳細
        - ヘッダに独自定義した項目を使う
        - ボディを使う、一般的
        - 詳細コード
        - 詳細情報 (のリンク)
        - 開発者向けと非開発者向けメッセージを用意する
    - ウェブサーバーなどを見直して、エラー時に HTML が返らないようにする
    - メンテナンス中は 503 を返す
        - `Retry-After: [datetime]` ヘッダを含める
        - https://developers.google.com/search/blog/2011/01/how-to-deal-with-planned-site-downtime
        - メンテナンス時間は長く見積もる
    - 状況によっては、不正確な情報を返して、ユーザーやシステムを守る

# 4章　HTTPの仕様を最大限利用する

- ステータスコード
    - https://developer.mozilla.org/ja/docs/Web/HTTP/Status
    - 200: 処理が成功したとき
    - 201: 何かが作られたとき
    - 202: 非同期的に受理されたとき
    - 204: No Content, delete した時など
    - 3XX
        - `Location` ヘッダにリダイレクト先のURLを埋め込む
        - リダイレクトをどう行うかはクライアントが決める
        - 定期的にリダイレクトが発生するAPIはアクセス回数を増やしてしまう
        - リダイレクトが起こりうるとわかっている場合はドキュメントに記述する
        - 307: 302を厳密にしたもの
        - 308: 301を厳密にしたもの
    - 400: Bad Request, その他
    - 401: Unauthorized, ユーザー認証不可
    - 403: Forbidden, 操作権限なし
    - 404: Not Found, 何が存在しないのかを伝えるべき
    - 405: Method Not Allowed
    - 406: Not Acceptable, API がクライアントの受け取りたいデータ形式に対応していない
    - 408: Reuest Timeout
    - 409: Conflict, ユニーク ID の登録が競合したなど
    - 410: Gone, email の検索などでこれを返すと課題になるかも
    - 413, 414: ボディやヘッダが長すぎる
    - 415: Content-Typeで指定されるデータには対応していない
    - 429: Too Many Request
    - 500: Internal Server Error
    - 503: Service Unavialable
- キャッシュ
    - 通信量の削減とレスポンス速度の改善、通信コスト削減
    - サービスの継続性
    - アクセス数の削減によるサーバーの維持費削減
    - Expiration Model
        - RFC 7234
        - 保存期限を決めておき、期限が切れたらアクセスする
        - `Expires: Fri, 01 Jan 2016 00:00:00 GMT` ヘッダ
            - 期限は1年以内にする
            - 日付の形式は RFC 1123 で決められている
                - `ddd, dd MMM yyyy HH:mm:ss GMT`
        - `Cache-Control: max-age=3600` ヘッダ
            - 同時利用の場合こちらが優先
            - max-age の計算には Date ヘッダを使う
    - Validation Model
        - 保持しているキャッシュが最新かを問い合わせる
        - 問い合わせのオーバーヘッドはあるが、データそのものを転送しなくて済む
        - 条件付きリクエスト
            - クライアントから、過去の時点でのデータを送る
                - 最終更新日付やエンティティタグ (下記)
            - 更新されていたらデータを返す
            - 更新されていなかったら 304 Not Modified を返す
            - `Last-Modified: ddd, dd mmm yyyy hh:mm:ss GMT`
                - クライアントは `If-Modified-Since` ヘッダを送る
            - `ETag: "asdf1234"`
                - クライアントは `If-None-Match` ヘッダを送る
                - 強い検証: 完全一致
                - 弱い検証: 完全一致ではないが意味合いは同一、広告など
    - Heuristic Expiration
        - クライアント側で過去の情報を元にキャッシュ期限を設ける
        - 基本的にはサーバー側で対応するべき
    - キャッシュして欲しくない時は `Cache-Control: no-store` を送る
    - Validation Model を使ってもらいたいときは `Cache-Control: no-cache`
    - `Vary`: URI 意外にデータを一意に特定するためのヘッダを指定する
        - `Server Driven Content Negotiation`
            - `Accept` で始まる一連のヘッダでレスポンスを変更する
                - e.g. `Accept-Language: ja`
    - `Cache-Control`
        - public: ユーザーが異なってもキャッシュできる
        - private: ユーザーごとにキャッシュが異なる
        - no-transform: プロキシはコンテンツのメディアタイプや内容を変えてはならない
        - must-revalidate: 常にオリジナルのサーバーへの再検証が必要
        - proxy-revalidate: プロキシはオリジナルサーバーへの再検証が必要
        - max-age: データが新鮮である期間
        - s-maxage: max-age と同じだが中継サーバーのみで使用
        - stale-while-revalidate
            - 秒数を指定する
            - プロキシが、期限を超えても裏で非同期にキャッシュの検証をして、キャッシュをレスポンスに使ってよい期間を示す
        - stale-if-error
            - 秒数を指定する
            - オリジンへのアクセスができなかったとき、保持している新線ではないキャッシュを返してよい秒数を示す
- メディアタイプの指定
    - text/plain
    - text/html
    - text/css
    - application/xml
    - application/json
    - application/javascript
    - application/rss+xml
    - application/atom+xml
    - application/octet-stream: バイナリデータ
    - application/zip
    - application/vnd.ms-excel
    - image/jpeg
    - image/png
    - image/svg+xml
    - multipart/form-data
    - video/mp4
    - `x-` で始まるサブタイプ: IANA に登録されていないタイプ
        - `x-www-form-urlencoded` は例外
    - 独自メディアタイプ: RFC 6838, Registration tree, prefix
        - Standard: なし
        - Vender: `vnd.`
        - Personal: `prs.`
        - Unregistered: `x.`
        - 独自ヘッダ
            - e.g. `X-GitHub-Media-Type: github.v3`
    - `Accept: text/html,apllication/xml;q=0.9,*/*`
        - q: 優先度
        - */*: 任意のタイプ
- 同一生成元ポリシーとクロスオリジンリソース共有
    - same origin policy
    - スキーム、ホスト、ポートで判断される
    - Cross Origin Resource Sharing
        - `Origin: url` を送る
        - `Access-Control-Allow-Origin: url` を返す
        - 許可されない場合 403 を返す
        - プリフライトリクエスト: リクエストを行う前に受け入れられるのか事前にチェックする
            - HEAD/GET/POST 以外の時
            - `Accept, Accepct-Language, Content-Language, Content-Type` 以外のヘッダを送るとき
            - `application/x-www-form-urlencode, multipart/form-data, text/plain` 以外をメディアタイプにしているとき
            - `OPTION` メソッドで送信する
            - ユーザー認証を送信する場合は `Access-Control-Allow-Credentials: true` を送る
- 独自ヘッダ: `X-` をプレフィクスにして作る
    - プライベートプロトコルなら無理にプレフィクスはつけない
    - むやみに `X-` をつけない
    - 統一する

# 5章　設計変更をしやすいWeb APIを作る

- API をバージョンで管理する
    - URL パスの一番上にバージョンを入れる
        - 最大派閥
    - セマンティックバージョニング
    - URL にマイナーバージョンまで含めるのは少数派
    - クエリ文字列にバージョンを入れる
        - 省略可能になる
        - 省略していると、意図せず挙動が変わっている可能性がある
        - パスのほうがよさそう
    - メディアタイプでバージョンを指定する
        - e.g. `application/vnd.github.v2+json`
        - `Accept: application/vnd.example.v2+json`
            - クライアントから送る
        - クライアントがメディアタイプを処理できない可能性がある
        - 独自ヘッダを使う
            - e.g. `GData-Version: 3.0`
- 指針
    - 後方互換性を保てる場合はマイナーバージョンだけ上げる
    - 整合性や整理のためにレスポンスデータの名前や形式を変えることでメジャーバージョンを挙げるべきではない
        - e.g. `gender: 1 or 2` を文字列に変えたいとき、 `genderStr: female or male` をレスポンスに追加する、など中身は変えない
        - deprecation warning を出す
    - セキュリティ起因の変更でメジャーバージョンを上げるのは妥当
    - ルールを整備して作り直したときメジャーバージョンを上げるのも妥当
    - バージョン指定がないときに、最新版を返すエイリアスは不要
        - 知らないうちに挙動が変わる可能性
- API の提供終了
    - 事前にアナウンスする
    - 410 を返す
    - スマホ向け API なら強制アップデートさせる
        - OS のバージョンを調査する
    - OS 対応とは分けておく
    - 利用規約にサポート期限を明記する
        - 逆にその期間はサポートする必要が出るため、注意すること
- オーケストレーション層
    - https://thenextweb.com/dd/2013/12/17/future-api-design-orchestration-layer
    - https://techblog.netflix.com/2014/03/the-netflix-dynamic-scripting-platform.html
    - サーバー側の汎用的な API とクライアントの間に、クライアントに合わせたインターフェース層を作る

# 6章　堅牢なWeb.APIを作る

- 安全性と安定性
- サーバーとクライアント間の情報不正入手
    - HTTPS による暗号化
    - HTTP Strict Transport Security
    - 中間者攻撃に弱い
        - 証明書発行元の確認
        - 有効期限
        - コモンネームの確認
            - 標準では行わないライブラリも多いため注意
    - SSL の脆弱性
    - 認証局が攻撃を受ける場合
        - Certificate and Public Key Pinning
        - 本物の証明書の発行元や公開鍵データのフィンガープリントをブラウザに埋め込んでおく
- サーバーの脆弱性による情報の不正入手や改ざん
- ブラウザからのアクセスを想定している API における問題
    - XSS: Cross Site Scripting
        - `{"data": "<script>alert('xss')</script>"}
            - `Content-Type: text/html` だと JS が実行される
        - `X-Content-Type-Options: nosniff` を使用する
        - 通常 JSON データのやり取りは XMLHttpRequest を使う
        - 特別な値のヘッダをつけることで、検証ができる
        - jQuery などでは `X-Requested-With` というヘッダを付与している
        - CORS を利用している場合、プリフライトリクエストが必要になってしまう
            - このため標準では付与しないフレームワークもある
            - 状況によっては明示的に付与する必要がある
        - JSON の文字列をエスケープする
            - `{"data": "\u003Cscript\u003Ealert('xss');\u003C\/script\u003E"}`
            - `+` もエスケープするほうが安全
    - XSRF
        - サイトをまたいだ偽造したリクエスト
        - ユーザー → 攻撃が仕込まれたサイト → 脆弱性のあるサイト
        - サーバー側のデータを変更するアクセスでは `POST`, `PUT`, `DELETE を使う
        - XSRF トークンを使う
            - `FORM` が `POST` を使えて、同一生成元ポリシーの影響を受けないため
            - 正規のフォームにトークンを埋め込む
            - あるいは、特別なヘッダをつけておく
    - JSON ハイジャック
        - API から JSON で送られてくる情報を第三者が盗み取ること
        - `SCRIPT` 要素に同一生成元ポリシーが適用されないことによる
        - JSON を SCRIPT 要素では読み込めないようにする
            - 特別なヘッダを要求するようにする
        - JSON をブラウザが必ず JSON と認識するようにする
            - きちんとメディアタイプを返す
            - `X-Content-Type-Options` を併用する
        - JSON を JavaScript として解釈不可能、あるいは実行時にデータを読み込めないようにする
            - 返り値をオブジェクト `{}` にする
            - 配列 `[]` は JavaScript として解釈可能
            - JSON の先頭に無限ループを仕込む
                - `for (;;); {}`
                - 残りの部分を文字列操作で残す
                - 最後の防波堤にはなる
    - ブラウザとスマホで異なるセッション管理方法を使う
- 悪意あるアクセスへの対策
    - パラメータの改ざん
        - 本来アクセスできない情報にはアクセスできないようにする
        - クライアントからの情報の整合性をチェックする
    - リクエストの再送信
        - 状態の管理
        - 多数のリクエストをエラーにする
        - 認証コードの追加
    - セキュリティ関係の HTTP ヘッダ
        - `X-Content-Type-Options`: メディアタイプの誤解釈を避ける
        - `X-XSS-Protection: 1; mode=block`
            - ブラウザが備えている XSS の検出、防御機能を有効にする
        - `X-Frame-Options: deny`
            - 指定したページをフレーム (`FRAME`, `IFRAME`) 内で読み込まれるのを防ぐ
        - `Content-Security-Policy`: 読み込んだ HTML 内の IMG, SCRIPT, LINK などの読込先としてどこを許可するのか指定する
        - `Strict-Transport-Security`: あるサイトへのブラウザからのアクセスを HTTPS のみに限定させる
            - とはいえこのヘッダがあっても先に HTTP リクエストが行われたら意味がない
        - `Public-Key-Pin`
            - HTTP-based public key pinning
            - SSL 証明書が偽造されたものかチェックする
            - ハッシュ値と有効期限がある
        - `Set-Cookie: session=asdf1234; Path=/; Secure; HttpOnly`
            - Secure: クッキーが HTTPS でのみ送信される
            - HttpOnly: クッキーが HTTP 形式の通信で使われる
                - JavaScript などではできない
- 大量アクセスへの対策
    - 時間
    - ユーザー/アプリケーション
    - API グループ
    - リミットの緩和
        - 一部の大口ユーザーが顧客のときとか
        - 無料枠と有料枠とか
    - 429 に `Retry-After` ヘッダをつけてもよい
    - レートリミットをユーザーに伝える
        - ドキュメント
        - API ダッシュボード
    - レスポンスでレートリミットを渡す
        - `X-RateLimit-Limit`: 単位時間当たりのアクセス上限
        - `X-RateLimit-Remaining`: アクセスできる残り回数
        - `X-RateLimit-Reset`: アクセス数がリセットされるタイミング
    - 実装
        - KVS
        - Apigee, 3SCALE

# 付録A　Web APIを公開する際にできること

ドキュメントの提供
https://apiblueprint.org
サンドボックスの提供
API コンソール
SDK の提供