0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

「Webを支える技術」まとめ(第1部〜第3部)

Posted at

Webを支える技術のメモ

第一章 Webとはなにか

Webの用途

  • Webサイト
    • 今みなさんが見ているこの記事や検索サイト、ショッピングサイトなど
  • UI(ユーザーインターフェース)
    • プリンタやテレビなどの各種デバイスの設定画面
    • HTMLによるヘルプの記述
  • プログラム用API
    • XMLやJSONなど、プログラムで解釈・処理しやすいもの

Webを支える技術

Webを支える最も基本的な技術
  • URI(Web上に存在する情報を特定するための識別子)
  • HTML(これらの情報を表現するためのXMLをもとにした文書フォーマット)
  • HTTP(これらの情報を取得したり発注したりするためのプロトコル)

これらの技術に支えられたWebは情報システムとしてハイパーメディアシステム分散システムの2つの側面がある。

ハイパーメディア

ハイパーメディア以前のメディア(書籍や映画)は情報を線形的に順に取得(読んだり視聴したり)する。ハイパーメディアはテキストや画像などの様々な情報をハイパーリンクまたは(単にリンク)でつなぎ、ユーザーはそれらの情報をリンクをもとに非線形的に自由に取得することができる。

分散システム

「分散システム」の対義語として「集中システム」があるが、これは1つの中央コンピュータがすべてを修理する形式である。一方、1つのコンピュータで扱いきれる情報は限られており、より膨大な情報を取り扱えるようにしたシステムが「分散システム」である。その特徴として

  • 複数のコンピュータに存在するデータを一元的に扱う。
  • 1台のコンピュータでは扱いきれない膨大な情報を操作できる。
  • 1台のコンピュータでは難しい機能や性能を実現できる。

が挙げられる。特にWebは分散システムとしては過去最大規模のシステムである。

第二章 Webの歴史

Webの歴史的背景をハイパーメディアと分散システムの2つの側面から説明

Web以前のハイパーメディア

Web以前にもMemexやXanadu、HyperCardなど様々なハイパーメディアが開発された。これらのシステムは複雑さが最大の問題点であり、逆にWebが普及したのは必要最低限のリンク機能だけを搭載したその単純さであると考えられる。

Web以前の分散システム

Web以前の分散システムとしてはRPC (Remote Procedure Call)が挙げられる。
この技術を用いるとリモートのサーバで実行しているプログラムをクライアント側から呼び出せるという特徴がある。この技術は現在でも分散システムの実装に用いられているが、このシステムが現実的に動作するのは通信相手がある程度決まっているイントラネットのみでより大規模な異種分散環境ではスケールしない。

Webのように大規模な分散システムには何が必要なのかはWebの分散システムを見ることで明らかにする。

Webの誕生

  • 1990年にスイスのCERNという研究所で働いていたTim Berners-LeeがWebの提案書を記述。
    同年のクリスマス休暇に最初のバージョンのブラウザとサーバを完成させる。

  • Webの普及を一気に推し進めたのが、1993年に公開されたMosaicというブラウザである。このブラウザはそれまで文字情報しか扱えなかったのに対し、インラインで画像を混在させることができた。MosaicはInternet ExplorerやFirefoxといった現在のブラウザの源流となった。

ハイパーメディアとしてのWeb

Webのハイパーメディアとそれ以前のハイパーメディアとの一番の違いはインターネットを利用していること。?
これにより不特定多数の情報をリンクさせ合うことができ、システムを大規模化しやすくなった。反面、情報を集中的に管理することが難しくなりリンク切れを起こしやすいリスクも存在する。Webのハイパーリンクはシンプルな単方向リンクであり、ユーザにとってもわかりやすく実装も簡単であり、これがWebの普及した理由と言える。

様々なハイパーメディアフォーマット

初期のWebではHTMLが唯一のハイパーメディアフォーマットだったが、HTMLでは対応できない様々な要望が生まれ新しいハイパーメディアフォーマットが誕生。microformatsやRSSが提案されたが最終的にIETFでAtomが標準化される。HTMLやAtomはXMLをもとにしたマークアップ言語でデータを記述するためには冗長過ぎたため単純なデータフォーマットが提案され、デファクトスタンダードとなったのがJSONである。

分散システムとしてのWeb

RPCの弱みとして通信相手が限られてしまうということを挙げたが、Webはクライアントとサーバ側の通信をHTTPというシンプルなプロトコルで固定したことによって多種多様なコンピュータ環境からでもWebサービスにアクセスが可能となった。

Webの標準化

もともとWeb以前のインターネット標準はIETFのRFCとして定められてきた。(HTTP1.1はRFC2616)
しかしWebが急速に普及する中で使用策定が追いつかず各社の実装がばらばらになった結果、相互運用性のかける状態が発生。この問題を解決するため1994年にBurners-LeeがW3Cを設立。HTML、XML、HTTP、URI、CSSなどの標準化作業が行われた。

RESTの誕生と普及

この流れの中でカリフォルニア大学アーバイン校の大学院生だったRoy-FieldingがWebがなぜこんなにも成功し、大規模なシステムが成立したのかをソフトウェアアーキテクチャの観点から分析し「REST」と呼ばれるアーキテクチャスタイルとしてまとめた。(詳しくは3章で記述)またSOAPと呼ばれるをもとにしたアーキテクチャスタイルが提案されたが、2002年に登場したAmazon WebサービスによってRESTの普及に弾みが付き、当時はSOAPを用いた形式も同サービス用いられていたが2004年から始まったWeb2.0の中でWeb APIが提供するリソースをHTTPやURIで簡単に操作できるRESTスタイルのほうが受け入れられるようになった。

第三章 REST

アーキテクチャスタイル

アーキテクチャスタイルは複数のアーキテクチャに共通する性質、様式、作法を指す言葉。
アーキテクチャを決定する際の羅針盤となるもの。
具体例としてはMVCやパイプ&フィルタ、イベントシステムが挙げられる。

