読書メモ:Webを支える技術

読書メモ:Webを支える技術

November 12, 2022

以下の本を読んだ。記憶しておきたいところについてメモを残しておく。

ずっと前から読もうと思っていた本だったが、ようやく手に取った。さすがに今となっては掲載内容はほとんど見聞きしたことがあった。しかし踏み込んだ詳細については知らなかった、あるいはこれまで進んで知ろうともしていなかった部分が多く、勉強になった。2010 年に出版された本であることから、現在主流な技術からは1歩昔の内容になってはいる。例えば HTML5 や HTTP/2 は本書よりも後に利用可能となった技術。しかしながら本書は Web 概論の説明を目的としたものであり、内容を理解するにあたって技術の新旧は特に問題にはならない。


第 1 部:Web 概論 #

第 2 章:Web の歴史 #

REST の誕生 - P.19 #

Web のアーキテクチャを決定する重要な人物が現れます。当時、カリフォルニア大学アーバイン校の大学院生だった Roy Fielding(ロイ・フィールディング) です。

HTTP の仕様を策定している時期、Fielding は大学院生でもあったので、自身の研究として Web がなぜこんなにも成功したのか、なぜこれほど大規模なシステムが成立したのかについてソフトウェアアーキテクチャの観点から分析を行い、1つのアーキテクチャスタイルとしてまとめました。2000 年、彼はこのアーキテクチャスタイルを「REST(レスト)」(Representational State Transfer)と名づけ、博士論文として提出します。

第 3 章:REST - Web のアーキテクチャスタイル #

アーキテクチャスタイルの重要性 - P.25 #

REST は Web のアーキテクチャスタイルです。アーキテクチャスタイルは別名「(マクロ)アーキテクチャパターン」とも言い、複数のアーキテクチャに共通する性質、様式、作法あるいは流儀を指す言葉です。

パターンという言葉からデザインパターンを想像するかもしれませんが、いわゆるデザインパターンは別名「マイクロアーキテクチャパターン」とも言い、アーキテクチャスタイルよりも粒度の小さいクラスなどの設計様式を指します。

アーキテクチャスタイルとしての REST - P.26 #

REST は数あるアーキテクチャスタイルの中でも、特にネットワークシステムのアーキテクチャスタイルです。ネットワークシステムのアーキテクチャスタイルとして有名なのはクライアント/サーバです。そして Web はクライアント/サーバでもあります。

REST は、クライアント/サーバから派生したアーキテクチャスタイルなのです。素のクライアント/サーバアーキテクチャスタイルにいくつかの制約を加えていくと、REST というアーキテクチャスタイルになります。

REST は複数のアーキテクチャスタイルを組み合わせて構築した複合アーキテクチャスタイルです。

1.クライアント/サーバ

クライアントはサーバにリクエストを送り、サーバはそれに対してレスポンスを返します。

ユーザインタフェースはクライアントが担当するため、サーバはデータストレージとしての機能だけを提供すればよくなります。さらに、複数のサーバを組み合わせて冗長化することで、可用性を上げられます。

2.ステートレスサーバ

ステートレスとは、クライアントのアプリケーション状態をサーバで管理しないことを意味します。

クライアント/サーバにステートレス性を追加すると、アーキテクチャスタイルは「クライアント/ステートレスサーバ」(Client Stateless Server, CSS)になります。

3.キャッシュ

キャッシュとは、リソースの鮮度に基づいて、一度取得したリソースをクライアント側で使いまわす方式です。

キャッシュを追加したアーキテクチャスタイルは、「クライアント/キャッシュ/ステートレスサーバ」(Client Cache Stateless Server, C$SS)になります。

キャッシュのつづりは「cache」ですが、同じ発音で「cach」(お金)という単語もあるため、キャッシュのことを「$」記号で表現します。

4.統一インタフェース

統一インタフェースとは、URI で指し示したリソースに対する操作を、統一した限定的なインタフェースで行うアーキテクチャスタイルのことです。

たとえば HTTP 1.1 では GET や POST など 8 個のメソッドだけが定義されており、通常はこれ以上メソッドが増えません。

