この度「Webを支える技術」という本を読んだので勉強のためにまとめました。
第1章 Webとは何か
現在最も重要なソフトウェアはブラウザで、ブラウザを通してインターネットの向こう側にあるWebサーバとやりとりをしている
様々なWebの用途
- Webサイト
- ポータルサイト&ショッピングサイト
- 動画投稿サイト
- その他いろいろ
- ユーザインタフェースとしてのWeb
- ネットワーク接続するデバイスの設定はブラウザで行うものがある
- HTMLによるヘルプの記述
- プログラムAPIとしてのWeb
- APIとしてのWebはプログラム向けのインターフェースでデータフォーマットにXMLやJSONのようなプログラムで解釈&処理しやすいものを使用する
そもそもWebとは何か?
Webの最も基本的な技術はHTTP、URI、HTMLである。
URIを使えば世界中のあらゆる情報を指し示すことが可能。
HTMLはそれらの情報を表現する文書フォーマット。
HTTPというプロコトルを使ってそれらの情報を取得できる。
ハイパーメディアについて
ハイパーメディアとはテキストや画像、音声、映像などさまざまなメディアを結びつけて構成したシステム。書籍は線形的で先頭から読むのに対してハイパーメディアは非線形的にユーザが自分でリンクを選択して情報を取得することができる。
ハイパーリンクとは情報どうしを結びつけて、HTMLで記述したWebページでは他のブラウザを使って自由に情報をたどれる。
分散システムについて
1つの中央コンピュータが全てを処理する形式を「集中システム」と呼ぶが、複数のコンピュータを組み合わせて処理を分散させるシステムを「分散システム」と呼び、1台のコンピュータで実行するよりも効率的に処理でき、膨大な情報を操作できる。
第2章 Webの歴史
Webの誕生
1990年11月12日、スイスのCERNという研究所でハイパーメディアを用いたインターネットベースの情報分散管理システムとしてWebの提案書が書かれた。その年のクリスマス休暇に最初のバージョンのブラウザとサーバーを完成させた。
Webの普及を一気に進めたのがイリノイ大学のNCSAが公開したブラウザであるMosaicで、これまで文字情報しか扱えなかったブラウザから進化してMosaicはインラインで画像を混在させることができた。
Webの標準化
Webを構成する技術であるHTTPとURIとHTMLの標準化が求められた。Webは普及が急速すぎたため各社の実装がバラバラで相互運用性に欠ける状態が発生した。
この問題を解決するために1994年にBerners-Leeが中心となってWebの標準化を行う団体であるW3Cを設立した。
当時の状況は「ブラウザ戦争」と呼ばれた。Netscape NavigatorとInternet Explorerが独自拡張を繰り返した結果、両者でHTMLとCSSのレンダリング結果が大きく異なり、開発者が対応を迫られていた。
RESTの誕生
Roy FieldingがこれまでApach httpdやlibwww-perlなどの実装経験をもとにHTTP1.0とHTTP1.1の使用策定に関わった。
当時大学院生だったので自身の研究としてWebがなぜこんなに成功したのか、なぜこれほど大規模なシステムが成立したのかをソフトウェアアーキテクチャの観点から分析を行い、1つのアーキテクチャスタイルとしてまとめた。2000年に彼はこのアーキテクチャを「REST」と名付け、博士論文として提出した。
第3章 REST Webのアーキテクチャスタイル
Restについて
Restの重要な概念の1つにリソース(Resource)がある。
リソースとは「Web上に存在する、名前を持ったありとあらゆる情報」でリソースの名前はあるリソースと他のリソースと区別して指し示すためのもの。
リソースの例
- 東京の天気予報
- Qiitaのこのページ
- githubのアカウントページ
これらは全てリソースでリソースの名前とはURIのことを表す。
リソースはWeb上の情報で世界中の無数のリソースは、それぞれのURIで一意の名前を持つのでURIを用いることでプログラムはリソースが表現する情報にアクセス可能。
また、1つのリソースは複数のURIを持つことができる。
例えば今日が2020年1月1日だとすると2つのURIは同じリソースを指す。
http://weather.example.jp/tokyo/today
http://weather.example.jp/tokyo/2020-01-01
RESTは次の6つを組み合わせたアーキテクチャスタイル
- クライアント/サーバ:ユーザインタフェースと処理を分離する
- ステートレスサーバ:サーバ側でアプリケーション状態を持たない
- キャッシュ:クライアントとサーバの通信回数と量を減らす
- 統一インターフェース:インターフェースを固定する
- 階層化システム:システムを階層に分離する
- コードオンデマンド:プログラムをクライアントにダウンロードして実行する
①クライアント/サーバ
WebはHTTPというプロコトルでクライアントとサーバが通信する。つまりクライアントはサーバーにリクエストを送り、サーバはそれに対してレスポンスを返す。
クライアント/サーバの利点は単一のコンピュータで全てを処理するのではなく、クライアントとサーバに分離していることでこれにより現在のWebはPCだけでなく携帯電話やゲーム機からもアクセスできる。
②ステートレスサーバ
ステートレスとはクライアントのアプリケーション状態をサーバで管理しないこと。(ステートレスについての詳しい説明は第6章を参照)
これによりサーバの実装を簡略化できるのでクライアントからのリクエストに応えたあとにすぐにサーバの計算機リソースを解放できる。
Cookieをつかったセッション管理はRESTの視点から見ると間違ったHTTPの拡張だがCookieを使ったフォーム認証をやめるわけにはいかないのでステートレスサーバの利点を捨てることを理解した上で必要最低限にする。
③キャッシュ
キャッシュの利点はサーバとクライアントの通信を減らすことができる。
ただし、古いキャッシュを利用してしまい情報の信頼度が下がる可能性もある。
④統一インターフェース
統一インターフェースとはURIで指し示したリソースに対する操作を統一した限定的なインターフェースで行うアーキテクチャスタイルのこと。
例えば、HTTP1.1ではGETやPOSTなど8個のメソッドだけが定義されており、通常はこれ以上メソッドが増えない。これは厳しい制約のように感じるが、インターフェースの柔軟性に制限を加えることで全体のアーキテクチャがシンプルになる。また、インターフェースを統一することでクライアントとサーバの独立性が向上する。
⑤階層化システム
統一インターフェースの利点としてシステム全体が階層化しやすいことがある。
たとえばWebサービスではサーバとクライアントの間にロードバランサを設置し負荷分散をしたり、プロキシを設置してアクセス制限する。
クライアントからするとサーバもプロキシも同じインターフェースで接続できるので、接続先を意識する必要がない。
これはサーバやプロキシなどの各コンポーネント間のインターフェースをHTTPで統一しているから実現している。
⑥コードオンデマンド
コードオンデマンドとはプログラムコードをサーバからダウンロードしたり、クライアント側でそれを実現するアーキテクチャスタイルで例えばJavaScriptやFlashなどがこれに該当する。
コードオンデマンドの利点はクライアントをあとから拡張できることで、クライアントプログラムにあらかじめ用意した機能だけでなく、新しい機能を追加できること。
欠点としてはネットワーク通信におけるプロコトルの可視性が低下すること。
RESTに基づいたアーキテクチャを構築する場合でも上記の規約のうちいくつか除外しても良い。
無理にRESTを採用する必要はなく、P2Pのような別のアーキテクチャが適していたらそちらを使うべき。
第4章URIの仕様
URIはリソースを統一的に識別するIDのこと。
URIを使うとWeb上に存在する全てのリソースを一意に示せる。
URIの構文
http://blog.example.jp/entries/1
このようなURIがあったとき以下のようにパーツを分解できる。
- URIスキーム:http
- ホスト名:blog.example.jp
- パス:/entries/1
URIはURIスキームから始まり、そのURIが使用するプロコトルを示すのが一般的。
ホスト名はDNSで名前が解決できるドメイン名かIPアドレスで、インターネット上で必ず一意になる。
ホスト名の後には階層を表すパスが続く。パスはそのホストの中でリソースを一意に指し示す。
複雑なURIの例
http://yohei:pass@blog.example.jp:8000/search?q=test&debug=true#n10
このURIは以下のように分けられる
- URIスキーム:http
- ユーザ情報:yohei:pass
- ホスト名:blog.example.jp
- ポート番号:8000
- パス:/search
- クエリパラメータ:q=test&debug=true
- URIフラグメント:#n10
ユーザ情報はリソースにアクセスするためのユーザ名とパスワードからなっていて「:」で区切る。
ユーザ情報の次に区切り文字である「@」があり、その後ろにホスト情報が続く。
ホスト情報はホスト名とポート番号からなり両者は「:」で区切られる。
ポート番号はこのホストにアクセスするときに用いられるTCPのポート番号を表す。
ポート番号を省略した場合は各プロコトルのデフォルト値が使われる。HTTPのデフォルト値は80番。
パスの後ろには区切り文字である「?」が付き、名前 = 値形式のクエリが続く。クエリが複数続くときは「&」で連結する。
最後の「#」で始まる文字列は「URIフラグメント」と言い、その前までの文字列で表現するURIが指し示すリソース内部のさらに細かい部分を特定するときに利用する。
例えば、このリソースがHTML文書の場合はidの属性値が「n10」である要素を示すことになる。
相対URIと絶対URI
URIのパスは相対パスを書くことができる。
例:
絶対パス/foo/bar/baz
相対パスbaz
(起点が/foo/barの場合)
相対URIはそのままではクライアントが解釈できないのでクライアントに起点となるURIを指定しないといけない。
この起点となるURIを指定するのがベースURI。
ベースURIの与え方としてリソースのURIをベースURIとする方法やベースURIを明示的に指定する方法がある。
URIと文字
URIでは次の文字が使用できる。
- アルファベット:A-Za-z
- 数字:0-9
- 記号:-.~:@!$&'()
この文字列はASCⅡ文字で、URIには日本語の文字列を直接入れることができない。
日本語のようなASCⅡ以外の文字をURIに入れるときは、%エンコーディングという方式を用いる。
%エンコーディング
URI仕様が許可している文字以外をURIに入れるには、%エンコーディングでその文字をエンコードする。
例えばモダンなブラウザでひらがなの「あ」を解説するWikipediaのURIを見てみるとアドレス欄は以下のようになる。
http//ja.wikipedia.org/wiki/あ
この見た目上のURIは、実際には次の文字に展開されてブラウザとサーバの間で転送される。
http//ja.wikipedia.org/wiki/%E3%81%82
ひらがなの「あ」が「%E3%81%82」になった。
これは「あ」がUTF-8で「%E3%81%82」で表現されるから。
一般的にURIは大文字と小文字を区別して扱うが%エンコーディングで使用する文字は大文字も小文字も同じ意味で扱われる。(URI的には大文字推奨)
第5章URIの設計
クールなURIは変わらない
良いURIやきれいなURIのことを「クールURI」と呼ぶ。
良いURIとは後から変わらないURIのことで変わらないURIこそが最上のURIである。
変わりにくいURIを設計するための方針
- URIにプログラミング言語依存の拡張子を利用しない(.pl、.rb、.do、.jspなど)。
- URIに実装依存のパス名を利用しない(cbg-bin、servletなど)。
- URIにセッションIDを含めない
- URIはそのリソースを表現する名詞である。
①プログラミング言語に依存した拡張子やパスを含めない
プログラミング言語に依存したURIの例
http://example.jp/cgi-bin/login.pl
このURIには「cgi-bin」というパスと「.pl」という拡張子がある。
このようなURIは昔はよく見たが現在はほとんど見かけない。
理由として1つ目はGGIが廃れてしまったことで、リクエストのたびにプロセスを起動するCGI方式は性能面で難点があったので他の手法に取って代わった。
2つ目の理由は、実装言語の選択肢が増加したことで、GGIの時代はほとんどのWebサービスがPerlで書かれていたが、現在はRubyやPHPなど選択肢がたくさんある。
拡張子「.pl」でもRubyスクリプトを動かすことはできるがメンテナンス性や可読性を考えると良くない。
サービスを他の言語で作り直そうとするとURIが変わってしまう。
②URIに実装依存のパス名を利用しない
http://example.jp/Login.do?action=showPage
Webアプリケーションフレームワークとして古いStrutsを採用するとこのようなURIになる。
Struts特有の拡張子である「.do」も問題だし、もっと問題なのはshowPageというメソッド名がURIに入っていること。これだとシステムをリファクタリングしてメソッド名を変更するとURIが変更される。
③URIにセッションIDを含めない
http://example.jp/home.jsp?jssessionid=12345678
これはセッションIDをURIに埋め込むんだ例であるがセッションIDはログインのたびに変わるのでログイン毎にURIが変わってしまうので良くない。
④URIはそのリソースを表現する名詞である。
URIはリソースの名前で名詞であるべき。しかし守られてないこともある。
以下は初期のRuby on Railsの例
http://example.jp/sample/show/123
これはIDが123である人物のリソースのURIである。パスの部分はsampleアプリケーションのPeopleコントローラーのshowメソッドに由来している。
一見すると良いURIに見えるが、「show」に問題がある。
HTTPではリソースに対して特定のHTTPメソッドだけを適用する。つまり、URIとHTTPメソッドの関係は名詞と動詞の関係にあるためURIは全体として名詞になるように設計するべき。
よってRuby on Rails2.0以降では次のようにメソッド名をURIに含めなくなった。
http://example.jp/sample/people/123
URIのユーザビリティ
シンプルなURIにはユーザビリティが高まるという利点がある。
複雑なURIの例
http://example.jp/servlet/LoginServlet
シンプルなURIの例
http://example.jp/login
文字数が少ないほうが当然見やすく覚えやすい。
「servlet」は実装依存の文字列であるがゆえに、開発者ではない人には馴染みのない単語なので使うべきではない。
マトリクスURI
URIは「/」を使うことで階層を表現することができる。
例えば1日の日記のURIは、
http://example.jp/diary/2010/05/01
と階層構造で表現できる。
しかし、すべての情報が階層で表現できるとは限らない。
複数の次元を持つ情報、例えば地図などは階層で表現できない。
緯度と経度の他にも表示スケールや地図か航空写真かのフラグなど、複数のパラメータ軸を持つ。
このような複数パラメータを組み合わせる必要がある場合はマトリクスパラメータを使う。
マトリクスパラメータでは複数の軸のパラメータを「;」で区切る。
http://example.jp/map/lat=35.705471;lng=139.751898
URIの不透明性
シンプルなURIは可読性が高いため、ユーザがURIの構造を推測しやすくなる。
しかしクライアント側はあくまでサーバが提供するURIをそのまま扱うだけであるべきでURIの内部構造を想像して操作したり、クライアント側でURIを構築したりするべきではない。
URIをクライアント側で組み立てたり、拡張子からリソースの内容を推測したりできないことを「URIはクライアントにとって不透明である。」と言う。
クライアントを作る際は、URIが不透明であることを心がけなければいけない。
第6章 HTTPの基本
HTTPはRFC2616で規定されたプロコトルで、現在バージョンは1.1。
HTTPはTCP/IPをベースにしていてTCPとIPはインターネットの基盤を構成する重要なネットワークプロコトル。
階層型プロコトル
インターネットのネットワークプロトコルは階層型になっている。層ごとに抽象化して実装すれば物理的なケーブルがメタルなのか光なのかといった下位層の具体的なことに左右されることなく上位層を実装できる。
ネットワークインターフェース層
一番下のネットワークインターフェース層は、物理的なケーブルやネットワークアダプタに相当する部分。
インターネット層
ネットワークインターフェース層の上にはインターネット層がある。
この層が担当するのはネットワークで実際にやり取りする部分でIPが相当する。
IPではデータの基本的な通信単位をパケットと呼び、指定したIPアドレスを送り先としてパケット単位でデータをやりとりして通信する。
IPではデータを送り出すことだけを保証していて、そのデータが送り先まで届くかどうかは保証していない。
トランスポート層
インターネット層の上にはトランスポート層がある。IPが保証しないデータの転送を保証するのがトランスポート層の役割でTCPが相当する。
TCPでは接続先の相手に対してコネクションを張り、このコネクションを使って接続先のデータの抜け漏れをチェックしデータの到達を保証する。
TCPで接続したコネクションで転送するデータがどのアプリケーションに渡るかを決定するのがポート番号で、ポート番号には1〜65535まである。
サーバ側のよく使用されるポート番号にはデフォルトの番号が割り当てられていて、HTTPはデフォルトで80番ポートを使用する。
アプリケーション層
トランスポート層の上にはアプリケーション層があり、具体的なインターネットアプリケーション(メールやDNS、HTTPなど)を実現する層。
TCPでプログラムをつくるときはソケットと呼ばれるライブラリを使うのが一般的である。
ソケットはネットワークでのデータやり取り抽象化したAPIで接続、送信、受信、切断などの基本的な機能備えていて、HTTPサーバやブラウザはソケットを用いて実装する。
ほとんどのプログラミング言語にはHTTPを実装したライブラリが標準でついているのでソケットを使ってHTTPを独自に実装することはほとんどないが、WebサービスやWeb APIを開発するにあたってはフレームワークの細かな挙動や設定、パラメータなどがプロトコルレベルでどのように動作するか把握しておく必要がある。
最後に以上の階層型プロコトルをわかりやすく紹介している記事があったので紹介。
クライアントとサーバ
Webはアーキテクチャスタイルにクライアント/サーバを採用している。つまり、クライアントが情報を提供するサーバに接続し、各種のリクエストを出してレスポンスを受け取る。
クライアントで行われること
- リクエストメッセージの構築
- リクエストメッセージの送信
- レスポンスが返るまで待機
- レスポンスメッセージの受信
- レスポンスメッセージの解析
- クライアントの目的を達成するために必要な処理
サーバから帰ってきたレスポンスを解析した結果、再度リクエストが必要になる場合もある。画像やスタイルシートへリンクがいくつもある場合はHTMLをレンダリングするのに必要なリクエスト回数が多くなる。
クライアントは最後に自身の目的を達成するための処理をする。ブラウザであればHTMLをレンダリングしてウィンドウに表示する処理をして、検索エンジン用にデータを集めるロボットプログラムであればHTMLの解析結果をデータベースに格納する処理をする。
サーバで行われること
- リクエストの待機
- リクエストメッセージの受信
- リクエストメッセージの解析
- 適切なアプリケーションプログラムへの処理の委託
- アプリケーションプログラムから結果を取得
- レスポンスメッセージの構築
- レスポンスメッセージの送信
一方、サーバでは以上のことが行われている。
HTTPのステートレス性
HTTPはステートレスなプロコトルとして設計されている。
ステートレスとはサーバがクライアントのアプリケーション状態を保持しない制約のこと。
これだけではわかりにくいのでハンバーガーショップを例としてステートフルとステートレスのやりとりをすると以下のようになる。
ステートフルなやりとり
客「こんにちは。」
店「いらっしゃいませ。〇〇バーガーへようこそ。」
客「ハンバーガーセットをお願いします。」
店「サイドメニューは何になさいますか?」
客「ポテトで。」
店「ドリンクは何になさいますか?」
客「コーラで。」
店「50円追加でドリンクをLサイズにできますがいかかですか?」
客「Mでいいです。」
店「以上でよろしいでしょうか?」
客「はい。」
店「かしこまりました。」
ステートレスなやりとり
客「こんにちは。」
店「いらっしゃいませ。〇〇バーガーへようこそ。」
客「ハンバーガーセットをお願いします。」
店「サイドメニューは何になさいますか?」
客「ハンバーガーセットをポテトでお願いします。」
店「ドリンクは何になさいますか?」
客「ハンバーガーセットをポテトとコーラでお願いします。」
店「50円追加でドリンクをLサイズにできますがいかかですか?」
客「ハンバーガーセットをポテトとコーラ(M)でお願いします。」
店「以上でよろしいでしょうか?」
客「ハンバーガーセットをポテトとコーラ(M)でお願いします。以上。」
店「かしこまりました。」
このようにステートフルなやりとりではサーバがクライアントのそれまでの注文を覚えているのに対してステートレスなやりとりではクライアントは毎回すべての注文を繰り返している。
ステートフルの欠点
サーバがクライアントのアプリケーション状態を覚えることは、クライアントの数が増えるにしたがって難しくなる。
1つのサーバが同時に相手をできるクライアントの数には上限があり、大抵の場合は複数のサーバを使用することになる。
よって複数のサーバ間でクライアントのアプリケーション状態を同期する必要が出てくるため、台数が増えるほどデータを同期するオーバーヘッドが無視できなくなりクライアントの数が増えた場合にスケールアウトさせにくくなる。
ステートレスの利点
このステートフルの問題を解決できるのがステートレスなアーキテクチャ。
ステートレスではクライアントがリクエストメッセージに必要な情報をすべて含んでいる。
このようなリクエストの処理に必要な情報が全て含まれているメッセージのことを「自己記述的なメッセージ」という。
ステートレスなアーキテクチャではサーバがクライアントのアプリケーション状態を覚える代わりにクライアントが自らのアプリケーション状態を覚え、全てのリクエストを自己記述的メッセージで送信する。
ステートレスの欠点
ステートレスなアーキテクチャはスケーラビリティの面で大きな威力を発揮するが欠点もある。
パフォーマンスの低下
サーバをステートレスにするにはクライアントは毎回必要な情報をすべて送信しないといけない。なので送信するデータ量が多くなるし、認証などサーバに負荷がかかる処理を繰り返してしまう。
一般的にDBへのアクセスは重い処理なので認証するたびにDBへのアクセスが必要なのはパフォーマンスが落ちる。
通信エラーへの対処
ステートレスでは通信エラー発生時の対処も問題になる。
ステートフルなやりとりの場合
客「ハンバーガーを1個ください。以上。」
店員「かしこ(雑音で聞こえない)。」
客「(念の為もう一度)ハンバーガーを1個ください。以上。」
店員「お客様はすでに1個注文されてますが、よろしいですか?」
ステートレスなやりとりの場合
客「ハンバーガーを1個ください。以上。」
店員「かしこ(雑音で聞こえない)。」
客「(念の為もう一度)ハンバーガーを1個ください。以上。」
店員「かしこまりました。」
このようにステートレスなやりとりであるとハンバーガーを2個注文してしまう。
ステートレスではネットワークトラブルが起きたときにリクエストの処理がされたかどうかがわからない問題がある。
第7章 HTTPメソッド
HTTPメソッドには、クライアントが行いたい処理をサーバに伝えるという重要な任務があり、以下の8つのメソッドで処理を行う。
- GET: リソースの取得
- POST: 子リソースの作成、リソースへのデータの追加、その他の処理
- PUT: リソースの更新、リソースの作成
- DELETE: リソースの削除
- HEAD: リソースのヘッダの取得
- OPTIONS: リソースがエクスポートしているメソッドの取得
- TRACE: 自分宛てにリクエストメッセージを返す(ループバック)試験
- CONNECT: プロキシ動作のトンネル接続への変更
HTTPメソッドとCRUD
HTTPメソッドのうちGET、POST、PUT、DELETEは4つで「CRUD(クラッド)」という性質を満たす代表的なメソッドである。
Create、Read、Update、Deleteの頭文字でCRUD。
以下CRUDとHTTPメソッドの対応
- Create: POST/PUT
- Read: GET
- Update: PUT
- Delete: DELETE
GET リソースの取得
GETは指定したURIの情報を取得する。Webページの取得、画像の取得、映像の取得、フィードの取得など数多くのGETが発行される。
GET /list HTTP/1.1
Host: example.jp
このリクエストはhttp://example.jp/list
に対するGET
HTTP/1.1 200 OK
Content-Type: application/json
[
{"uri": "http://example.jp/list/item1"},
{"uri": "http://example.jp/list/item2"},
{"uri": "http://example.jp/list/item3"},
{"uri": "http://example.jp/list/item4"}
]
サーバは指定されたURIに対応するデータをレスポンスとして返している。
POST リソースの作成、追加
POSTはGETの次に利用頻度が高いメソッドで以下の3つの役割がある。
1. 子リソースの作成
2. リソースへのデータの追加
3. 他のメソッドでは対応できない処理
①子リソースの作成
POSTの代表的な機能はあるリソースに対する子リソースの作成で、ブログ記事の投稿などの操作で使われる。
POST /list HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8
こんにちは!
このリクエストではhttp://example.jp/list
に対して新しい子リソースを作成するうようにPOSTで指示していてPOSTのボディには新しく作成するリソースの内容を入れてある。
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Location: http://example.jp/list/item5
こんにちは!
レスポンスでは201 Createdというステータスコードが返ってきてこのステータスコードは新しいリソースを生成したことを示す。
Locationヘッダには新しいリソースのURIが入っていて今回の場合は/listの下に、新しく/list/item5というリソース(子リソース)を生成している。
②リソースへのデータの追加
子リソースの作成ほど一般的ではないが、POSTの代表的な機能に既存リソースへのデータ追加がある。
POST /log HTTP/1.1
Host: example.jp
このリソースのURIはhttp://example.jp/log
で、CSV形式のログを表現する。
HTTP/1.1 200 OK
Content-Type: text/csv; charset=utf-8
2010-10-10T10:10:00Z, GET/list, 200
2010-10-10T10:11:00Z, POST/list, 201
2010-10-10T10:20:00Z, GET/list, 200
このリソースに新しいログを追加するには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が返ってくる。
リクエストが新規リソースではなくデータの追加の場合(URIが増えない場合)こうなる。
③ほかのメソッドでは対応できない処理
POSTの3つ目の機能は、他のメソッドでは対応できない処理を実行する。
例えば検索結果を表現するURIの場合はhttp://example.jp/serch?q={キーワード}
になる。
通常はこのURIをGETして検索結果を表示するが、仮にキーワードが非常に長かった場合はGETできない。
理由はURIには実装上2000文字などの上限が存在するため、2000文字以上のキーワードは入れられないから。
このような場合はPOSTを用いることになる。
POST /log HTTP/1.1
Content-Type: application/x-www-form-urlencoded
q=very+long+keyword+foo+bar
GETではURIにキーワードが含まれるが、POSTではリクエストボディに入れられる。
PUT リソースの更新、作成
PUTは以下の2つの機能を持つ
- リソースの更新
- リソースの作成
①リソースの更新
例えば「こんにちは!」と書かれたリソースを「こんばんは!」に変更するPUTは以下のようになる。
PUT /log HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8
こんばんは!
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
こんばんは!
PUTへのレスポンスはボディに結果を入れても良いし、ボディには何も入れずにレスポンスがボディを持たないことを示す204 No Contentを返しても良い。
②リソースの作成
PUTはリソースの作成もできる。
例えば、http://example.jp/newitem
がまだ存在しないとして以下のPUTを送信する。
PUT /newitem HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8
新しいリソース/newitemの内容
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
新しいリソース/newitemの内容
この場合存在しないURIへのリクエストのためサーバはリソースを新しく作成して、201 Createdを返す。
/newitemがすでに存在する場合はリソースの更新処理をする。
POSTとPUTの使い分けについて
POSTでもPUTでもリソースの作成はできるのでどうやって使い分けるか?
これについて正解は存在しないが、設計上の指針では以下の事例がある。
- POSTでリソースを作成するときは、クライアントはリソースのURIを指定できず、URIの決定権はサーバにある。
- PUTでリソースを作成するときは、リソースのURIはクライアント側が決定する。
twitterのように投稿のURIがサーバ側で自動的に決まるようなものはPOSTを用いる。
逆にWikiのようにクライアントが決めたタイトルがURIになるようなものはPUTを用いる。
DELETE リソースの削除
DERETEはリソースを削除するメソッド。
DELETE /list/item2 HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
基本的にDELETEのレスポンスをボディを持たず、そのためのレスポンスのステータスコードにはボディがないという意味の204 No Contentが使われることもある。
HEAD リソースのヘッダの取得
HEADはGETに似ているが、HEADはリソースのヘッダだけを取得するメソッド。
レスポンスにボディが含まれないのでリソースの大きさを調べたり、リソースと更新日時を取得したりできる。
OPTIONS リソースがサポートしているメソッドの取得
OPTIONSはそのリソースがサポートしているメソッドの一覧を返す。
OPTIONS /list HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
Allow: GET, HEAD, POST
POSTでPUT/DELETEを代用する方法
HTTPメソッドでもっともよく使用されているメソッドはGETとPOSTでこれはHTMLのフォームで使用できるメソッドがGETとPOSTだけという制限のため。
しかしAjaxの発展でこの制限は解消されつつある。
Ajaxで用いるXMLHttpRequestというモジュールを利用すると任意のメソッドを発行可能。
しかし携帯電話向けのブラウザなどXMLHttpRequestがサポートされていない場合はGETとPOSTのみでPUTやDELETEを使わないといけない。主な方法は以下の2つ
その1. _methodパラメータを使う
フォームの隠しパラメータに_methodというパラメータを用意し、そこに本来送りたかったメソッドの名前を入れる。
<form method="POST" action="/list/item1">
<input type="hidden" id="_method" name="_method" value="PUT"/>
</form>
_methodパラメータは主にRuby on Railsで使用されている。
その2. X-HTTP-Method-Override
_methodパラメータはフォームを送信する場合は有効であったが、POSTの内容がXMLなどの場合利用できない。
このような場合に利用できるのがX-HTTP-Method-Overrideヘッダで、GoogleのGDataなどに採用されている。
POST/ /list/item1 HTTP/1.1
Host: example.jp
Content-Type: application/xml; charset=utf-8
X-HTTP-Method-Override: PUT
<body>...</body>
Webアプリケーションフレームワークのサーバ側の実装は、X-HTTP-Method-Overrideヘッダを見て、このリクエストをPUTとして扱う。
べき等性と安全性
HTTPメソッドはその性質によって以下のように分類される。
- GET, HEAD べき等かつ安全
- PUT, DELETE べき等だが安全でない
- POST べき等でも安全でもない
べき等とは「ある操作を何回行っても結果が同じこと」を意味する。
安全とは「操作対象のリソースを変化させないこと」を意味する。
メソッドの誤用
WebサービスやWeb APIの設計を誤ると、メソッドが安全でなくなったり、べき等でなくなったりすることがある。
GETが安全でなくなるケース
GET /resources/1/delete HTTP/1.1
Host: example.jp
この例では/resources/1を削除するために/resources/1/deleteをGETしていてGETの目的を完全に無視している。これは誤った使い方。
POSTの誤用
POSTは万能なメソッドなので誤用につながりやすい。
GET、PUT、DELETEでもできることをPOSTで実装してしまうと、GET、PUT、DELETEのもつべき等性や安全性が利用できなくなるという問題がある。
PUTがべき等ではなくなる例
PUTがべき等でなくなる例としてトマトの価格を表現するリソースhttp://example.jp/tomato
があるとする。
GET /tomato HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
100
現在トマトは100円。
PUT /tomato HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8
+50
PUTで50円追加。
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
150
50円追加されてトマトの値段は150円になる。
もう一度同じリクエストを送ると…
PUT /tomato HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8
+50
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
200
トマトの値段が200円になってしまい、PUTがべき等ではなくなっている。
この場合価格を+50で表現するのではなく150で表現すべき。
DELETEがべき等ではなくなる例
ソフトウェアの最新バージョンを表現するhttp://example.jp/latest
というエイリアスリソースを例に考える。
このリソースがDELETEを受け付けるとどうなるか?
http://example.jp/latest
というエイリアスリソースそのものを削除するようにWebサービスやWeb APIを実装した場合は、このリクエストはべき等である。
DELETEを何度発行しても、/latestが削除されているという結果は変わらない。
DELETE /latest HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
DELETE /latest HTTP/1.1
Host: example.jp
HTTP/1.1 404 Not Found
Content-Type:text/plain; charset=utf-8
http://example.jp/latestは見つかりませんでした。
しかし、もし/latestというURIが意味的に指し示す実際の最新バージョンのリソース(/1.2など)を消した場合はべき等ではなくなる。
DELETE /latest HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
Content-Type:text/plain; charset=utf-8
http://example.jp/1.2を削除しました。
DELETE /latest HTTP/1.1
Host: example.jp
HTTP/1.1 200 OK
Content-Type:text/plain; charset=utf-8
http://example.jp/1.1を削除しました。
最初のDELETEでバージョン1.2が削除され、次のDELETEで1.1が削除されてしまっている。
8章 ステータスコード
ステータスコードは3桁の数字であり、先頭の数字によって以下の5つに分類される。
①1xx:処理中
処理が継続していることを示す。クライアントはそのまま処理を継続するか、サーバの指示に従ってプロコトルをアップデートして再送信する。
②2xx:成功
リクエストが成功したことを示す。
③3xx:リダイレクト
ほかのリソースへのリダイレクトを示す。クライアントはこのステータスコードを受け取ったときにレスポンスメッセージのLocationヘッダを見て新しいリソースへ接続する。
④4xx:クライアントエラー
クライアントエラーを示す。原因はクライアントにリクエストにあり、エラーを解消しない限り正常な結果が得られないので、同じリクエストをそのまま再送信することはできない。
⑤5xx:サーバエラー
サーバーエラーを示す。原因はサーバ側にある。サーバ側の原因が解決すれば、同一のリクエストを再送信して正常な結果が得られる可能性がある。
よく使われるステータスコード
200OK リクエスト成功
200 OKはリクエストが成功したことを示す。
201 Created リソースの作成成功
201 Createdはリソースを新たに作成したことを示す。POSTとPUTのレスポンスとして返る。
301 Moved Permanently リソースの恒久的な移動
301 Moved Permanentlyは、リクエストで指定したリソースが新しいURIに移動したことを示す。
古いURIを保ちつつ、新しいURIに移行する際にこのステータスコードを用いる。
303 See Other 別URIの参照
303 See Otherはリクエストに対する処理結果が別のURIで取得できることを示す。典型的にはブラウザからPOSTでリソースを操作した結果をGETで取得するときに使う。
301や303のような別のURIにクライアントが自動的に再接続する処理を「リダイレクト」と呼ぶ。
400 Bad Request リクエストの間違い
400 Bad Requestはリクエストの構文やパラメータが間違っていたことを示す。
例えば、パスワードをPUTで変更しようとしたときに、設定したパスワードが単純すぎてエラーを出す場合など。
ほかに適切なクライアントエラーを示すステータスコードが無い場合にも用いられる。
401 Unauthorized アクセス権不正
401 Unauthorizedは、適切な条件を与えずにリクエストを行ったことを示す。
レスポンスのWWW-Authenticateヘッダで、クライアントに対して認証方法を伝える。
404 Not Found リソースの不在
おそらく一番有名なステータスコード。
404 Not Foundは、指定したリソースが見つからないことを示す。
500 Internal Sever Error サーバ内部エラー
500 Internal Sever Errorは、サーバ側に何らかの異常が生じていて正しいレスポンスが返せないことを示す。
他に適切なサーバエラーを示すステータスコードがない場合にも用いられる。
503 Service Unavailable サービス停止
503 Service Unavailableは、サーバがメンテナンスなどで一時的にアクセスできないことを示す。
レスポンスのRetry-Afterヘッダでサービス再開時期がおよそ何秒後かを通知できる。
ステータスコードとエラー処理
HTTP使用規定で4xxと5xxはともにエラーを表現するが、どんなエラーメッセージをボディに入れるかは規定されていない。
人間用のWebサービスならエラーメッセージはエラーメッセージをボディにHTMLで書けばいいが、Web APIの場合はクライアントが解釈できる形式でエラーメッセージを返す必要がある。
ステータスコードの誤用
ステータスコードを正しく使うことは最低限のマナー。
404 Not Foundで返すべき情報を200 OKで返すと、クローラーに正式なリソースであると勘違いされインデックス登録されてしまう恐れがある。
第9章 HTTPヘッダ
ヘッダはボディに付加的な情報、メタデータを表現できる。
クライアントはヘッダを見てメッセージに対する挙動を決定する。
以下はHTTP1.1で定義されたよく使用されるヘッダ。
日時
DateやExpiresが相当し、値に日時を持つ。
Dateヘッダ
Date: Tue, 06 Jul 2010 03:21:05 GMT
HTTPでは日時はすべてGMT(グリニッジ標準時)で記述することになっていて、これはグリニッジ標準時の2010年7月6日3時21分5秒を表現している。
MIMEメディアタイプ
リソースの表現の種類を指定する。
Content-type メディアタイプを指定する。
Content−Typeヘッダは、そのメッセージのボディの内容がどのような種類なのかをメディアタイプで示す。
XHTMLを表すメディアタイプなら
Content-Type: application/xhtml+xml; charset=utf-8
と指定し、application/xhtml+xmlがメディアタイプ。
「/」の左側が「タイプ」で、右側が「サブタイプ」と言う。
タイプには以下のようなものがある。
- text 人が読んで直接理解できるテキスト
- image 画像データ
- audio 音声データ
- video 映像データ
- application そのほかのデータ
- multipart 複数のデータからなる複合データ
- message 電子メールメッセージ
- model 複数次元で構成するモデルデータ
- example 例示用
タイプは増やすことができないのに対して、サブタイプは比較的自由に増やせる。IANAのWebサイトからフォームで登録したり、「x-」を接頭辞にすることで独自のサブタイプも作れる。
先程の例ではサブタイプに「+xml」という接尾辞がついているが、これはXMLのメディアタイプを規定したRFC3023が定義していて、XHTMLやSVGのようなXMLのメディアタイプには必ず「+xml」をつけて、それがXMLと判断できるようになっている。
charsetパラメータ 文字エンコーディングを指定する。
メディアタイプはcharsetパラメータを持つことができ、charset=utf-8
ならUTF-8とエンコードしていることを示す。
charsetは省略が可能であるが、タイプがtextの場合は、きちんと指定しないと文字化けを引き起こす可能性がある。
これはHTTPでは、textタイプのデフォルトの文字エンコーディングをISO 8859-1と定義しているためである。
言語タグ
リソース表現の自然言語を指定するヘッダも存在しそれがContent-Languageヘッダである。
このヘッダの値を言語タグと呼ぶ。
例:Content-Language: ja-JP
言語タグの「-」の左側にはISO 639が定義する言語コードが入り、日本語ならば「ja」、英語ならば「en」が入る。
一方右側には、ISO 3166が定義する地域コードが入る。例えば日本なら「JP」、アメリカなら「US」、イギリスなら「GB」となる。
コンテントネゴシエーション
メディアタイプや文字コーディング、言語タグはサーバが一方的に決定するだけでなく、クライアントと交渉して決めることもできる。
この手法をコンテントネゴシエーションと呼び以下のヘッダを利用する。
- Acceptヘッダ 処理できるメディアタイプを伝えるヘッダ
- Accept-Charset 処理できる文字エンコーディングを伝えるヘッダ
- Accept-Language 処理できる言語を伝えるヘッダ
Content-Length
メッセージがボディを持っているときにContent-Lengthヘッダを利用することで、サイズを10進数のバイトで示すことができる。
あらかじめサイズのわかっているリソースを転送するときはこのヘッダを利用すると良い。
例:Content-Length: 5538
チャンク転送
動的に画像を生成するようなWebサービスなど最終的なファイルサイズがわからない場合はTransfer-Encodingヘッダを利用する。
例:Transfer-Encoding: chunked
このヘッダにchunkedを指定すると最終的なサイズが不明なボディを少しづつ転送できる。
HTTPの認証
HTTP1.1が規定している認証方式にはBasic認証とDigest認証がある。
Basic認証
ユーザ名とパスワードによる認証方式。
ユーザ名とパスワードはBase64エンコードされるが、これは簡単にデコード可能なのでセキュリティ強度的には低い。
Digest認証
Basic認証よりもセキュアな認証方式で複雑な流れで認証が行われる。
ハッシュ関数を使用してハッシュ値(ダイジェスト)を使う。
パスワードを盗まれる危険がなく、パスワードをサーバに預けなくても良いが、メッセージ自体は平文でネットワーク上にながれるので暗号化したいならHTTPSを利用するべき。
ApacheなどのWebサーバではDigest認証がオプション扱いのためホスティングサービスではサポートしていない可能性がある。
キャッシュ
キャッシュはサーバから取得したリソースをローカルストレージに蓄積して再利用する手法。
リソースがキャッシュ可能かどうかやキャッシュの有効期限はサーバが指定するPragma、Expire、Cache-Controlヘッダで判断できる。
- Pragma キャッシュを抑制する(no-cache)
- Expire キャッシュの有効期限を示す
- Cache-Control 詳細なキャッシュ方法の指定
その他のHTTPヘッダ
- Content-Disposition ファイル名を指定する。
- Slug ファイル名のヒントを指定する。
第10章 HTML
メディアのタイプには「text/html」と「application/xhtml+xml」の2種類がある。
前者はSGMLベースのHTMLで後者はXMLベースのXHTML。
「.html」または「.htm」という拡張子を使用。現在は「.html」が主流。
XMLについて
XML文書は要素を入れ子にして表現する。
名前空間
複数のXMLフォーマットを組み合わせるときに名前の衝突を防ぐために「名前空間」を使う。
名前空間宣言 xmlns:接頭辞="名前空間"
接頭辞を省略した場合は、接頭辞がないデフォルトの名前空間を意味する。
HTMLの構成要素
ヘッダ
要素の例
- title
- link
- script
- meta
ボディ
ボディにはブロックレベル要素とインライン要素の2種類の要素が入る。
ブロックレベル要素
文書の段落や見出しなどある程度大きなかたまりを表現
インライン要素
ブロックレベル要素の中に入る要素で強調、改行、画像埋め込みなどを表現する。
属性
HTMLのすべての要素はid属性とclass属性を持つことができる。
id属性
文書内で一意であり、複数の要素に同じ名前のid属性をつけることができない。
class属性
要素が属するクラスを表現し、複数の要素に同じ名前のclass属性をつけることができる。
リンク
<a>要素
<a>要素は他のWebページにリンクするために使用する。
<link>要素
<link>要素はHTMLのヘッダ内でWebページ同士の関係を指定するために使用する。
オブジェクトの埋め込み
画像や映像のようなオブジェクトを埋め込む場合は画像の場合は<img>要素を使用し、それ以外のオブジェクトでは<object>要素を使用する。
フォーム
HTMLのフォームではリンク先のURIに対してGETとPOSTが発行できる。
GETの場合はターゲットURIとフォームへの入力値からリンク先のURIを生成する。
POSTの場合はターゲットURIに対してPOSTを発行する。
リンクの関係
リンクを見るのがプログラムの場合にそれぞれのリンクがどのような意味かを機械的に判断させるための機構がある。
rel属性
<a>要素と<link>要素はそれぞれrel属性を持てる。
rel属性の値にはリンク元のリソースとリンク先のリソースがどのような関係にあるかを記述する。
microformats
現在のWebはさまざまなリソースをHTMLで表現しているため、リソースのリンク関係を表現するにはHTMLが定義しているリンク関係だけでは足りない。
そのためHTMLのリンク関係の拡張がmicroformatsなどで行われている。
第11章 microformats
HTMLの中でさらに意味のあるデータを表現するための技術がmicroformats。
これを用いることでリンクの細かい意味やイベント情報などを表現できる。
セマンティックWebを実現するとされている。
セマンティクス(意味論)とは
Webにおけるセマンティクスとはリソースが持つ意味を確定させるための理論。
人間が読んで理解するWebページの意味をプログラムからも解釈し処理できるように形式的に意味を記述するための技術がセマンティックWeb。
RDFについて
プログラムで処理可能な情報を記述するための仕様として1990年代後半に登場したのがRDF。
リソースの意味を厳密に記述できる汎用的なフレームワークである。
RDFの問題点
- 記述が複雑になりがち
- 統一的な記述がしにくい
- 対象データとは独立したメタデータが必要
こういった理由でほとんど普及しなかった。
RDFの問題点を解消したmicroformats
microformatsはHTMLに直接メタデータを埋め込むことができ、必要最低限の情報を追加するだけで良い。
microformatsはmicroformats.orgにてさまざまなメタデータ記述の仕様を策定されている。
microformatsの分類
microformatsは大きく2つに分類できる。
- elemental microformats:リンク関係(rel属性)を使ってメタデータを表現するフォーマット
- rel-licenceやrel-nofollowなど
- compound microformats:class属性を使って階層構造のあるメタデータを表現するフォーマット
- hCalendarやhAtomなど
microformatsの問題点
microformatsではclass属性やrel属性の値だけでメタデータを特定するため、もし同じ値のclass属性やrel属性を持ったWebページがあるとプログラムが誤判定を起こしたり、同じ属性値をもった別のmicroformatsが作れなくなったりする。
RDFaでこの問題は解決できるが、文書が複雑化してしまうという欠点がある。
RDFaはmicroformatsよりは厳密にメタデータを定義したいという限られた用途で必要になる。
第12章 Atom
Atomは一般的にはブログなどの更新情報を配信するためのフィードとして知られているが、幅広い用途で応用できる。
正式名称はAtom Syndication FormatでRFC4287が規定するXMLフォーマット。
RSSとの違い
- RSS:主にブログの新着情報を伝えるフィード
- Atom:拡張性が高くブログだけでなく検索エンジンや写真管理などさまざまなサービスのWebAPIとして利用できる。
Atomのリソースモデル
Atomの構成要素はメンバリソースとコレクションリソースの2つに分かれる。
・メンバリソース
Atomにおける最小のリソース単位。
ブログなら1つ1つの記事がメンバリソースとなる。
メンバリソースはさらにエントリリソースとメディアリソースに分かれ、エントリリソースはXMLで表現できる。
・コレクションリソース
複数のメンバリソースを含むリソースで階層化できない。
<feed>要素で表現され、この表現はフィードと呼ばれる。
Atomのメディアタイプ
Atomのメディアタイプは「application/atom+xml」で、エントリやフィードで明示したいときはtypeパラメータで「entry」「feed」を指定する。
Atomの最小単位のエントリについて
メンバリソースの1つであるエントリリソースは<entry>要素で表現される。
この<entry>の中にメタデータを入れる。
<id>、<title>、<author>、<updated>は必須のメタデータなので必ず使うこと。
これ以外にも様々な要素が使える(category、linkなど)。
エントリの集合であるフィード
<feed>要素の中に複数の<entry>要素が入り、エントリと共通のメタデータを持つ(必須のメタデータも同じ)。
また、<subtitle>、<generator>のようなフィード独自のメタデータもある。
Atomの拡張
Atomは拡張性が高いため、ブログ以外の様々なシステムで応用できる。
例えばOpenSearchというAtomとは別に標準化された仕様を利用すると、検索結果を表現することができる。
もしOpenSearchを実装していないフィードリーダの場合は、OpenSearchで書かれた記述は無視され通常のAtomフィードとして扱われる。
その他の拡張
- Atom Threading Extensions--スレッドを表現する
- Atom License Extension--ライセンス情報を表現する
- Feed Paging and Archiving--フィードを分割する
Feed Paging and Archiving
1万件以上の検索結果をフィードで表現する場合、全ての結果を1つのフィードで表現することは不可能。
そこでフィードの分割を行い、1つのコレクションが複数のフィードに分割していることをFeed Paging and Archivingで表現する。
Feed Paging and Archivingではフィードを以下の3種類に分類する。
- 完全フィード:全てのエントリを1つの文書に含んでいるフィード
- ページ化フィード:エントリを複数の一時的な文書に分割しているフィード
- アーカイブ済みフィード:エントリを複数の恒久的な文書に分割しているフィード
①完全フィード
その文書単体でコレクションに含まれる全てのメンバリソースを含んでいるフィードのことを完全フィードと言う。
フィードが複数に分割されていないことを示すのが目的で完全フィードだと宣言するには<feed>要素の直下に<fh:complete>要素を置く。
②ページ化フィード
膨大なエントリを含むフィードを、いくつかのページに分割するのがページ化フィード。
複数ページ間の関係を表すリンクは以下の通り。
- first:最初のページへのリンク
- last:最後のページへのリンク
- previous:前のページへのリンク
- next:次のページへのリンク
- current:現在のページへのリンク
③アーカイブ済みフィード
ブログのトップページの最新エントリのフィードがページ化フィードであるのに対して、月別のアーカイブのようなフィードはアーカイブ済みフィードである。
アーカイブ済みフィードの特徴
- フィードに含まれるエントリの数が比較的固定されている。
- フィードごとに含まれるエントリの個数にばらつきがある。
13章 Atom Publishing Protocol
Atom Publishing Protocol(AtomPub)というプロトコルを採用するとブラウザ以外のWebクライアントからブログを投稿したり、システム同士の連携が可能になる。
AtomをベースとしてリソースにCRUDできる。
Atomとは兄弟のような仕様となっている。
- Atom:データフォーマットの規定(フィート、エントリ)、ただのデータフォーマット
- AtomPub:Atomを利用した編集プロコトルの規定&リソースの編集(CRUD)を実現する
AtomPubとREST
AtomPubはRESTスタイルに基づいたプロコトル仕様。
RESTはアーキテクチャスタイルなのでリソース設計やリンク機構の提供は設計者の手に委ねられ、設計者の自由度が確保できるがRESTを正しく理解していないと上手に設計ができない。
AtomPubは基本的なリソースモデルとリンク機構を提供してくれるのでRESTの欠点を解消し、独自に設計する必要がある部分が大幅に削減されている。
AtomPubによるCRUDの特徴
- PUTではリクエスト時に知らない要素・属性含めて全ての情報を再送信することが求められる
- POSTでは、レスポンス情報にクライアントがPOSTしたエントリには入っていない情報がが追加されることがある
AtomPubに向いているWeb API
AtomPubに向いているWeb API
- ブログサービスのAPI
- 検索機能を持つデータベースのAPI
- マルチメディアファイルのリポジトリのAPI
- タグを使ったソーシャルメディアサービスのAPI
AtomPubに向いていないWeb API
- Cometを利用するような、リアルタイム性が重要なAPI
- 映像のストリーム配信のようなHTTP以外のプロコトルを必要とするAPI
- データの階層構造が重要なAPI
- 「タイトル」「作者」「更新日時」など、Atomフォーマットが用意するメタデータが不要なAPI
第14章 JSON
JSONはJavaScript Object Notationの略でJavaScriptの記法でデータを記述できるフォーマット。
XMLと比べてデータの冗長性が低いのでAjax通信におけるデータフォーマットとして活用されている。
メディアタイプは「application/json」で拡張子は.json。
明示的にjsonを取得したいのなら、URIに「.json」をつけるようにリソース設計する。
データ型
JSONには以下の6つのデータ型が用意されている。
- オブジェクト
- 配列
- 文字列
- 数値
- ブーリアン
- null
日時型は存在しないので、日時を表現するときには開発者側で何らかの規定を用意しなければならない。よくあるのはUNIX時間を数値として表現する方法。
123456789
これは2009年2月14日8時31分30秒を意味する。
UNIX時間とは1970年1月1日0時0分0秒からの経過秒数で日時を表す単位。
JSONPによるクロスドメイン通信
Ajaxで用いるXMLHttpRequestというJavaScriptのモジュールは、セキュリティ上の制限からJavaScriptファイルを取得したのと同じサーバとしか通信できない。
不特定多数のドメインに属するサーバにアクセスすることを「クロスドメイン通信」という。
JSONPではこのクロスドメイン通信をつかって別ドメインにあるJSONを呼び出すことができる。
15章 読み取り専用のWebサービスの設計
この章では読み取り専用のWebサービスやWeb APIのリソース設計について学ぶ。
リソース設計の指針
- Webサービスで提供するデータを特定する。
- データをリソースに分ける
- リソースにURIで名前を提供する
- クライアントに提供するリソースの表現を設計する。
- リンクとフォームを利用してリソース同士を結びつける。
- イベントの標準的なコースを検討する。
- エラーについて検討する。
郵便番号検索サービスを提供するWeb APIの設計工程
- リソース設計の最初の工程は、サービスで提供するデータを理解して特定する。
-
データをリソースに分割する。一番大変で難しい工程で、このステップが上手く行けば後の工程はスムーズにいく。
- 郵便番号リソース
- 検索結果リソース
- 地域リソース
- トップレベルリソース
各リソースに対してURIで名前をつけていく。
各リソースの表現形式を設計する。今回はデータフォーマットとして軽量フォーマット表現にJSON、XML表現にXHTMLを使用。郵便番号には著者データや更新日時のようなデータが存在しないためAtomは不適切。XHTMLならブラウザで最低限表示することができて便利。
これまで設計してきたリソース同士をリンクで接続する。
-
イベントの標準的なコースを検討する。このWebサービスの提供者側が想定する標準的な利用コースは次の3つ。
- 郵便番号を検索するコース
- 住所から郵便番号を検索するコース
- 地域リソースの階層をたどりながら郵便番号を選択するコース
-
読み取り専用サービスでもエラーが起こる可能性はあるのでエラーケースを考える。エラー時のステータスコードについて検討しておく。
- 存在していないURIを指定した
- 必須パラメータをしていしていない
- サポートしていないメソッドを利用した
リソース設計はスキルであり、URIの設計方法、表現選択の指針、リンクの設計などは身につけることができる。
16章 書き込み可能なWebサービスの設計
この章では前章の読み取り専用の郵便番号検索サービスに書き込み機能を追加する。
書き込み可能なサービスの設計
- リソースの作成
- リソースの更新
- リソースの削除
- バッチ処理
- トランザクション
- 排他制御
リソースの作成、更新、削除
サービスの作成、更新、削除専用のリソース「ファクトリリソース」を作り、そのリソースに対して作成/更新/削除するデータをJSON形式でリクエストする。
リソースの作成、更新をPUTで行う。作成はPOSTでも良いがURIが郵便番号なので予め決まっているためPUTも魅力的。
削除はDELETEで行う。
また、リソースの更新にはバルクアップデートとパーシャルアップデートがある。
前者が更新しない値だろうとリソース全体を送信するのに対して後者は更新したい部分だけを送信する。
バッチ処理、トランザクション
大量の郵便番号を作成したり、更新したりするときに、リクエストを1回1回送信するとサーバへの接続回数が多くなるのでパフォーマンスに問題が出る可能性がある。
作成、更新したいリソースを一括で送信(バッチ処理)できるようにWebサービスを実装する。
また、バッチ処理では処理の途中でエラーが起こると処理できているものと処理できなかったものが混在してしまい厄介なことになるのでトランザクションが必要になる。
トランザクションは処理の途中にエラーが発生した場合はその処理をなかったことにして元の状態に戻すことを保証する。
実装するときはトランザクション専用のファクトリリソース(トランザクションリソース)を作成して、その中でトランクザクション処理を行う。
排他制御
1つのリソースに対して複数のクライアントが同時に更新処理を行った場合コンフリクトを起こしてしまう。
なので複数のクライアントを相手にするWebサービスには排他制御が必要になってくる。
排他制御には大きく分けて悲観的ロックと楽観的ロックの2つがある。
悲観的ロック
あるユーザがリソースを編集している間、リソースをロックして他のユーザは編集できなくしてしまうのが悲観的ロック。
WebDAVのLOCK/UNLOCKメソッドを使う方法と、独自のロックリソースを使う方法の2つがある。
楽観的ロック
悲観的ロックでは世界中の不特定多数のユーザが同時に編集するような大規模なシステムでは不向き。Wikiのようなシステムだと誰かが文書をロックしたままずっと編集できないなどの問題が発生する。
楽観的ロックでは、通常の編集では文書をロックせずに競合が発生したときだけ対処する。
実装には条件付きPUTと条件付きDELETEを利用する。
条件付きPUTや条件付きDELETEを発行した際にリソースが変更されていた場合は、412 Precondition Failedが返る。このステータスコードには、クライアントが指定した条件を満たせなかったという意味がある。
412 Precondition Failedが発生した場合の処理は次の3つの方法がある。
- 競合を起こしたユーザに確認した上で、更新または削除する。
- 競合を起こしたデータを、競合リソースとして別リソースに保存する(PUTの場合のみ)
- 競合を起こしたユーザに変更点を伝え、マージを促す(PUTの場合のみ)
設計のバランス
設計はバランスを取る作業であり、各種のパラメータを加味して自分が最もバランスがとれていると感じるところに落ち着かせる。
Webサービスを設計するにあたって重要なのは以下の3つ。
- なるべくシンプルに保つ
- 困ったらリソースに戻って考える
- 本当に必要ならPOSTで何でもできる
第17章リソースの設計
リソース設計で一番重要なことはWebサービスとWeb APIを分けて考えないこと。
リソース指向アーキテクチャの落とし穴はリソース指向アーキテクチャで設計しようとしたときに「Webサービスで提供するデータを特定」する方法と「データをリソースにわける」方法がわからない。
関係モデルから導出
関係モデルのER図からリソースの導出をする。
中心となるテーブルからリソースを導出するときにURIに一意のIDを含める方法として、データの主キーを含める方法と主キー以外の一意のキーを含める方法がある。
今回の「郵便番号検索サービス」では郵便番号は一意なので今回は郵便番号を入れたURIを採用した。
リソースが持つデータの特定
続いてリソースが持つデータを検討する。
中心となったテーブルの正規化を崩して全ての属性をリソースに入れる。
関係モデルではデータの重複を省くために通常は正規化を行うがリソースの設計では1つ1つのリソースをそれ自身で全てを表現できるように自己記述的にするために正規化を崩す。
検索結果リソースの導出
関係モデルは検索と強く結びついている。
検索のような機能は、検索行為をモデル化するのではなく、「検索結果」をリソースとして表現する。
検索結果リソースはこのデータベースから何を検索したいのかというユーザの利用シナリオに基づいて導出する。
郵便番号データベースの場合、次の検索条件が考えられる。
- 住所の全部または一部による検索
- 郵便番号の全部または一部による検索
検索キーワードを一括で受け付けるように設計した場合のURI
http://zip.ricollab.jp/search?q={query}
階層化の検討
関係モデルが苦手とするデータ構造に階層構造がある。
関係モデルを見ただけでは階層構造はわかりにくいので別途ドキュメントなどから理解し必要であればリソースの設計に反映する。
トップレベルリソース
ER図からは直接導出できないリソースとしてトップレベルリソースがある。
郵便番号検索サービス」では検索結果リソースを持つWebサイトなら検索フォームが置かれる。
リンクによる結合
リソース間を結合するリンクの設計にはER図の関連が利用できる。
リンクによる結合を行って関係モデルからのリソースの導入を完了させる。
オブジェクト指向モデルからの導出
オブジェクト指向モデルでリソースを導出する場合、クラスの持つメソッドを操作結果リソースへと変換することが重要。
これによってクラスの持つ豊富なメソッドをHTTPの限定されたメソッドに置き換えることができる。
情報アーキテクチャからの導出
よく設計された情報アーキテクチャを持つWebサイトの構造は、ほぼそのままWebサービスに適用できる。
これは情報アーキテクチャとリソース指向アーキテクチャが相互に保管関係にあることによるもの。
リソース指向アーキテクチャは情報を分類しリソースに分けるのが苦手だが、情報アーキテクチャは情報の分類手法なのでリソース指向アーキテクチャが苦手なところを上手く保管できる。