アーキテクチャスタイルとしてのREST

RESTはクライアント/サーバというアーキテクチャスタイルにいくつか制約を加えた
アーキテクチャスタイルで、特にネットワークシステムのアーキテクチャスタイルである。
RESTはWeb全体として、そして個別のWebサービスやAPIのアーキテクチャスタイルでもある。

リソース

リソースはRESTにおける重要な概念の1つです。Webにおけるリソース例を下に記す。

  • 東京の天気予報
  • 技術評論社の「Webを支える技術」のページ
  • 東京駅の写真
  • 筆者の最近のブックマーク

これらのリソースを一言で説明すると
「Webに存在する、名前を持ったありとあらゆる情報」となる。
そしてリソースにおける名前は必ず他のものと区別できなければならない。
リソースの名前としてのURIの具体例として

が挙げられる。リソースの特徴をまとめると以下のようになる。

  • リソースとは、Web上の情報
  • 世界中のリソースは、それぞれURIで一意の名前を持つ。
  • URIを用いることで、プログラムはリソースが表現する情報にアクセス可能
  • 1つのリソースは複数のURIを持つことができる。
  • HTML形式やPDFなど複数の表現を持つことができる。
  • 状態があり、時間の経過に従ってリソースの状態が変化すると表現も変化する。

RESTアーキテクチャスタイルの構成

クライアント/サーバに他のアーキテクチャスタイルを追加してRESTを構成していく。
まずRESTを構成するアーキテクチャスタイルを紹介する。

  • クライアント/サーバ
  • ステートレスサーバ
  • キャッシュ
  • 統一インタフェース
  • 階層化システム
  • コードオンデマンド
クライアント/サーバ

HTTPというプロトコルでクライアントとサーバが通信。クライアントはサーバにリクエストを送り、サーバはそれに対してレスポンスを送る。単一のコンピュータですべてを処理するのではなく、ユーザインターフェースとしてのクライアントとデータストレージとしてのサーバに分離して処理できる。複数のサーバを組み合わせれば可用性をあげられる。

ステートレスサーバ

サーバがステートレスであるとはクライアントのアプリケーション状態をサーバが管理しないことを意味する。これによりサーバ側の実装を簡略化できる。
ただし、現実にはステートレスではないWebサービスやWeb APIが多数存在する。その代表はCookieをつかったセッション管理でRESTの視点から見ると間違った拡張である。しかし、Cookieを使ったフォーム認証をやめるわけにもいかないのでステートレスではない特質を理解し必要最低限に利用することが求められる。

キャッシュ

リソースの鮮度に基づいて一度取得したリソースをクライアント側で使い回す方式。
利点はサーバとクライアントの通信を減らすことであるが、古いキャッシュを利用することによって情報の信頼性が下がる可能性があること。

統一インターフェース

URIで指定したリソースに対する操作を統一した限定的なインターフェースで行うことを指す。たとえばHTTP1.1では、メソッドが8個しか用意されていない。
しかし、これにより全体のアーキテクチャがシンプルになり、クライアントとサーバの実装の独立性が向上する。

階層化システム

統一インターフェースの利点の1つとしてシステムが階層化しやすいというメリットがある。

  • 階層化の例
    • クライアント --- ステートレスサーバ
    • クライアント --- プロキシ --- ステートレスサーバ
    • クライアント --- ステートレスサーバ --- インターフェースの異なるレガシーシステム

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

コードオンデマンド

サーバ側のプログラムコードをクライアント側がダウンロードし利用するというアーキテクチャスタイル。JavaScriptやFlash、Javaアプレットなどがこれに該当する。これによりクライアントプログラムで用意している機能をあとから拡張できるようになる。近年のWebではこれらの重要度が増している。
一方で、HTTPのプロトコルを利用した通信をしていないので、アプリケーションプロトコルの可視性が低下するというデメリットがある。

RESTアーキテクチャスタイルに基づいた設計

RESTはアーキテクチャスタイルなので、システムのアーキテクチャは原則これに従わなければならない。しかし、実際のソフトウェアやシステム設計においてはこの理想から妥協しなければならないところも出てくる。また、ほとんどのRESTスタイルを除外しなければならない場合、無理やりRESTスタイルを採用する必要はない。代表例としてサーバを介さない通信であるP2Pが挙げられる。

第四章 URIの仕様

URIの構文

http://blog.example.jp/entries/1
このURIを構成するパーツは次のようになる。

  • URIスキーム(URIが利用するプロトコル)
    • http
  • ホスト名(DNSで名前が解決できるドメイン名かIPアドレス)
    • blog.example.jp
    • インターネット上で必ず一意になる。
  • パス(ホスト名の中でのリソースを一意に指し示すもの)
    • /entries/1

複雑なURI

http://yohei:pass@blog.example.jp:8000/search?q=test&debug=true#n10

  • URIスキーム(略)
    • http
  • ユーザ情報(リソースにアクセスする際に利用するユーザ名とパスワード)
    • yohei:pass
    • 後の「@」は区切り文字
  • ホスト名(略)
    • blog.example.jp
    • 後の「:」は区切り文字
  • ポート番号(ホストにアクセスする際のプロトコルで用いられるTCPのポート番号)
    • 8000
  • パス(略)
    • /search
    • 後の「?」は区切り文字
  • クエリパラメータ(クライアントから動的にURIを生成する際に用いいられる)
    • q=test&debug=true
    • クエリが複数あるときは「&」でつなぐ。
    • 検索サービスに検索キーワードを引き渡すときが動的なURI生成の例
  • URIフラグメント(リソース内部の細かい部分を特定するときに利用)
    • #n10
    • HTML文書の場合id属性の値がn10であることを示す。

絶対URIと相対URI

相対URIは絶対URIからURIスキームとホスト名を除いてパスだけで表現したもの

ベースURI

