Content Negotiation

この頁では、HTTPを使って「表現」を自動選択する機能である、内容ネゴシエーション(コンテントネゴシエーション)について解説します。

内容ネゴシエーション(コンテントネゴシエーション)とは

内容ネゴシエーション(※)とは、クライアントサーバが文字通りそのリソースの「内容」について「交渉」をすることで、クライアントが最も望む形態のリソースを得られる様にするための仕組みです。 RFC 2616のsection 12では、内容ネゴシエーションに関する章です。

(※) 内容ネゴシエーション{Content Negotiation}は、英語をそのまま読んだ「コンテントネゴシエーション」(あるいは「コンテンツネゴシエーション」)と書かれる場合も多いですが、当サイトでは「内容ネゴシエーション」という呼び名で統一します。

多くの HTTP レスポンスは、人間ユーザによって解釈される情報から成るエンティティを含む。 当然、リクエストに対応した "最も利用できる" エンティティをユーザに供給するのが望ましい。 サーバやキャッシュにとって不幸な事は、すべてのユーザが "最上" なものについて同じ優先度を持たせているわけではないし、すべてのユーザエージェントがすべてのエンティティタイプを等しく表現できるわけではないという事である。 この理由により、HTTP は "内容ネゴシエーション"、すなわち複数の利用可能な表現がある時に与えられたレスポンスにとって最適な表現を選択するための処理のためのいくつかのメカニズムを持っている。

注意:これは、ある別の表現として、メディアタイプは同じであるが使用言語が異なっている等、そのタイプとは異なる能力{capabilities} を使用するかもしれないので、"フォーマットネゴシエーション" とは呼ばれない。

エンティティボディを含むあらゆるレスポンスは、エラーレスポンスを含めネゴシエーションを受けさせる事ができる

元々、内容ネゴシエーションは、HTTPの仕様とは別に仕様が考えられていました。 それがRFC 2295という文書で、RFC 2068HTTP/1.1の初版)の発行後に、単独で発行されています。

web 基盤に内容ネゴシエーションを追加する事は、web の初期から重要であると考えられてきた。 内容ネゴシエーションのための十分に強力なシステムの期待される利点の中には以下のものがある。

