『Webを支える技術』を読んで学んだこと
第1部 Web概論
インターフェース
ユーザインタフェースとしてのWeb:人間向けのインタフェース
APIとしてのWeb:プログラム向けのインタフェース
ハイパーテキストとハイパーメディア
ハイパーテキスト:文字情報中心の文書を相互にリンクさせた概念
ハイパーメディア:ハイパーテキストの考え方を拡張し、音楽や動画など多様なメディアを相互にリンクさせた概念
RESTとリソース
RESTという名前の由来
HTTPはもともとハイパーテキストを「転送」(Transfer)するためのプロトコルであったが、実際にはハイパーテキスト以外の様々なものを運んでいる。何を運んでいるのかと言えば「リソースの状態」(Resource State)の「表現」(Representation)であるという考えが、REST(Representational State Transfer)の名前の由来である。
リソースの表現と状態
リソースは「Web上に存在する情報」という抽象的な概念である。サーバとクライアントの間で実際にリソースをやりとりする時には、何らかの具体的なデータ(「リソースの表現」(Resource Representation)と呼ぶ)を送信し合う。
1つのリソースは複数の表現を持つことができ、リソースには状態がある。時間の経過に従ってリソースの状態が変化すると、その表現も変化する。
RESTアーキテクチャスタイルの構成
RESTは複数のアーキテクチャスタイルを組み合わせて構築した複合アーキテクチャスタイルである。
クライアント/サーバ
クライアントはサーバにリクエストを送り、サーバはそれに対してレスポンスを返す。
単一のコンピュータ上で全てを処理するのではなく、クライアントとサーバに分離して処理できる利点がある。
ステートレスサーバ
クライアントのアプリケーション状態をサーバで管理しない。
サーバ側の実装を簡略化できる利点がある。Cookieを使ったセッション管理はステートレスサーバの利点をあえて捨てている。
キャッシュ
リソースの鮮度に基づいて、一度取得したリソースをクライアント側で使い回す。
クライアントとサーバ間の通信を減らすことでネットワーク帯域の利用や処理時間を縮小し、より効率的に処理できる利点がある。ただし、古いキャッシュを利用することで情報の信頼性が下がる危険性もある。
統一インタフェース
URIで指し示したリソースに対する操作を、統一した限定的なインタフェースで行う。
HTTP1.1でGETやPOSTなど8個のみHTTPメソッドが定義されているように、インタフェースの柔軟性に制限を加えることで全体のアーキテクチャがシンプルになるという利点がある。
階層化システム
システムをいくつかの階層に分離する。
クライアントとサーバの間に、ロードバランサを設置して負荷分散をしたり、プロキシを設置してアクセス制限をしたりできる利点がある。
コードオンデマンド
プログラムコード(JavaScriptなど)をサーバからダウンロードしクライアント側でそれを実行する。
クライアントプログラムにあらかじめ用意した機能だけでなく、新しい機能を追加していける利点がある。
第2部 URI
URIの構文
URIを構成するURIスキームは、そのURIが利用するプロトコルを示すのが一般的で、URNなど、正確にはプロトコルを示さないURIスキームも存在する。
インターネット上で必ず一意になるホスト名と、ホスト内で必ず一意になる階層的なパス、を組み合わせることで、あるリソースのURIが世界中で一意となる。
ベースURI
相対URIは、そのままでは起点となるURIがどこか分からないため、クライアントが解釈できない。この起点となるURIを指定するのがベースURIである。「/」から始まる相対URIは、ホスト名からのパスとして解釈する。
URIで使用できる文字
日本語などの、ASCII(American Standard Code for Information Interchange)以外の文字をURIに入れるときは、%エンコーディング
(またはURI(URL)エンコーディング
)という方式を用いる。
https://ja.wikipedia.org/wiki/あ
という見た目上のURIは、実際には
https://ja.wikipedia.org/wiki/%E3%81%82
というURIとして、ブラウザ/サーバ間で転送される。
マトリクスURI
複数パラメータの組み合わせで表現するリソースにはマトリクスURIを用いる。マトリクスURIでは、階層構造を表現するスラッシュの代わりに、複数の軸のパラメータをセミコロンかカンマで区切る。
;
はパラメータの順序が意味を持たない場合に、https://example.jp/map/lat=35.705471;lng=139.751898
のように使い、,
はパラメータの順序が意味を持つ場合に、https://example.jp/map/35.705471,139.751898
のように使う。
第3部 HTTP
階層型プロトコルにおけるアプリケーション層
TCPでプログラムを作るときは、ソケットと呼ばれるライブラリを使うのが一般的である。ソケットはネットワークでのやりとりを抽象化したAPIで、接続・送信・受信・切断などの基本的な機能を備えている。HTTPサーバやブラウザはソケットを用いて実装する。
ステートフル/ステートレスの欠点
- ステートフル:不特定多数のクライアントを相手にする場合は、クライアントごとに接続するサーバを特定できない。そのため、複数のサーバ間でアプリケーション状態を扱えるようにしなければならず、スケールアウトしにくい。
- ステートレス:クライアントは毎回必要な情報を全て送信しなければならないため、送信するデータ量が多くなったり、認証などDBアクセスを伴いサーバに負荷をかける処理を繰り返したりするハメになる。
HTTPメソッド
POSTについて
- そのデータをリソースの末尾に追加するのか、先頭に追加するのか
- リソースへのPOSTが作成を意味するのか、データ追加を意味するのか
はサーバ側の実装に依存するため、POSTの挙動はWebサービスやWeb APIの仕様書などで表現する。
HEADについて
HEADに対するレスポンスにはボディが含まれないため、この性質を利用すると、ネットワークの帯域を節約しながらリソースの大きさを調べたり、リソースの更新日時を取得したりできる。
冪等性と安全性
- 冪等:ある操作を何回行なっても結果が同じこと
- 安全:操作対象のリソースの状態を変化させない(副作用がない)こと
ステータスコード
- ステータスコードを先頭の数字で分類するのは、クライアントとサーバの約束事を最小限に抑え、クライアントとサーバの結び付きをなるべく緩やかに、すなわち疎結合にするための工夫である。
HTTPヘッダ
- 認証やキャッシュなどの機能は、HTTPヘッダをHTTPメソッドやステータスコードと組み合わせて初めて実現できる。
- HTTPヘッダやMIME(Multipurpose Internet Mail Extensions)メディアタイプは電子メールから拝借してきた仕様である。
- あるリソースにアクセス制御がかかっている場合、ステータスコード
401 Unauthorized
とWWW-Authenticate
ヘッダを利用して、クライアントにリソースへのアクセスに必要な認証情報を通知できる。 - OpenIDは、シンプルなシングルサインオンを実現する仕様。
- OAuthは、Webサービス間でデータをやり取りできるようにするための仕様。
- より複雑なキャッシュを可能にするため、HTTP1.1ではCache-Controlヘッダを追加され、HTTP1.0のPragmaヘッダとExpiresヘッダを完全に代用できるようになった。
- 条件付きGETは、そのリソースがLast-ModifiedヘッダまたはETagヘッダを持っているときに利用できる。
第4部 ハイパーメディアフォーマット
HTML
- HTMLの最も基本的な構成要素はヘッダとボディであり、HTTPメッセージ(リクエストメッセージ&レスポンスメッセージ)と同様に、ヘッダには文書のメタデータを、ボディには文書の内容そのものを入れる。
- フォームによるGETは、キーワード検索などユーザからの入力によってURIを生成するときに利用する。
- <a>要素と<link>要素はrel属性を持つことができ、リンク元のリソースとリンク先のリソースがどのような関係にあるかを記述する。
Atom Syndication Format & Atom Publishing Protocol
-
Atom Syndication Format
はXMLフォーマットで、ブログなどの更新情報を配信するためのフィードとして知られている。タイトル、著者、更新日時といった基本的なメタデータを備えたリソース表現のためのフォーマットである。 -
Atom Publishing Protocol
はAtom Syndication Formatが規定したフィードやエントリで表現するリソースの編集、いわゆるCRUD操作を実現するためのプロトコルである。
JSON
- JSONはXMLのように文書をマークアップすることには向いていないが、ハッシュや配列といったプログラミング言語から扱いやすいデータ構造を記述できることが特徴。
- JavaScriptではメンバの名前に識別子や数値もとれるが、JSONではメンバの名前は常に文字列である。
- JSONには組み込み型としての日時型がないため、標準的なフォーマット(ISO 8601)で日時を格納するのが望ましい。
第5部 Webサービスの設計
読み取り専用のWebサービスの設計
- リソースを設計する際には、WebサービスとWeb APIを分けて考えないことが大切。
- 機能をリソースに落とし込む場合、機能の結果をリソースとして捉えることが大切。
- 一部がパラメータとして変動するURIを記述する場合、
{}
でパラメータ部分をhttps://example.jp/search?q={query}
のように囲む、URI Templates
という表記が一般的である。
書き込み可能なWebサービスの設計
- リソースの作成方法は2つあり、1つはファクトリリソース(リソースを作成するための特別なリソース)へ
POST
する方法で、もう1つはPUT
で直接作成する方法である。 -
PUT
の場合、新しく作成したいリソースのURIにリクエストを直接送る。また、作成するリソースのURIをクライアントが既に知っているため、レスポンスメッセージにLocationヘッダは含めない。 - リソースの更新は基本的に
PUT
で行うが、PUT
だと更新対象のリソースをURIで指定しなければならないため、バッチ更新はPOST
で行う。- リソース全体を送信する更新方法を
バルクアップデート
と呼び、クライアントの実装が簡単になる反面、送受信するデータが大きくなるという欠点がある。 - リソースの一部分だけを送信する更新方法を
パーシャルアップデート
と呼び、送受信するデータが少なくなる反面、GET
したリソースの一部を修正してそのままPUT
するという使い方ができなくなる。
- リソース全体を送信する更新方法を
- 悲観的ロックは、ユーザをあまり信用せずに、競合が発生しないようにする排他制御の方法。
- 楽観的ロックは、通常の編集ではロックせずに、競合が起きた時に対処する方法。
- 条件付きPUTのやりとりでは、Etagの値を得るためにまずリソースをGETで取得する。条件付き
PUT
と条件付きDELETE
では、If-Unmodified-Since
、If-Match
を用い、条件付きGET
では、If-Modified-Since
、If-None-Match
を用いる。
リソースの設計
関係モデル(Relational Model)では、データの重複を防ぐために通常は正規化を行うが、リソースの設計では、一つ一つのリソースをそれ自身で全てを表現できるように自己記述的にするためにあえて正規化を崩す。これはWebが分散システムであることに起因する。ネットワーク帯域やサーバへの接続をできるだけ減らすために、なるべく一度のリクエストで、クライアントが必要とする全てのデータが取得できるようにリソースを設計する。