相対URIはそのままでは起点となるURIがどこなのか分からないため解釈できない。
この起点となるのがベースURIである。(下表参照)

ベースURIhttp://example.jp/foo/barとして

相対URI 絶対URI
hoge/fuga http://example.jp/foo/bar/hoge/fuga
../../hoge http://example.jp/hoge
?q=hoge http://example.jp/foo/bar?q=hoge

このベースURIを指定する方法として以下の方法がある。

  • あるリソースを取得したときに、そのURIをベースURIとして相対URIを取得する方法
  • HTMLやXMLの中で明示的にページURIを指定する方法

URIと文字

URIで使用できる文字
  • アルファベット:A-Za-z
  • 数字:0-9
  • 記号:-.~:@!$&'()
%エンコーディング

許可されている文字を使用する場合には%エンコーディングでその文字をエンコーディングする。
例えば下のサイトは
https://ja.wikipedia.org/wiki/あ
下のようにエンコーディングされてブラウザとサーバの間で転送される。
https://ja.wikipedia.org/wiki/%E3%81%82

この%エンコーディングはどの文字エンコーディングを使用するかで違いが生じる。
しかし、現代的なWebさいとの多くは文字エンコーディングとしてUTF-8を採用しているため、UTF-8を採用するのが無難と言える。

URIの実装上気をつけなければならないのは相対URIの解決%エンコーディングの2つである。

第五章 URIの設計

良いURIやきれいなURIのことを「クールURI」と呼ぶ。
Burnres-Leeは「URIは変わらないべきである。変わらないURIこそが最上のURIである。」という主張をクールURIに込めた。クールURIの設計をこの章でまとめる。

URIを変わりにくくするためには

ポイントは以下の3点

  • URIにプログラミング言語依存の拡張子やパスを利用しない。
    • javaやrb、plなど
    • javaの例:servlet/LoginServlet
      • PHPに変えた途端servletの部分の記述は変更されてしまう。
      • またクラス名「LoginServlet」はRubyやPerlでは先頭の「L」は小文字が文化
  • メソッド名やセッションIDを含めない。
    • リファクタリングでメソッド名を変えるとURIも変更されてしまう。
    • セッションIDはログインのたびに変更されてしまう。
  • URIはそのリソースを表現する名詞である。
    • 「名詞」なのでそのリソースを変更する、取得する等の「動作」はURIに含めるべきでない。

URIのユーザビリティ

クールURIとはシンプルなURIでもあり、実装依存を排除しシンプルなURIにすればURIを変更しにくくなる。これは比較的設計者目線のメリットだが、シンプルなURIは覚えやすく、開発者ではない普通の人にも使いやすくなるというメリットがある。

URIを変更したいとき

変わらないURIこそが、クールなURI。この定義に立ち返ると現在運用しているURIを安易に変更してはならない。しかし、ハードウェアの老朽化やシステム全体の機能変更・追加などでどうしてもシステムを入れ替えなければならないことがある。その場合、リダイレクトを用いて古いURIを新しいURIに転送するとよい。

URI設計のテクニック

コンテントネゴシエーション

グローバル企業の場合、プレスリリースを1つ公開するといっても英語や日本語など様々な言語で記述することが一般的である。このときHTTPにはコンテントネゴシエーションという便利な機能があり様々な言語のOSに対してその言語に対応したレスポンスを返すことができる。例えば日本語のOSから以下のようなリクエストが来たとする。

GET /2010/05/01/press HTTP/1.1
Host: example.jp
Accept-Language: ja,en_us;q=0.7,en;q=0.3

Accept-Languageヘッダではクライアントが表示してほしい言語を指定している。(詳しくは9章で記述)このリクエストに基づいて日本語のプレスリリースをレスポンスとして返す。

ここでもしURIのパスが/pressだけの場合、日本語のOSを用いている人は英語のプレスリリースを取得するためにわざわざブラウザの設定を変えなければならない。このような問題に対処するためにURIに言語の拡張子をつけ、リソースの言語を明示的に指定するURIを用いる(/press.en)。
また、同様にしてフォーマットの指定も可能(html, txt, json)。

補足
拡張子はURIの設計上悪だと述べたが、実装に依存しない拡張子の場合以上に述べたようなメリットもある。

マトリクスURI

URIは「/」を用いて階層表現ができる。
http://example.jp/diary/2010/05/01
これは日付情報が年→月→日という階層構造を持っているから。
一方で階層では表現できない情報も存在する。例えば、地図上のある点を指すWebサイトがあるとして緯度(lat)と経度(lng)が情報として必要になるが、このパラメータを階層化することは不適切である。この場合以下のようなURIが使われる。
http://example.jp/map/lat=35;lng=139
このように複数の軸のパラメータをセミコロン(;)で区切ってリソースを表現する。

URIの不透明性

クライアントを作る際、URIをクライアント側で操作したり、構築してはならない。
なぜならクライアント側でURIの構築をした場合、サーバ側でURIの構造を変更しよとすると密結合状態になりシステムが動かなくなるからである。常にURIはクライアント側で組み立てたり、クライアント側が拡張子からリソースの内容を推測できない、つまり「URIはクライアントにとって不透明である」状態を保たなければならない。

第六章 HTTPの基本

HTTPの重要性

HTTPはRESTの重要な特徴を実現しているWebの基盤となるプロトコルである。現在のWebにはバージョン1.1のHTTPが最も使われている。HTTPはHTMLやXMLなどのハイパーテキストのみならず、静止画、音声、Javascriptプログラムなどコンピュータで扱えるデータであれば何でも転送できる。

TCP/IPとは何か

インターネットのプロトコルは階層型になっている。

階層型プロトコル
アプリケーション層:HTTP、NTP、SSH、SMTP、DNS
トランスポート層:UDP、TCP
インターネット層:IP
ネットワークインターフェース層:イーサネット
ネットワークインターフェース層

物理的なケーブルやアダプタ

インターネット層