統一インタフェースを追加したアーキテクチャスタイルを「統一/クライアント/キャッシュ/ステートレスサーバ」(Uniform Client Cache Stateless Server, UC$SS)と呼びます。

5.階層化システム

たとえば Web サービスでは、サーバとクライアントの間にロードバランサを設置して負荷分散をしたり、プロキシを設置してアクセスを制限したりします。

このようにシステムをいくつかの階層に分離するアーキテクチャスタイルのことを階層化システムと言います。

階層化システムを追加したアーキテクチャスタイルを「統一/階層化/クライアント/キャッシュ/ステートレスサーバ」(Uniform Layered Client Cache Stateless Server, ULC$SS)と呼びます。

6.コードオンデマンド

コードオンデマンドは、プログラムコードをサーバからダウンロードし、クライアント側でそれを実行するアーキテクチャスタイルです。たとえば JavaScript や Flash、Java アプレットなどがこれに該当します。

コードオンデマンドを追加したアーキテクチャスタイルを「統一/階層化/コードオンデマンド/クライアント/キャッシュ/ステートレスサーバ」(Uniform Layered Code on Demand Client Cache Stateless Server, ULCODC$SS)と呼びます。

REST = ULCODC$SS

クライアント/サーバにコードオンデマンドまでを追加した複合アーキテクチャスタイルは「ULCODC$SS」という覚えにくい名前です。このアーキテクチャスタイルに Fielding は REST と名前を付けました。REST とはつまり、次の 6 つを組み合わせたアーキテクチャスタイルのことなのです。

  • クライアント/サーバ:ユーザインタフェースと処理を分離する
  • ステートレスサーバ:サーバ側でアプリケーション状態を持たない
  • キャッシュ:クライアントとサーバの通信回数と量を減らす
  • 統一インタフェース:インタフェースを固定する
  • 階層化システム:システムを階層に分離する
  • コードオンデマンド:プログラムをクライアントにダウンロードして実行する

第 3 部:HTTP #

第 7 章:HTTP メソッド #

HTTP メソッドと CRUD - P.89 #

  • Create: POST/PUT
  • Read: GET
  • Update: PUT
  • Delete: DELETE

GET

リソースの取得

GET は指定した URI の情報を取得します。

# リクエスト

GET /list HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

[
  {"uri": "http://example.jp/list/item1"},
  {"uri": "http://example.jp/list/item2"},
  {"uri": "http://example.jp/list/item3"},
  {"uri": "http://example.jp/list/item4"}
]

POST

子リソースの作成

POST の代表的な機能は、あるリソースに対する子リソースの作成です。

# リクエスト

POST /list HTTP/1.1
Host: example.jp

こんにちは!
# レスポンス

HTTP/1.1 201 Created
Location: http://example.jp/list/item5

こんにちは!

このリクエストでは http://example.jp/list に対して新しい子リソースを作成するように POST で指示しています。POST のボディには、新しく作成するリソースの内容を入れてあります。

レスポンスでは 201 Created というステータスコードが返ってきました。このステータスコードは新しいリソースを生成したことを示しています。そして Location ヘッダに新しいリソースの URI が入っています。ここでは http://example.jp/list/tem5 です。つまり /list の下に、新たに /list/item5 というリソース(子リソース)を生成したのです。

リソースへのデータの追加

子リソースの作成ほど一般的ではありませんが、POST の代表的な機能の 2 つめは既存リソースへのデータの追加です。

# リクエスト

GET /log HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

2010-10-10T10:10:00Z, GET /list, 200
2010-10-10T10:11:00Z, POST /list, 201
2010-10-10T10:20:00Z, GET /list, 200

このリソースの URI は http://example.jp/log で、CSV 形式にログを表現します。このリソースに新しいログを追加するには POST を使います。

# リクエスト

POST /log HTTP/1.1
Host: example.jp

2010-10-10T10:13:00Z, GET /log, 200
# レスポンス

HTTP/1.1 200 OK

レスポンスでは 201 Created ではなく 200 OK が返ってきました。リクエストが新規リソースの作成ではなく、データの追加を意味したからです。