HTTP/1.1 では、web サイトの作者が単一の URI に属する同じ情報の複数のバージョンを置く事を許している。 それらバージョンのそれぞれは `バリアント' と呼ばれる。 例えば、リソース http://x.org/paper は、文書の三つの異なるバリアントに結び付ける事ができる:

  1. HTML, English
  2. HTML, French
  3. Postscript, English

内容ネゴシエーションは、リソースがアクセスされた時に、最善のバリアントが選択される事による過程である。 この選択は、利用可能なバリアントの属性とユーザエージェントの能力やユーザの優先度が一致する事によって完了する。

たとえば、「日本語版」や「英語版」などの『リソースの言語が異なる』場合、あるいは HTML ファイルやプレーンテキスト、更にそれに gzip による圧縮等が施されているといった『メディアタイプが異なる』場合、これらのファイルのそれぞれはバリアント{variant}と呼ばれます。 内容ネゴシエーションとは、単一のURIに配置された複数のバリアントをサーバやユーザエージェントが自動に選択できるようにする仕組みです。

サーバ駆動型ネゴシエーション

内容ネゴシエーションをサーバ側で行うことをサーバ駆動型ネゴシエーションと言い、section 12.1 に記述されています。

レスポンスとしての最適な表現の選択がサーバに設けられたアルゴリズムによって行われた場合、これはサーバ駆動型ネゴシエーションと呼ばれる。 選択は、利用可能なレスポンスの表現 (それが変化できる次元、例えば言語や内容コーディング等) や、リクエストメッセージ内の特定のヘッダフィールドの内容、あるいはそのリクエストに関するその他の情報 (例えばクライアントのネットワークアドレス) に基づく。

サーバ駆動型ネゴシエーションは、利用可能な表現の中から選択するためのアルゴリズムがユーザエージェントに説明するのが難しい時や、サーバが最初のレスポンスと一緒にクライアントに自身の "最適な推測" を送る事を望む (もしその "最適な推測" がユーザにとって十分なものであれば、以降のリクエストの往復時間遅れ{round-trip delay} を避けられる) 時に有益である。 サーバの推測を改善するため、ユーザエージェントはそのようなレスポンスのための自身の優先度を表すリクエストヘッダフィールド (Accept, Accept-Language, Accept-Encoding 等) を含む事ができる

ここでいう「表現」とは、先に挙げた言語やメディアタイプの違いなどを指しています。 もし、内容ネゴシエーションがなかったら、クライアントが受け取ったリソースについて、クライアントがより望む形の「表現」が存在すると知った場合、クライアントはそのリソースは廃棄し、再度サーバにリクエストをするでしょう。 サーバ駆動型ネゴシエーションはこの手間を省いてくれます。 結果的に、時間的・金銭的通信コストが節約され、またサーバの負荷も軽減できるということになるわけです。

品質値

では、リソースの「表現」に優先順位をつけたい場合、たとえば「image/pngがあればそのリソースを、なければimage/jpegのリソースをください」という場合、どうすればいいのでしょうか? このような場合、ユーザエージェントAcceptAccept-LanguageAccept-Encodingなどのヘッダに品質値というパラメータを追加することができます。 品質値については、RFC 2616のsection 3.9をご覧ください。

HTTP 内容ネゴシエーション (section 12) は、さまざまなネゴシエート可能なパラメータの相対な重要性 ("ウェイト") を示すために短い "浮動小数点" 数を使う。 ウェイトは、0 から 1 までの実数値と標準化され、0 は最小値で 1 は最大値である。 もしパラメータが 0 の品質値を持っていたら、そのパラメータと共にあるものはクライアントは「利用不可能」である。 HTTP/1.1 アプリケーションは、小数点以下で三桁を越える数字を生成してはならない。 これらの値のユーザ設定も、この様式にかぎられるべきである

 qvalue         = ( "0" [ "." 0*3DIGIT ] )
                | ( "1" [ "." 0*3("0") ] )

"品質値" とは、単に要請される特質の相対的な格付けを示すものなので、実際は誤った表現である。

では、実際にどのように使われているのかの例として、Acceptヘッダについて記述されている、RFC 2616のsection 14.1をご覧ください。

次の例を見よ。

 Accept: audio/*; q=0.2, audio/basic

この例では、「私は audio/basic を望むが、もし品質を 80% 下げても最も有効に利用できるのであれば、どんな audio タイプでもかまわない」と解釈されるべきである

Accept ヘッダフィールドが無い場合は、クライアントはすべてのメディアタイプを受けつけるとみなされる。 Accept ヘッダフィールドがある場合に、サーバが Accpet フィールド値に適したレスポンスを送る事ができなければ、サーバは 406 (not acceptable) レスポンスを返すべきである

より複雑な例を示す。

 Accept: text/plain; q=0.5, text/html,
         text/x-dvi; q=0.8, text/x-c

これを文字通りに解釈すると、「text/html と text/x-c が望むメディアタイプであるが、もし存在しないのであれば text/x-dvi エンティティを、それも無ければ、text/plain エンティティを送信せよ。」となる。

メディアレンジでは、より特定されるメディアレンジやメディアタイプが優先される。 あるメディアタイプに複数のメディアレンジが当てはまる場合、最も特定される指示を優先される。 例を見よ。

 Accept: text/*, text/html, text/html;level=1, */*

この優先順位は以下の様になる。

  1. text/html;level=1
  2. text/html
  3. text/*
  4. */*

あるメディアタイプに関連付けられる品質係数は、そのメディアタイプにマッチする最も高い優先度を持つメディアレンジを発見する事によって決定される。 例を見よ。

 Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1,
        text/html;level=2;q=0.4, */*;q=0.5

品質係数は以下の様になる。

 text/html;level=1         = 1
 text/html                 = 0.7
 text/plain                = 0.3
 image/jpeg                = 0.5
 text/html;level=2         = 0.4
 text/html;level=3         = 0.7

注: ユーザエージェントは、あるメディアレンジに対して既定の品質値を供給するかもしれない。 しかし、ユーザエージェントが他のレンダリングエージェントと相互作用できないクローズドなシステムで無いのであれば、この既定値はユーザが設定できるようにすべきである。

上はAcceptの例でしたが、これ以外のヘッダでも、品質値を使って、リソースに対する優先度を指定しています。

エージェント駆動型ネゴシエーション

前節では、サーバ駆動型ネゴシエーションの動作原理について記述してきましたが、実は弱点もあります。 引き続き、RFC 2616のsection 12.1をご覧ください。

サーバ駆動型ネゴシエーションは以下の不都合を持つ。

  1. ユーザエージェントの能力とレスポンスの使用目的 (例えば、ユーザはそれをスクリーンに表示させたいのか、それとも紙に印刷したいのか?) の両方の完全な情報が必要なので、サーバがそのユーザにとって "最適" であるものをすべて正確に決定するのは不可能である。
  2. リクエスト毎にユーザエージェントに自身の能力を記述させる事は、非常に能率が悪く (レスポンスが複数の表現を持っている確率は少ないため)、ユーザのプライバシーの潜在的な侵害となりうる。
  3. リクエストに対してレスポンスを生成するため、オリジンサーバの実装とそのアルゴリズムが複雑になってしまう。
  4. 複数のユーザのリクエストに同じレスポンスを使う共有キャッシュの能力を制限するかもしれない。

サーバ駆動型ネゴシエーションを、すべてのユーザに対して満足するように実行するためには、ユーザから個人情報を大量に受け取る必要があります。 その情報が少ないと、ユーザがどのリソースを適切だと思うかわからないため、サーバが選択したリソースは本来ユーザが望むものでない可能性が生じてしまうことになります。

一方で、ユーザから個人情報を大量に受け取ると、サーバ上で管理する情報が増え、潜在的なセキュリティリスクとなります。 また、そもそもユーザが複数のリソースを用意していることが少ない(たとえば、日本人が作るWebリソースはほとんどの場合「日本語リソース」しか作らない)ため、大量の個人情報を送ることが無駄になるというリスクがあるし、仮に複数のリソースがあっても、そのような大量な個人情報をサーバ上で捌くためには、非常に複雑な実装が必要となってしまいます。

そこで、サーバ上だけでなく、クライアント側にもネゴシエーションを行わせようという考えが持ち出されました。 これをエージェント駆動型ネゴシエーション(クライアント駆動型ネゴシエーション)と言い、RFC 2616のsection 12.2に記述されています。

エージェント駆動型ネゴシエーションの場合、レスポンスとしての最適な表現の選択は、オリジンサーバからの最初のレスポンスを受けとった後にユーザエージェントによって実行される。 選択は、最初のレスポンスのヘッダフィールドやエンティティボディに含まれる利用可能なレスポンスの表現のリストに基づき、それぞれの表現毎に自身の URI によって識別される。 表現内からの選択は、 (ユーザエージェントがそうする能力があれば) 自動的に、あるいは生成された (おそらくハイパーテキストの) メニューからのユーザの選択によって手動的に行われるだろう。

エージェント駆動型ネゴシエーションは、レスポンスが一般的に使われる次元 (例えばタイプ、言語、エンコーディングのような) と異なる時、オリジンサーバがリクエストの評価からはユーザエージェントの能力を決定できない時、そして一般的に共有キャッシュがサーバの負荷を分散し、ネットワークの使用を減らすために使用される時に有益である。

エージェント駆動型ネゴシエーションは、最適な入れ替わるべき "表現" を得るために次のリクエストが必要となるという不都合がある。 次のリクエストはキャッシングが使用されている時にのみ効率的である。 さらに、この仕様書では自動選択をサポートするどんなメカニズムも拡張として改良したり、HTTP/1.1 内で使用したりする事を禁止しないが、そのいかなるメカニズムも定義しない。

HTTP/1.1 は、サーバがサーバ駆動型ネゴシエーションで使って異なるレスポンスを供給しようとしない、あるいはできない時にエージェント駆動型ネゴシエーションを可能にするため、300 (Multiple Choices) と 406 (Not Acceptable) ステータスコードを定義する。

クライアント(ユーザエージェント)は、サーバから特定のステータスコードを持つレスポンスを受け取ることで、エージェント駆動型ネゴシエーションを実行します。

レスポンスとして、複数のリソースから選択を要求する場合(300レスポンス)

サーバが、リクエストに含まれるユーザ情報からサーバ駆動型ネゴシエーションを行った時、ユーザ情報が足りないため、該当するリソースが複数ヒットする場合があります。 この場合、クライアントがリクエストを完了するためには、該当リソースのうちどれが欲しいかを選択してもらう必要がありますが、それを示すためのステータスコード300レスポンスです。 RFC 2616のsection 10.3.1をご覧ください。

リクエストされたリソースは、表現セットの一つに対応し、各々が特有の場所にあり、エージェント駆動型ネゴシエーション情報 (section 12) が、ユーザ (あるいはユーザエージェント) が望む表現を選択でき、その位置にリクエストをリダイレクトできるように供給されている。

もし HEAD リクエストでなければ、レスポンスはエンティティにユーザかユーザエージェントが最も適切なものを選択するためのリソースの特徴と場所のリストを含むべきである。 エンティティのフォーマットは、Content-Type ヘッダフィールドにて与えられるメディアタイプによって指定される。 データフォーマットやユーザエージェントの能力に依存する事なので、最も適切な選択は自動的に行われるかもしれない。 しかし、この仕様書ではそのような自動選択に対してどのような標準も定義しない。

サーバが選択された表現を持っているのであれば、Location フィールド内にその表現のための具体的な URI を含むべきである。 ユーザエージェントは、自動リダイレクションのためにその Location フィールドの値を使う事ができる。 このレスポンスは、別のものを示しているのでなければキャッシュ可能である。

300レスポンスには“Multiple Choices”という説明句がついていますが、その名の通り、該当リソースが複数ある場合にユーザに選択させるためのステータスコードです。 リソースの選択は、ユーザエージェントが行っても、その後ろにいるユーザが行ってもよいですが、ユーザエージェントに自動的に選択させたいのならばLocationヘッダを使うとよいでしょう。

レスポンスとして、ふさわしいリソースを保持していないと判断した場合(406レスポンス)

上の例とは逆に、リクエストに含まれるユーザ情報からサーバ駆動型ネゴシエーションを行ったものの、該当するリソースがない場合があります。 この場合は、該当リソースがないことを示すために、サーバは406レスポンスを返します。 RFC 2616のsection 10.4.7をご覧ください。

リクエストによって識別されるリソースは、リクエスト中に送られた Accept ヘッダによれば、受け入れられない内容特性を持つレスポンスエンティティを生成する事ができるのみである。

もし HEAD リクエストでなければ、レスポンスはエンティティにユーザかユーザエージェントが最も適切なものを選択するためのリソースの特徴と場所のリストを含むべきである。 エンティティのフォーマットは、Content-Type ヘッダフィールドにて与えられるメディアタイプによって指定される。 データフォーマットやユーザエージェントの能力に依存する事なので、最も適切な選択は自動的に行われるかもしれない。 しかし、この仕様書ではそのような自動選択に対してどのような標準も定義しない。

注: HTTP/1.1 サーバは、リクエスト中に送られた Accept ヘッダによれば受け入れる事ができないとされるレスポンスを返す事を許されている。 そのような場合、406 レスポンスを送る事が望ましい。 ユーザエージェントは、もしそれを受け入れられるなら、それを決定するために送られてきたレスポンスのヘッダを詳しく調べる事が推奨される。

もしレスポンスが受け入れる事ができなければ、ユーザエージェントはそれ以上のデータの受信を一時的に中止し、それ以降の動作を決定するためユーザに尋ねるべきである

たとえば、日本語を用いたリソースしか用意していないサイトに、日本語を理解できないユーザエージェントがアクセスしてきた場合を考えます。

 GET /negotiation HTTP/1.1
 Host: www.studyinghttp.net
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9) Gecko/2008052906 Firefox/3.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Referer: http://www.studyinghttp.net/
 Cache-Control: max-age=0

 HTTP/1.x 406 Not Acceptable
 Date: Wed, 18 Jun 2008 11:51:26 GMT
 Server: Apache
 Alternates: {"negotiation.shtml" 1 {type text/html} {charset euc-jp} {language ja}}
 Vary: negotiate
 TCN: list
 Keep-Alive: timeout=1, max=9
 Connection: Keep-Alive
 Transfer-Encoding: chunked
 Content-Type: text/html; charset=iso-8859-1

リクエストのAccept-Languageには、日本語を意味する“ja”が含まれていません。 しかし、このサイトには日本語のリソースしかなので、サーバは返すべきリソースが無いと判断しました(現在、サーバにある表現のリストはAlternatesヘッダに含むことができます)。 よって、このリクエストは無効であるとして、サーバはクライアントにリクエストの再実行を求めるために、406を返しているのです。

なお、クライアントが受け入れ可能であると提示していないタイプのエンティティでも、サーバはレスポンスとして返すことは禁止されていません。 ただし、その場合でも、「本来ユーザが望んだものとは異なるリソースを返している」ということを示すという意味で、ステータスコードは406を返すべきとされています。 また、ユーザエージェントもそのようなリソースを受け入れる前に、これを取得すべきかどうかユーザに尋ねるべきでしょう。

サーバ側の原因でネゴシエーションに失敗した場合(506レスポンス)

なお、リクエストに含まれるユーザ情報からサーバ駆動型ネゴシエーションを行おうとしたものの、サーバ上の設定ミスなどにより、ネゴシエーションに失敗したことを表すステータスコードとしては、506というものが定義されています。 RFC 2295のsection 8.1をご覧ください。

506 ステータスコードは、サーバが内部配置上のエラーがある事を示す: 選択されたバリアントリソースは、透過的内容ネゴシエーション自体に関与するために形成されたものであり、従ってネゴシエーションプロセスにおける適切な終点ではない。

サーバ内部で透過的内容ネゴシエーションが使用されていますが、そこで選択されたリソース先がユーザが取り出して利用できるようなものではないので、リソースを返すことができません。 たとえば、ふさわしいリソースとして指された先が自分自身を示しているなど、「ネゴシエーションが無限ループしている」といった場合が考えられます。

JavaScriptを用いてWebブラウザの名前とバージョンを取得するためのライブラリ - H_Browser.js

ここでは、Studying HTTPが製作した「JavaScriptを用いてWebブラウザの名前とバージョンを取得するためのライブラリ」であるH_Browser.jsを公開します。 これは、JavaScriptエンジンを持つ有名ブラウザnavigator.userAgent文字列を解析し、そのブラウザ名とバージョンをプロパティとして保持するものです。

H_Browser.js, v0.4 Update: 2010-10-10

このライブラリを使用することで、エージェント駆動型ネゴシエーションを実現させることができます。 何故ならば、WebブラウザによってJavaScriptによって利用できる機能(あるいは機能の利用の仕方)に差があるために、各Webブラウザに適したコードを読み込ませなければ、異なるブラウザ間で同じような振る舞いを実現できないからです。 使用例とデモコードはJudge Your Browser.を参照ください。

(※) 現在のH_Browser.jsでは、ブラウザのメジャーバージョンしか取得できません(マイナーバージョンは取得できません)。 また、コンピュータのOS(Windows, Macintosh, Unix, ...)を判定することはできません。

参照文献

Webリソース