ネットワークで実際にデータをやり取りする部分でIPが担う。IPではデータの基本的な単位を「パケット」と呼ぶ。指定したIPアドレスを送り先としてパケット単位でデータをやり取りする。
IPでは、自分のネットワークインターフェースでデータを送り出すことだけを保証しているが、最終的な送り先まで到達することは保証しない。

トランスポート層

IPが保証しなかったデータの転送を保証するのがTCP。TCPでは送り先に対してコネクションを張りコネクションを使ってデータの抜け漏れをチェックし、データの到達を保証する。どのアプリケーションに渡るのかを決めるのがポート番号で1から65535までの番号が割り当てられており、HTTPではデフォルトで80が割り当てられている。

アプリケーション層

メールやHTTP、DNSなどの具体的なアプリケーションを実現する層。
TCPでプログラムを作る際にはソケットと呼ばれるライブラリを用いるのが一般的で、ソケットはネットワークでのデータのやり取りを抽象化したAPIである。接続、送信、受信、切断などの基本的な機能を備えている。HTTPサーバやブラウザはソケットを用いて実装する。
ほとんどのプログラミング言語にはHTTPを実装したライブラリが標準で搭載されているためソケットを用いたHTTPの独自実装はほとんど行われない。

リクエストとレスポンス

HTTPではクライアントが出したリクエストをサーバで処理してレスポンスを返す。このようなプロトコルをリクエスト/レスポンス型のプロトコルと呼ぶ。HTTPは同期型のプロトコルであるため、サーバでの処理に時間がかかる場合でもクライアントはレスポンスが返ってくるまで待機する。このようなやりとりの際に、クライアントとサーバで行われていることをみていく。

クライアントで行われること
  • リクエストメッセージの構築
  • リクエストメッセージの送信
  • (レスポンスが返ってくるまで待機)
  • レスポンスメッセージの受信
  • レスポンスメッセージの解析
  • クライアントの目的を達成するために必要な処理

画像やスタイルシートなどへのリンクがいくつも含まれている場合、1回だけではなく数十回リクエストを発行しなければならない場合もある。

サーバで行われること
  • (リクエストの待機)
  • リクエストメッセージの受信
  • リクエストメッセージの解析
  • 適切なアプリケーションプログラムへの処理の委譲
  • アプリケーションプログラムから結果を取得
  • レスポンスメッセージの構築
  • レスポンスメッセージの送信

アプリケーションではデータベースから最新記事を取得したり、広告へのリンクを生成したりする。

HTTPメッセージ

リクエストメッセージとレスポンスメッセージをまとめて「HTTPメッセージ」と呼ぶ。

リクエストメッセージ

まずhttp://example.jp/testに対する必要最小限のリクエストは

GET /test HTTP/1.1
Host: example.jp

1行目は「リクエストライン」と呼び、メソッド(GET)、リクエストURI(/test)、プロトコルバージョン(HTTP/1.1)から成る。リクエストURIには絶対URIを入れてもいい。また複雑なURIの場合は次のようになる。

GET /search?q=test&debug=true HTTP/1.1
Host: example.jp:8000

リクエストラインにはURIフラグメントを除いたパス以降の文字列が入る。URIフラグメントはクライアント側で処理するのでリクエストメッセージには含めない。ポート番号はHostヘッダで指定。

リクエストメッセージの2行目以降はヘッダが続く。ヘッダはリクエストメッセージのメタデータである。各ヘッダは「名前:値」という構成をしている。また、ヘッダの後にはボディが続くこともある。例えばリソースを新しく作成したり更新したりするときにリクエストのボディにリソースの表現そのものが入る。

レスポンスメッセージ

先程のhttp://example.jp/testへのリクエストが成功すると次のレスポンスがクライアントに返される。

HTTP/1.1 200 OK
Content-Type: application/xhtml+xml; charset=utf-8

<html xmlns="http://www.w3.org/1999/xhtml">
・・・
</html>

基本的な構造はリクエストメッセージと同じだが、リクエストラインの部分はステータスラインと呼び、ステータスコード(200)とテキストフレーズ(OK)が入る。これらについては8章で記述する。

HTTPのステートレス性

まずステートレスとは何かを日常生活におけるステートレスなやり取りとステートフルなやり取りを比較して見ていく。

ステートレスなやり取り ステートフルなやり取り
ご注文をお伺いします。 ご注文をお伺いします。
ハンバーガーセットで。 ハンバーガーセットで。
サイドメニューはいかがなさいますか? サイドメニューはいかがなさいますか?
ハンバーガーセットをポテトで。 ポテトで。
ドリンクはいかがなさいますか? ドリンクはいかがなさいますか?
ハンバーガセットをポテトとコーラで。 コーラで。

このようにステートレスなやり取りでは店員(サーバ)がお客様(クライアント)の注文を覚えていないため、毎回同じ注文内容を全て繰り返さなければならないのに対し、ステートフルなやり取りではその差分だけを説明すればいい。なお、「ハンバーガーセットをポテトで」というお客様(クライアント)の注文内容をクライアントのアプリケーション状態という。

ステートフルの欠点

このやり取りの欠点はサーバがクライアントが増加すればそれだけアプリケーション状態を覚えることが難しくなる点にある。特に不特定多数のクライアントを相手にする場合、クライアントごとに接続するサーバを特定できないためどのサーバでも同じアプリケーション状態を扱えるようにデータを同期しなければならずこの際のオーバーヘッドが無視できないほど大きくなる。そのため、スケールアウトしにくい。

ステートレスの利点

クライアントからのリクエストに必要な情報が全て含まれている「自己記述的メッセージ」がサーバ側に送られるため、ステートレスなサーバはアプリケーション状態を覚える必要がない。そのため、サーバは単純になり新しく来るリクエストの処理に集中すれば良い。この場合クライアントが増えてもサーバを増設するだけでどのサーバにリクエストを送っても構わないのでスケールアウトしやすい。