データ追加として POST したときに、そのデータをリソースの末尾に追加するのか先頭に追加するのかはサーバ側の実装に依存します。また、そもそもあるリソースへの POST が作成を意味するのかデータ追加を意味するのかも実装に依存します。URI を見ただけでは POST の挙動はわかりません。POST の挙動は Web サービスや Web API の仕様書などで表現します。

ほかのメソッドでは対応できない処理

POST の 3 つめの機能は、ほかのメソッドでは対応できない処理の実行です。

検索結果を表現する次の URI を例に考えてみましょう。

http://example.jp/search?q={キーワード}

通常はこの URI を GET することで検索を実行しますが、キーワードが非常に長かった場合はどうなるでしょうか。URI の長さはキーワードに連動します。URI の仕様上は長さ制限がありませんが、実装上は 2,000 文字などの上限が存在します。そのような長いキーワードの場合、URI にキーワードを入れて GET する方式は利用できません。この場合、以下のように POST を用います。

# リクエスト

POST /search HTTP/1.1

q=very+long+keyword+foo+bar+..........

GET では URI に含めていたキーワードを、POST ではリクエストボディに入れられます。これによってどんなに長いキーワードでも実現できます。

このように、ほかのメソッドでは実現できない機能は POST で代用します。

PUT

リソースの更新

PUT の 1 つめの機能はリソースの更新です。

# リクエスト

GET /list/item5 HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

こんにちは!

このリソースを、PUT を使って「こんばんは!」に更新してみます。

# リクエスト

PUT /list/item5 HTTP/1.1
Host: example.jp

こんばんは!
# レスポンス

HTTP/1.1 200 OK

こんばんは!

この例では PUT へのレスポンスにリソースを更新した結果の表現が入っています。PUT へのレスポンスは、この例のようにボディに結果を入れてもよいですし、ボディには何も入れずに、レスポンスがボディを持たないことを示す 204 No Content を返してもかまいません。

リソースの作成

PUT の 2 つめの機能はリソースの作成です。

例えば http://example.jp/newitem がまだ存在しないとします。

# リクエスト

PUT /newitem HTTP/1.1
Host: example.jp

新しいリソース/newitemの内容
# レスポンス

HTTP/1.1 201 Created

新しいリソース/newitemの内容

この PUT は存在しない URI へのリクエストのため、サーバはリソースを新しく作成すると解釈し、リクエストが成功した場合は 201 Created を返します。POST の場合は新しく作成したリソースの URI が Location ヘッダで返りましたが、PUT の場合はクライアントがすでにリソースの URI を知っているため Location ヘッダを返す必要はありません。

/newitem がすでに存在していた場合は、先述したリソースの更新処理になります。

POST と PUT の使い分け

さて、POST でも PUT でもリソースを作成できることがわかりました。それでは両者をどのように使い分ければよいのでしょうか。これには正解は存在しませんが、設計上の指針として次の事実があります。

POST でリソースを作成する場合、クライアントはリソースの URI を指定できません。URI の決定権はサーバ側にあります。逆に PUT でリソースを作成する場合、リソースの URI はクライアントが決定します。

たとえば Twitter のようにつぶやきの URI をサーバ側で自動的に決定する Web サービスの場合は、POST を用いるのが一般的です。逆に、Wiki のようにクライアントが決めたタイトルがそのまま URI になる Web サービスの場合は、PUT を使う方が適しているでしょう。ただし PUT の場合、リソースの上書きを避けるためにクライアントで事前に URI の存在をチェックしなければならないかもしれません。

一般的に、クライアントがリソースの URI を決定できるということは、クライアントを作るプログラマがサーバの内部実装(URI にどの文字を許すのか、長さの制限はどれくらいかなど)を熟知していなければなりません。特別な理由がない限りは、リソースの作成は POST で行い URI もサーバ側で決定する、という設計が望ましいでしょう。

DELETE

リソースの削除

DELETE はその名のとおり、リソースを削除するメソッドです。

# リクエスト

DELETE /list/item2 HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

一般的に DELETE のレスポンスはボディを持ちません。そのためレスポンスのステータスコードにはボディがないという意味の 204 No Content が使われる場合もあります。

べき等性と安全性 - P.101 #

通信エラーが発生したときにリクエストをどのように回復するかは、HTTP において重要な課題です。HTTP の仕様では、プロトコルのステートレス性を保ちながら、この課題を解決するための工夫がなされています。