ステートレスの欠点

一方で、ステートレスには欠点もある。

  • パフォーマンスの低下

サーバをステートレスにするにはクライアントから毎回必要な情報を全て送信しなければならないが、
「送信するデータ量の増加」、「認証などサーバに負荷がかかる処理を繰り返す」
といった理由からパフォーマンスに影響を与える。

  • 通信エラーへの対処

ステートレスなアーキテクチャではネットワークトラブルが起きたときにリクエストが処理されたかどうかが分からない。

第七章 HTTPメソッド

HTTPには以下の8つのメソッドが定義されており、主に以下の6つのメソッドが使われる。

メソッド 意味
GET リソースの取得
POST 子リソースの作成、リソースへのデータ追加、その他の処理
PUT リソースの更新、リソースの作成
DELETE リソースの削除
HEAD リソースのヘッダの取得
OPTIONS リソースがサポートしているメソッドの取得

HTTPメソッドのうちGET、POST、PUT、DELETEはこれら4つで「CRUD」という性質を満たす。
「CRUD」はCreate(作成)、Read(読み込み)、Update(更新)、Delete(削除)というデータ操作の基本となる4つの処理のこと。CRUDとHTTPメソッドの対応関係は以下のようになる。

CRUD 意味 HTTPメソッド
Create 作成 POST、PUT
Read 読み込み GET
Update 更新 PUT
Delete 削除 DELETE
以下それぞれのメソッドについて説明する。

GET

リクエスト
GET /list HTTP/1.1
Host: example.jp
レスポンス
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": "http://example.jp/list/item5"},
]

http://example.jp/listに対するGETで、サーバはリクエストに対して指定されたURIに対応するデータをレスポンスとして返している。

POST

子リソースの作成
リクエスト
POST /list HTTP/1.1
Host: example.jp
Content-Type: text/plain; charset=utf-8

こんにちは!
レスポンス
HTTP/1.1 201 Created
Content-Type: text/plain; charset=utf-8
Location: http://example.jp/list/item5

こんにちは!

新しい子リソースの作成をPOSTで指示し、ボディにリソースの内容を入れてある。このリクエストに応じて/listの下に新たに/list/item5というリソース(子リソース)を作成。201 Createdは新シリソースを生成したことを示す。

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

ログリソースへのデータの追加を考える。まずはリソースをGETしてみる。

リクエスト
GET /list HTTP/1.1
Host: example.jp
レスポンス
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

このリソースの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

200 OKが返ってきたのは新規リソースの作成ではなく、データの追加を意味したから。
あるリソースへのPOSTが作成を意味するのか追加を意味するのかはURIでは判別できず実装に依存するため、挙動はWebサービスやWeb APIの仕様書などで表現する。

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

たとえば検索結果を表現するURIを例に考えてみる。
http://example.jp/search?q={キーワード}
通常であればこのURIをGETするころで検索を実行するが、キーワードが非常に長い場合、実装によってはこのURIが長さの上限にかかってしまう場合がある(仕様上は長さ制限はない)。この場合以下のようにPOSTを用いる。

リクエスト
POST /search HTTP/1.1
Host: example.jp
Content-Type: application/x-www-form-urlencoded

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

GETではURIに含めていたキーワードをPOSTではリクエストボディに入れることができる。

PUT

リソースの更新
リクエスト
GET /list/item5 HTTP/1.1
Host: example.jp
レスポンス
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

こんにちは!

このリソースをPUTを使って更新する。

リクエスト
PUT /list/item5 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 /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の内容

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

POSTとPUTの使い分け

どのように使い分けるかに正解はないが設計上の指針として次の事実がある。
「POSTでリソースを作成する場合、URIの決定権はサーバ側にあるが、PUTの場合はその決定権がクライアント側にある。」
たとえばTwitterのようにつぶやきのURIをサーバで自動的に決定する場合、POSTを用いるのが普通。逆にWikiのようにクライアントが決めたタイトルがそのままURIになるWebサービスの場合はPUTを使うのが適している。しかし、クライアントがリソースのURIを決定できるということは、クライアントを作るプログラマーがサーバの内部実装を熟知していなければならない。そのため、PUTのほうがサーバとの結合が密になるのでリソースの作成はPOST、URIはサーバ側が決めるという設計が望ましい(第5章参照)。

DELETE

リクエスト
DELETE /list/item2 HTTP/1.1
Host: example.jp
レスポンス
HTTP/1.1 200 OK

一般にDELETEのレスポンスはボディを持たないのでステータスコードには204 No Contentが使われる場合もある。

HEAD

GETがリソースを取得するメソッドに対して、HEADはリソースのヘッドだけを取得するメソッドである。

リクエスト
HEAD /list/item1 HTTP/1.1
Host: example.jp
レスポンス
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

レスポンスにはボディが含まれないためネットワークの帯域を節約しながらリソースの大きさを調べたり、更新日時を取得できたりする。

OPTIONS

そのリソースがサポートしているメソッドの一覧を返す。なお、OPTIONS自体はAllowヘッダには含めない。

リクエスト
OPTIONS /list/ HTTP/1.1
Host: example.jp
レスポンス
HTTP/1.1 200 OK
Allow: GET, HEAD, POST

OPTIONSを実装する場合、多くのWebアプリケーションフレームワークでリソースに対応しているメソッドを返すように自前で実装しなければならない。

POSTでPUT/DELETEを代用する方法

6つのHTTPメソッドを説明してきたが、現実に一番良く利用されているのはGETとPOSTの2つだけである。これは、HTMLフォームに指定できるメソッドがGETとPOSTだけという制限に起因する。AjaxのXMLHttpRequestというモジュールを用いることで任意のメソッドを発行できるようにはなったが、このモジュールをサポートしない携帯電話向けブラウザはフォームしか利用できず、結果的にGETとPOST以外は使えない。またプロキシでこの2つのメソッド以外を制限している場合もある。このような状況でサーバにPUTやDELETEを伝える方法がある。

_methodパラメータ

_methodパラメータはRuby on Railsが採用。

PUTを_methodパラメータで指定
<form method="POST">
  <input type="hidden" id="_method" name="_method" value="PUT"/>
  <textarea id="body">...</textarea>
</form>
リクエスト
PUT /list/item1 HTTP/1.1
Host: example.jp
Content-Type: application/x-www-form-urlencoded

_method=PUT&body=...

Webアプリケーションフレームワークなどサーバ側の実装は_methodパラメータを見て、このリクエスト自体をPUTとして扱う。

X-HTTP-Method-Override

_methodパラメータはフォームを用いてリクエストを送る際には有効だが、POSTの内容がXMLなどapplication/x-www-form-urlencoded以外の場合は利用できない。この場合にX-HTTP-Method-Overrideヘッダが利用できる。これはGoogleのGDataが採用している手法。

リクエスト
PUT /list/item1 HTTP/1.1
Host: example.jp
Content-Type: application/xml; charset=utf-8
X-HTTP-Method-Override: PUT

<body>...</body>

サーバ側の実装はX-HTTP-Method-Overrideヘッダを見てこのリクエストをPUTとして扱う。

条件付きリクエスト

HTTPメソッドと更新日時などのヘッダを組み合わせることでメソッドを実行するかどうかをリソースの更新日時を条件にサーバが選択できるようになる。このようなリクエストを「条件付きリクエスト」と呼ぶ。
ヘッダとしてはIf-Modified-Since、If-UnModified-Sinceなどが使われる。

べき等と安全性

通信エラーが起きたときにリクエストをどのように回復するかという問題を解決するための工夫がHTTPの仕様でなされている。HTTPメソッドはその性質によって以下のように分類できる。

メソッド 性質
GET、HEAD べき等かつ安全
PUT、DELETE べき等だが安全でない
POST べき等でも安全でもない

べき等:ある操作を何回行っても結果が同じこと
安全:操作対象のリソースの状態を変化させないこと

PUTとDELETE(べき等だが安全でない)

どちらも更新、削除と操作対象のリソースの状態を変化させているという意味で「安全でない」が、何度同じ内容でリクエストを送信しても同じ内容が返ってくるので「べき等」
DELETEの場合は二回目以降404 Not Foundが返ってくるがリソースを削除したという結果は変わらない。

GETとHEAD(べき等かつ安全)

両方のメソッドが何回同じ内容をリクエストしても結果は変わらず、かつ操作対象のリソースを変化させることもない。

POST(べき等でも安全でもない)

POSTはべき等でも安全でもないため、リクエストの結果何が起こるかわからない。そのためステートレスなサーバではもし通信エラーの際にPOSTを再送信してしまうと二重注文のような問題が起こる可能性がある。

メソッドの誤用

GETやHEADがべき等かつ安全、PUTとDELETEがべき等であることはHTTPの仕様で定められているが、WebサービスやWeb APIの設計を誤ると、安全やべき等が保たれなくなる。

GETが安全でなくなる例
リクエスト
GET /resources/1/delete HTTP/1.1
Host: example.jp

この例では/resources/1を削除するために/resources/1/deleteをGETしている。このようにGETでリソースを更新したり、削除したりすると安全ではなくなる。この例ではそもそも名詞であるはずのURIにdeleteという動詞が入っていることもおかしい。

他のメソッドでできることにPOSTを誤用

POSTは他のメソッドでは対応できない処理をする点において万能メソッドではあるが、その万能性ゆえにPOSTをもちいてGETやPUT、DELETEなどのメソッドを実現してしまう。他の適切なメソッドが用意されているのにPOSTを用いることは、べき等性や安全性を利用できなくなるので誤用である。

PUTがべき等でなくなる例

現在のトマトの価格が100円であるとして、PUTで価格を更新することを考える。

リクエスト
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

150

これにもう一度同じリクエストを送るとトマトの価格が200円になり、べき等性が成り立っていない。この場合、価格を差分で表現せずに変更後の値で表現するべき。

DELETEがべき等でなくなる例

ソフトウェアの最新バージョンを表現するhttp://example.jp/latestというエイリアスリソースを考える。
DELETEによってエイリアスリソースそのものを削除するようにWebサービスやWeb APIを実装している場合べき等性が確保できているが、/latestが意味的に指し示す最新バージョンのリソースを削除してしまうと、

1回目のリクエスト
DELETE /latest HTTP/1.1
Host: example.jp
1回目のレスポンス
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

http://example.jp/1.2を削除しました。
2回目のリクエスト
DELETE /latest HTTP/1.1
Host: example.jp
2回目のレスポンス
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

http://example.jp/1.1を削除しました。

このように最初のDELETEで最新バージョン1.2、次のDELETEで1.1というようにDELETEがべき等でなくなってしまう。
この場合エイリアスリソースそのものを削除するように設計するのもいいですが、時間や状況で指し示すリソースが変化する特殊なリソースは特別な理由がない限り更新や削除などの操作ができなくなるように設計すると良い。

第八章 ステータスコード

ステータスコードの分類と意味

  • 1xx:処理中
    • 処理が継続していることを示す。リクエストを継続するか、サーバの指示に従ってプロトコルをアップデートして再送信する。
  • 2xx:成功
    • リクエスト成功
  • 3xx:リダイレクト
    • 他のリソースへのリダイレクトを示す。
  • 4xx:クライアントエラー
    • クライアントのリクエストが原因のエラー。エラーを解消しないと正常な結果が得られないので同じリクエストをそのまま再送信できない。
  • 5xx:サーバエラー
    • 原因はサーバ側にある。サーバ側の問題が解決すれば同一のリクエストを再送信して正常な結果が得られる可能性がある。