HTTP メソッドはその性質によって次のように分類できます。べき等と安全という性質が登場します。

べき等とは「ある操作を何回行っても結果が同じこと」を意味する数学用語です。安全とは「操作対象のリソースの状態を変化させないこと」を意味しいます。安全は「操作対象のリソースに副作用がないこと」とも言います。

  • GET, HEAD: べき等かつ安全
  • PUT, DELETE: べき等だが安全でない
  • POST: べき等でも安全でもない

最初に PUT を見てみましょう。http://example.jp/test を例に考えます。

# リクエスト

GET /test HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

<test>test1</test>

PUT で更新してみましょう。

# リクエスト

PUT /test HTTP/1.1
Host: example.jp

<test>test2</test>

リクエストが成功した場合、 要素の内容が「test1」から「test2」に更新されます。しかし通信エラーが起こり、クライアントがレスポンス(200 OK)を確認できなかったらどうなるでしょうか。そのときは、再び同じリクエストを送信できます。

# リクエスト

PUT /test HTTP/1.1
Host: example.jp

<test>test2</test>
# レスポンス

HTTP/1.1 200 OK

今度は 200 OK を確認できました。ここでは同じ PUT を 2 回送信しましたが、結果は 1 回送信したときと同じです。どちらの場合も、最終的には 要素の内容が「test2」になります。

今度は DELETE の例を見てみましょう。

# リクエスト

DELETE /test HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 200 OK

リソースの削除が完了し、200 OK が返ってきました。もう一度同じリクエストを送信してみます。

# リクエスト

DELETE /test HTTP/1.1
Host: example.jp
# レスポンス

HTTP/1.1 404 Not Found

すでにリソースを削除しているため、リソースが存在しないという意味のステータスコード 404 Not Found が返ってきました。しかし、「リソースが削除されている」という結果は先ほどと同じです。

このように PUT と DELET には、同じリクエストを複数回送信しても結果が変わらない性質(べき等性)があります。この性質により、クライアントは送信の重複を恐れることなく、PUT と DELETE を何回でも送信できます。

POST は安全でもべき等でもありません。すなわち、リクエストの結果で何が起きるかわかりません。クライアントは POST を複数回送ることに慎重でなければなりません。

第 8 章:ステータスコード #

ステータスコードの分類と意味 - P.112 #

ステータスコードは 3 桁の数字であり、先頭の数字によって次の 5 つに分類すると書かれています。

  • 1xx:処理中
    • 処理が継続していることを示す。クライアントはそのままリクエストを継続するか、サーバの指示に従ってプロトコルをアップデートして再送信する
  • 2xx:成功
    • リクエストが成功したことを示す
  • 3xx:リダイレクト
    • ほかのリソースへのリダイレクトを示す。クライアントはこのステータスコードを受け取ったとき、レスポンスメッセージの Location ヘッダを見て新しいリソースへ接続する
  • 4xx:クライアントエラー
    • クライアントエラーを示す。原因はクライアントのリクエストにある。エラーを解消しない限り正常な結果が得られないので、同じリクエストをそのまま再送信することはできない。
  • 5xx:サーバエラー
    • サーバエラーを示す。原因はサーバ側にある。サーバ側の原因が解決すれば、同一のリクエストを再送信して正常な結果が得られる可能性がある。

本書外の備忘録 #

本書中に登場したトピックのうち、自分で補完したものを以降に記録しておく。

JSONP(JSON with padding) #

ひと昔前は JSONP の名称をよく目にしたが、よく理解しないでいるうちに次第に見かけなくなった。本書が書かれたのは 2010 年であることから JSONP の言及もある。JSONP とはなんなのか改めて自分でも調べたところ、こちらで公開されていた PDF 資料がわかりやすかった。

https://sowel.co.jp/PDF_file/JavaScript/JS_JSONP.pdf

About JSONP

About JSONP

About JSONP

トランザクションリソース #

REST での実装時に直面する、複数のリソースにまたがった更新をひとまとまりに扱いたいケース、いわゆるトランザクションが必要なケースについて。

本書でもページを割いて説明されているが、次の記事も参考になった。