ステータスコードを先頭の文字で分類するのには2つの意味がある。

  • クライアントはとりあえず先頭の文字を見ればサーバがどのようなレスポンスを返したのか理解でき、クライアントでどのような処理をするべきかの大枠を知ることができる。
  • クライアントとサーバの約束事を最小限に抑えて、結びつきをなるべく緩やかにする。

よく使われるステータスコード

200 OK(リクエスト成功)
201 Created(リソースの作成成功)

これら2つは今まで出てきていたのでここでは割愛する。

301 Moved Permanently(リソースの恒久的な移動)

リクエストで指定したリソースが新しいURIに移動したことを示す。別のURIにクライアントが自動的に再接続する処理を「リダイレクト」と呼ぶ。

303 See Other(別URIの参照)

リクエストに対する処理が別のURIで取得できることを示す。

リクエスト
POST /login HTTP/1.1
Host: example.jp
Content-Type: application/x-www-form-urlencoded

username=yohei&password=foobar
レスポンス
HTTP/1.1 303 See Other
Location: http://example.jp/home/yohei
Content-Type: application/xhtml+xml; charset=utf-8

<html xmlns="http://www.w3.org/1999/xhtml">
  <head><title>redirect</title></head>
  <body>
    <p><a href="http://example.jp/home/yohei">結果</a>を確認してください。</p>
  </body>
</html>
リクエスト
GET /home/yohei HTTP/1.1
Host: example.jp
レスポンス
HTTP/1.1 200 OK
Content-Type: application/xhtml+xml; charset=utf-8

<html xmlns="http://www.w3.org/1999/xhtml">
  .....
</html>

このように典型的にはブラウザからPOSTでリソースを操作した結果をGETで取得するときに使う。

400 Bad Request(リクエストの間違い)

リクエストの構文やパラメータが間違っていたことを示す(例としてパスワードが単調すぎるなど)。また、適切なクライアントエラーを示すステータスコードがない場合やクライアントにとって未知の4xx系のステータスコードが返ってきたときにも400 Bad Requestと同じ扱いで処理するよう仕様で定められている。

401 Unauthorized(アクセス権不正)

適切な認証情報を与えずにリクエストを行ったことを示す。

404 Not Found(リソースの不在)

指定したリソースが見つからないことを示す。

500 Internal Server Error(サーバの内部エラー)

サーバ側に何らかの異常が起きていて正しくレスポンスが返せないことを示す。また、適切なサーバエラーを示すステータスコードがない場合やクライアントにとって未知の5xx系のステータスコードが返ってきたときにも500 Internal Server Errorと同じ扱いで処理するよう仕様で定められている。

503 Service Unavailable(サービス停止)

サーバがメンテナンスなどで一時的にアクセスできないことを示す。

ステータスコードとエラー処理

4xx系と5xx系のステータスコードはエラーを表現しているがボディにどんなエラーメッセージを入れるかは規定していない。たとえば404 Not Foundでは「ご指定のページは見つかりませんでした。」というメッセージ入りのHTMLをボディに加えることが一般的である。人間用のWebサービスの場合はエラーメッセージがHTMLで何も問題はないが、プログラム用のWeb APIは注意が必要である。これはクライアントがHTMLを解釈できるとは限らないからで、クライアントが解釈できる形式でエラーメッセージを返すと良い。例えば13章で出てくるAtomPubを利用したWeb APIの場合、AtomPubクライアントはAtom形式なら解釈できるためエラーメッセージはAtomで返すのが1つの方法である。

第九章 HTTPヘッダ

HTTPヘッダの重要性

HTTPヘッダはメッセージのボディに対するメタデータを表し、メディアタイプや言語タグなどをつける役割があり、認証やキャッシュなどメソッドやステータスコードなどと組み合わせて実現する機能もあるためHTTPの重要な構成要素である。

日時

Dateヘッダの例
Date: Tue, 06 Jul 2010 03:21:05 GMT

HTTPでは日時は全てGMTを使用することになっている。以下に日時を持つヘッダ一覧を示す。

利用するメッセージ ヘッダ 意味
リクエストとレスポンス Date メッセージを生成した日時
リクエスト If-Modified-Since 条件付きGETでリソースの更新日時を指定するときに利用する。
リクエスト If-Unmodified-Since 条件付きPUTや条件付きDELETEでリソースの更新日時を指定するときに利用する。
レスポンス Expires レスポンスをキャッシュできる期限
レスポンス Last-Modified リソースを最後に更新した日時
レスポンス Retry-After 再度リクエストを送信できるようになる日時の目安

MIMEメディアタイプ

リソースの表現の種類を示す。事例は今までも出てきていたので省略する。

Content-Type

ボディの内容がどのような種類なのかをメディアタイプで示す。
例えば「application/xhtml+xml」の場合、「/」の左側がタイプ、右側がサブタイプでタイプは勝手に増やすことができず現状以下の表の9つが定義されている。なお、サブタイプは自由に増やせる。

タイプ 意味
text 人が読んで直接理解できるテキスト text/plain
image 画像データ image/jpeg
audio 音声データ audio/mpeg
video 映像データ video/mp4
application その他のデータ application/pdf
multipart 複数のデータからなる複合データ multipart/related
message 電子メールメッセージ message/rfc822
model 複数次元で構成するモデルデータ model/vml
example 例示用 example/foo-bar

登録済みのタイプとサブタイプはIANAが管理しており、こちらのサイトMedia Typesでその一覧を見ることができる。

charsetパラメータ

charsetパラメータは省略可能であるが、タイプがtextの場合は要注意である。というのはtextのデフォルトエンコーディングがISO8859-1であると設定されており、仮にメッセージに日本語文字列が含まれていてもISO8859-1でエンコーディングされ、文字化けを起こしてしまう可能性がある。また、xmlでエンコーディングを宣言してもtextタイプの場合はContent-Typeヘッダのcharsetパラメータが優先されるためcharsetパラメータを省略せず書いておくのが良い。

言語タグ

言語タグの例
Content-Language: ja-JP

左側にはISO 639が定義する言語コード、右側にはISO 3166が定義する地域コードが入る。日本語の場合、日本でしか使用されないため言語コードと地域コードが等しいが英語の場合だと「en-US」「en-CA」など様々な国で使われる英語の種類が定義される。

コンテントネゴシエーション

Accept(処理できるメディアタイプを伝える)
Acceptヘッダの例
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

この場合優先度は、「text/html,application/xhtml+xml」がデフォルトの1、application/xmlが0.9、その他の全てのメディアタイプが0.8となる。
メディアタイプにサーバが対応していなかった場合、「406 Not Acceptable」が返る。

Accept-Charset(処理できる文字エンコーディングを伝える)
Accept-Charsetヘッダの例
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7

なお、ISO 8859-1はデフォルトで1となっているがより具体的なShift_JISが優先される。

Accept-Language(処理できる言語を伝える)
Accept-Languageヘッダの例
Accept-Language: ja,en-us;q=0.7,en;q=0.3

Content-Lengthとチャンク転送

Content-Length

ボディの長さを指定できる。静的なファイルなどあらかじめサイズの分かっているリソースを転送する際にこのヘッダを利用するのが簡単。

Content-Lengthヘッダの例
Content-Length: 5538
チャンク転送

動的に画像を生成するようなWebサービスの場合にTransfer-Encodingヘッダが用いられる。

Transfer-Encodingヘッダの例
Transfer-Encoding: chunked

これを指定することで最終的なサイズがわからないボディを少しずつ転送できるようになる。

チャンク転送
POST /test HTTP/1.1
Host: example.jp
Transfer-Encoding: chunked
Content-Type: text/plain; charset=utf-8

10
Thebrown for ju

10
mps quickly over 

e
the lazy dog.

0

各チャンクの先頭にはチャンクサイズが16進数で入る。最後には必ず長さ0のチャンクと空行がつく。

認証

Basic認証

まずクライアントはサーバに

Basic認証
DELETE /test HTTP/1.1
Host: example.jp
Authorization: Basic dXNlcjpwYXNzd29yZA==

Authorizationヘッダの内容は認証方式(Basic)に続けて、ユーザ名とパスワードを「:」で連結し、Base64エンコードした文字列(デコードするとuser:password)。このエンコードは簡単にデコードされてしまうので、それが許される程度のセキュリティで良いのか、HTTPS通信で通信路上で暗号化するのかを考える必要がある。

Digest認証

ダイジェストとはあるメッセージに対してハッシュ関数を適用した結果のハッシュ値。まずクライアントは認証情報なしでリクエストを送信する。401 Unauthorizedが返ってくるが、WWW-Authenticateに含まれる値「チャレンジ」を使って次回のリクエストを組み立てる。チャレンジに含まれる値について以下に説明する。

  • nonce
    number used onceのことを意味し、リクエストごとに変化する文字列を指す。基本的にタイムスタンプやサーバだけが知り得るパスワードから生成する。タイムスタンプはnonceを使ったリクエストの有効期間を狭めるため。
  • qop
    quality of protectionの略でauthかauth-initを指定する。クライアントが送信するダイジェストの作成方法に影響を与える。authはメソッドとURIからダイジェストを作成し、auth-initはそれに加えてメッセージボディも利用する。POSTやPUTでボディを送信するときにauth-initを使うとメッセージ全体が改ざんされていないことを保証できる。
  • opaque
    クライアントには不透明な文字列で、同じURI空間へのリクエストでは共通してクライアントからサーバに送る。

これらの値を用いてどのようにダイジェストを生成するのかについて以下に記す。

  1. ユーザ名、realm、パスワードを「:」で連結し、MD5ハッシュ値を求める。
  2. メソッドとURIのパスを「:」で連結し、MD5ハッシュ値を求める。
  3. 「1」の値、サーバから得たnonce、クライアントがnonceを送った回数、クライアントが生成したnonce、qop、「2」の値を「:」で連結し、MD5ハッシュ値を求める。

この方法で求めたダイジェスト値をresponceというフィールドに入れてリクエストを送信。

Digest認証の欠点
  • パスワードを暗号化することはできるが、メッセージは平文なので暗号化するためにHTTPSを用いる必要がある。
  • リクエストのたびに401 Unauthorizedレスポンスを得る必要がある。
その他の認証方式

ここでは紹介しないが、AtomPubなどのWeb APIの認証に用いられるWSSE認証などもある。

キャッシュ

キャッシュ用ヘッダ

3種類のキャッシュ用ヘッダが存在する。

  • Pragma

    • このヘッダに指定できる値は公式にはno-cacheのみでリソースをキャッシュしてはならないことを示す。
  • Expires

    • キャッシュの有効期限を示す。
    • クライアントはリソースにアクセスするときこの期限内か否かでサーバに再度アクセスするかキャッシュを利用するかを決定。
  • Cache-Control

    • 上の2つよりキャッシュに複雑な設定を行いたいときに用いる。
条件付きGET

キャッシュ用ヘッダによってキャッシュが再利用できないと判断された場合でも条件付きGETを利用することで再利用できる可能性がある。これはサーバ側のリソースがクライアントローカルのキャッシュから変更されているかどうかを調べるヒントをリクエストヘッダに含めることでキャッシュがそのまま使えるかどうかを検証する。

  • If-Modified-Since
    ローカルキャッシュの更新日時をリクエストヘッダに含めて、これ以降変更されていないかを検証。変更されていなければ304 Not Modifiedを返す。
  • If-None-Match
    • If-None-MatchヘッダとETagヘッダを用いて行う検証で、If-None-Matchヘッダに指定した値が変更されているかどうかを検証。変更がなければ304 Not ModifiedとETagを返す。ETagはリソースの更新状態を比較するための文字列で更新したときに別の値になるので、更新しているかどうかをこのETagで確認することがきでる。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?