Apache Content Negotiation

### Apache HTTP Server

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

Apache のコンテントネゴシエーションのサポートは HTTP/1.1 の規格に合うように 更新されました。それにより、ブラウザにより提供されたメディアタイプ、 言語、キャラクタセットと、エンコーディングの優先順位を用いてリソースの 一番良い表現方法を選択できます。また、不完全なネゴシエーション用の情報を 送ってくるブラウザからのリクエストをより優雅に扱うための機能を いくつか実装しています。

コンテントネゴシエーションは mod_negotiation モジュールにより 提供されていて、デフォルトで組み込まれています。

## コンテントネゴシエーションについて

リソースは、幾つか異なった表現で利用できる場合があります。 たとえば、異なる言語や異なるメディアタイプ、またはそれらの組み合わせで 利用できるかも知れません。もっとも適した選択をする方法の一つには、 インデックスページをユーザに見せて、ユーザに選んでもらう方法があります。 しかし、サーバが自動的に選ぶことができる場合が多くあります。 これは、ブラウザがリクエスト情報毎の情報の一部に、 どの表現を嗜好するかを送ることで動作しています。 たとえばブラウザは、可能ならフランス語で情報を見たい、 不可能ならその代わりに英語でもよいと、自分の嗜好を知らせることができます。 ブラウザはリクエストのヘッダで自分の優先傾向を知らせます。 フランス語の表現だけを要求する場合は、ブラウザは以下を送ります。

      Accept-Language: fr

この優先傾向は、選択可能な表現が存在して、 言語によって様々な表現がある場合にのみ適用される ということに注意してください。

もっと複雑なリクエストの例を挙げましょう。 このブラウザはフランス語と英語を受け付ける、 しかしフランス語を好む、そして様々なメディアタイプを受け付けるが、 プレインテキストや他のタイプよりは HTML を好む、 他のメディアタイプよりは GIF や JPEG を好む、しかし最終手段として 他のメディアタイプも受け付ける、というように設定されています。

      Accept-Language: fr; q=1.0, en; q=0.5
      Accept: text/html; q=1.0, text/*; q=0.8, image/gif; q=0.6,
            image/jpeg; q=0.6, image/*; q=0.5, */*; q=0.1

Apache 1.2 は HTTP/1.1 の規格で定義されている 'server driven' コンテントネゴシエーションをサポートしています。 Accept, Accept-Language, Accept-Charset, Accept-Encoding リクエストヘッダを完全にサポートしています。Apache 1.3.4 は RFC 2295 と RFC 2296 で定義されている実験的なネゴシエーションプロトコルである、 'transparent' もサポートしています。それらの RFC で定義されている 'feature negotiation' はサポートしていません。

リソースとは URI で特定される概念上のもののことです (RFC 2396)。 Apache のような HTTP サーバは、その名前空間の中での リソースの表現へのアクセスを提供します。 それぞれの表現は定義されたメディアタイプ、文字セット、 エンコーディング等の付属した、バイト列の形式です。 それぞれのリソースはある時点で 0 個、1 個、それ以上の表現と 関連付けられる可能性があります。複数の表現が利用できる場合は、 リソースはネゴシエーション可能であるとされ、 個々の表現は variant と呼ばれます。 ネゴシエーション可能なリソースの variant が異なる、その状態を指して、 ネゴシエーションの次元と呼びます。

## Apache におけるネゴシエーション

リソースをネゴシエーションするためには、サーバはそれぞれの variant についての情報を知っておく必要があります。 これは以下の二つの方法のどちらかで行われます。

- タイプマップ (すなわち、`*.var` ファイル) を使う方法。これは、variant を明示的に挙げているファイルを指定します。
- 'Multiviews' を使って、 サーバが暗黙の内にファイル名にパターン照合を 行なってその結果から選択する方法。

### type-map ファイルを使う

タイプマップは `type-map` ハンドラ (もしくは、古い Apache の設定に上位互換となるために維持されている mime タイプ `application/x-type-map`) に関連付けられたドキュメントです。 この機能を使うためには、あるファイルの拡張子を `type-map` として定義するハンドラを設定ファイルで設定する 必要があることに注意してください。これは

      AddHandler type-map .var

をサーバの設定に書くことが一番良い方法です。 詳細はサンプルの設定ファイルのコメントを参照してください。

タイプマップファイルにはそれぞれの variant についてのエントリがあります。これらのエントリは連続した HTTP のヘッダ行から成ります。別の variant のエントリとは空行で 分けられています。エントリ中に空行があってはいけません。 マップファイルは全体のエンティティをくっつけた形で始めるのが 習慣となっています (これは要求されているわけではなく、もしあった場合は無視されます)。 たとえば、マップファイルは次のようになります。この例では、 ファイルの名前は `foo.var` で、リソース `foo` のいろいろな variant があるディレクトリに配置されることになります。

      URI: foo

      URI: foo.en.html
      Content-type: text/html
      Content-language: en

      URI: foo.fr.de.html
      Content-type: text/html;charset=iso-8859-2
      Content-language: fr, de

variant の品質が違うときは、この画像のように (JPEG, GIF, ASCII アートがあります) メディアタイプの "qs" パラメータとして指定されます。

      URI: foo

      URI: foo.jpeg
      Content-type: image/jpeg; qs=0.8

      URI: foo.gif
      Content-type: image/gif; qs=0.5

      URI: foo.txt
      Content-type: text/plain; qs=0.01

qs 値の範囲は 0.000 から 1.000 です。qs 値が 0.000 の variant は決して選択されないことに注意してください。'qs' 値のない variant は qs 値 1.0 を 与えられます。qs パラメータはクライアントの能力に関係無く、他の variant と比較したときの variant の相対的な「品質」を示します。たとえば、 写真を表現しようとしているときは JPEG ファイルの方が普通は ASCII ファイルよりも高い品質になります。 しかし、リソースが元々 ASCII アートで表現されているときは、ascii ファイルの方が JPEG ファイルよりも高い品質になります。このように、qs は表現されるリソースの性質によって variant 毎に特有の値を取ります。

認識されるヘッダの完全な一覧は以下のようになります:

`URI:`
  (与えられたメディアタイプのもので、与えられた content encoding でエンコードされた) variant のファイルの URI 。マップファイルからの相対 URL として解釈されます。 同じサーバ上にある必要があり、 直接要求されたときにクライアントがアクセスを 認められているファイルを参照していなければなりません。
`Content-Type:`
  メディアタイプ --- charset, level, "qs" パラメータを指定することができます。これらはしばしば MIME タイプと呼ばれます。典型的なメディアタイプは `image/gif`, `text/plain`, `text/html; level=3` です。
`Content-Language:`
  RFC 1766 のインターネット標準言語タグで指定された、variant の言語。 (たとえば、英語は `en`、 韓国語は `kr`, 等)。
`Content-Encoding:`
  生のデータではなく、ファイルが圧縮されていたり、 エンコードされたりしているときに、 どのようにそれがなされているかを指定します。 Apache は AddEncoding ディレクティブで定義されたエンコーディングのみを認識します。通常は compress されたファイルのための `x-compress` や gzip されたファイルのための `x-gzip` のようなエンコーディングを含みます。 エンコーディングの比較時には接頭辞 `x-` は無視されます。
`Content-Length:`
  ファイルのサイズ。タイプマップで content length を指定すると、 サーバが実際のファイルを調べずに ファイルのサイズを比較することができます。
`Description:`
  人間が読める variant を説明した文章。Apache が適切な variant を見つけられなかった場合は、すべての可能な variant の一覧を挙げるエラー応答を返します。その variant 一覧は人間が読める variant の 説明を含みます。

### Multiviews

`MultiViews` はディレクトリ毎のオプションで、 `<Directory>`, `<Location>`, `<Files>` や、(`AllowOverride` が適切な値に設定されていると) `.htaccess` ファイルで `Options` ディレクティブによって設定することができます。`Options All` は `MultiViews` を設定しないことに注意してください。 明示的にその名前を書く必要があります。

`MultiViews` の効果は以下のようになります: サーバが `/some/dir/foo` へのリクエストを受け取り、 `/some/dir/foo` が存在しない場合、 サーバはディレクトリを読んで、`foo.*` にあてはまるすべてのファイルを探し、 事実上それらのファイルをマップするタイプマップを作ります。 そのとき、メディアタイプとコンテントエンコーディングは、 そのファイル名を直接指定したときと同じものが割り当てられます。 それからクライアントの要求にもっとも合うものを選び、 そのドキュメントを返します。

サーバがディレクトリの索引を作ろうとしていると、 `MultiViews` は `DirectoryIndex` ディレクティブで指定された名前の探索にも適用されます。 設定ファイルに

      DirectoryIndex index

が書かれていて、`index.html` と `index.html3` が両方存在していると、 サーバはその中から毎回どちらかを適当に選びます。 もしその両方が存在せずに `index.cgi` が存在していると、サーバはそれを実行します。

ディレクトリを読んでいるときに見つかったファイルに CGI スクリプトがあった場合は、何が起こるべきかは自明ではありません。 そのような場合はコードは特別な扱いをしています。リクエストが POST か QUERY_ARGS や PATH_INFO のある GET の場合は、スクリプトに非常に高い品質が与えられ、 通常それが起動されます。その他のリクエストのときは、スクリプトには 非常に低い品質が与えられ、他のものが(もしあれば)取得されます。

## ネゴシエーション方法

Apache がタイプマップファイルかディレクトリのファイル名から リソースの variant の一覧を取得したら、「最善」の variant を選ぶために 二つの方法のどちらかを起動します。Apache のコンテントネゴシエーションの機能を使うために、 どのようにネゴシエーションが行なわれるかの詳細を知る必要は ありません。 しかし、これ以降では関心のある人のためにその方法を説明します。

ネゴシエーション方法は二つあります。

1. 通常は Apache のアルゴリズムを用いた Server driven negotiation が使用されます。Apache のアルゴリズムは後で詳細に説明されています。 このアルゴリズムが使用された場合、Apache はより良い結果になるように、特定の次元において品質の値を 「変える」ことができます。Apache が品質の値を変える方法は後で詳細に説明されています。
2. RFC 2295 で定義されている機構を用いてブラウザが特に指定した場合、 transparent content negotiation が使用されます。このネゴシエーション方法では、ブラウザが「最善」の variant の決定を完全に制御することができます。 ですから、結果はブラウザが使用しているアルゴリズムに依存します。 Transparent negotiation の処理の過程で、ブラウザは RFC 2296 で定義されている 'remote variant selection algorithm' を実行するように頼むことができます。

### ネゴシエーションの次元

Dimension | Notes
--- | ---
メディアタイプ | Accept ヘッダフィールドでブラウザにより示される。 それぞれの項目は品質係数を持つことが出来ます。 variant の説明にも品質係数 ("qs" 要素)を持つことができます
言語 | Accept-Language ヘッダフィールドでブラウザにより示される。 それぞれの項目は品質係数を持つことが出来ます。 variants は 0, 1, またはそれ以上の言語と関連づけることができます。
エンコーディング | Accept-Encoding ヘッダフィールドでブラウザにより示される。 それぞれの項目は品質係数を持つことが出来ます。
キャラクタセット | Accept-Charset ヘッダフィールドでブラウザにより示される。 それぞれの項目は品質係数を持つことが出来ます。Variants でもメディアタイプの要素としてキャラクタセットを示すことが出来ます。

### Apache のネゴシエーションアルゴリズム

ブラウザに返す「最適な」variant を（もしあれば）選択するように Apache は次のアルゴリズムを使うことができます。このアルゴリズムは 未だ設定可能なものではありません。次のように動作します:

1. まずはじめに、ネゴシエーションの次元それぞれについて適切な Accept* ヘッダフィールドをチェックして、variant それぞれに品質を割り当てます。もしある次元の Accept* ヘッダでその variant が許容できないと暗示されていれば、それを削除します。 variants が一つも残っていなければ、ステップ 4 に行きます。
2. 消去法で「最適な」 variant を選びます。 次のテストが順番に適応されます。テストで選択されなかった variant は削除されていきます。テスト後 variant が唯一残っていれば、それを最適なものとしてステップ 3 に進みます。 variant が複数残っていれば、次のテストに進みます。
   1. variant のメディアタイプの品質数値と Accept ヘッダの品質数値との積を計算して、最高値の variant を選びます。
   2. 言語品質数値が最高の variant を選びます。
   3. (もしあれば) Accept-Language ヘッダの言語順か、 (もしあれば) `LanguagePriority` ディレクティブの言語順で最適な言語の variant を選びます。
   4. 最高「レベル」のメディアパラメータ (text/html メディアタイプのバージョンを与えるために使われます)を持つ variant を選びます。
   5. Accept-Charset ヘッダ行で与えられている最高の文字セット メディアパラメータを持つ variant を選びます。明示的に除外 されていない限り、ISO-8859-1 が許容されるようになっています。 `text/*` メディアタイプであるけれども 特定の文字セットに明示的に関連づけられているわけではない variant は ISO-8859-1 であると仮定されます。
   6. ISO-8859-1 ではない文字セットメディアパラメータと 関連づけられている variant を選びます。そのような variant がない場合は、代わりにすべての variant を選びます。
   7. 最適なエンコーディングの variant を選びます。もし user-agent が許容するエンコーディングがあれば、その variant のみを選びます。 そうではなく、もしエンコードされたものとそうでない variant が混ざって存在していたらエンコードされていない variant のみを選びます。 variant がすべてエンコードされているか variant がどれもエンコードされていないという場合は、すべての variant を選びます。
   8. 内容の最も短い variant を選びます。
   9. 残っている variant の最初のものを選びます。 タイプマップファイルの最初にリストされているか、 variant がディレクトリから最初に読み込まれる時に ASCII 順でソートしてファイル名が先頭になったか、のどちらかです。
3. アルゴリズムを使って一つの「最適な」variant を選びましたので、それを応答として返します。 ネゴシエーションの次元を指定するために HTTP レスポンスヘッダ Vary が設定されます (リソースのキャッシュをする時に、 ブラウザやキャッシュはこの情報を使うことができます)。 以上で終わり。

4. ここに来たということは、variant が一つも選択されなかった (ブラウザが許容するものがなかったため) ということです。406 ステータス ("No Acceptable representation" を意味する) が、利用可能な variant のリストのついた HTML ドキュメントとともに返されます。 変化の次元を示す HTTP Vary ヘッダも設定されます。

   Apacheにより返されるエラーメッセージが必要以上に簡明で、 (同等の内容を提示しているけれども、) ユーザが混乱する原因になることに留意すべきです。 もしユーザがこのエラーページを見ることを避けたい場合は、 標準の言語の(また、標準のエンコーディング等を添えた) 文書を置いておくことで、もしブラウザから要求された言語や エンコーディング等が使えなかった場合は常にそれが返されます。

   ブラウザから要求された言語の文書がどれも得られない時に デフォルト言語の文書を返したい場合は、 言語属性セットなしの文書を作成してください。詳細は後の Variants with no Language 参照。

## 品質の値を変える

上記の Apache ネゴシエーションアルゴリズムの厳格な解釈で得られるであろう値から、 Apache は品質数値を時々変えます。完全ではない、 あるいは正確でない情報を送るブラウザ向けのアルゴリズムで、 よりよい結果を得るために行われます。かなりポピュラーなブラウザで、 もしないと間違った variant を選択する結果になってしまうような Accept ヘッダ情報を送るものもあります。 ブラウザが完全で正しい情報を送っていれば、 この数値変化は適用されません。

### メディアタイプとワイルドカード

Accept: リクエストヘッダはメディアタイプの優先傾向を指定します。 これはまた、"image/*" や "*/*" といった「ワイルドカード」メディアタイプを含むことができます。 ここで * は任意の文字列にマッチします。ですから、次の:

      Accept: image/*, */*

を含むリクエストは、"image/" ではじまるタイプすべてが許容できる、 そして他のどんなタイプも許容できる (この場合はじめの "image/*" は冗長になります) ことを示します。 扱うことのできる明示的なタイプに加えて、 ルーチンのようにワイルドカードを送るブラウザもあります。たとえば:

      Accept: text/html, text/plain, image/gif, image/jpeg, */*

こうすることの狙いは、明示的にリストしているタイプが優先されるけれども、 異なる表現が利用可能であればそれでも良い、ということです。 しかしながら基本的なアルゴリズムでは、上に示したように、*/* ワイルドカードが他のすべてのタイプと全く同等なので優先されません。 ブラウザは */* にもっと低い品質 (優先) 値を付けてリクエストを送るべきなのです。たとえば:

      Accept: text/html, text/plain, image/gif, image/jpeg, */*; q=0.01

明示的なタイプには品質数値が付けられていませんので、デフォルトの 1.0 (最高値) の優先になります。ワイルドカード */* は低い優先度 0.01 を与えられているので、明示的にリストされているタイプに合致する variant がない場合にのみ、他のタイプが返されます。

もし Accept: ヘッダが q 値を全く含んでいなければ、 望みの挙動をするために、Apache は "*/*" があれば 0.01 の q 値を設定します。また、"type/*" の形のワイルドカードには 0.02 の q 値を設定します (ですからこれらは "*/*" のマッチよりも優先されます)。 もし Accept: ヘッダ中のメディアタイプのどれかが q 値を含んでいれば、 これらの特殊な値は適用されず、正しい情報を送るブラウザからの リクエストは期待通りに動作しはじめます。

### 言語属性のない variant

もし特定のリソースの variant のうちのいくつかが言語属性を持っていて いくつかは持っていない場合は、それらの言語属性を持たない variant には 0.001 という非常に低い品質係数が与えられます

この言語属性を持たない variant に 0.001 という非常に低い品質係数が与えられるという設定の理由は ブラウザの言語設定に合う variant が一つもなかったときに標準の variant を適用することを可能にするためです。これにより、 リクエストされたリソースに対して、用意していない言語だけを accept するように設定されたブラウザのユーザが "406" エラーページを見るのを避けることが可能になります。

たとえば Multiviews が有効で variants が三つある状況を考えます。

- foo.en.html, language en
- foo.fr.html, language fr
- foo.html, no language

言語属性のない variant の意味は常にブラウザに適応することです。 リクエストが `foo` で Accept-Language ヘッダに en または fr (またはその両方) が含まれる場合、foo.en.html か foo.fr.html のどちらかが返されます。ブラウザが許容するものとして en と fr のどちらも挙げられていない場合は代わりに foo.html が返されます。クライアントが `foo.html` をリクエストした場合は完全に一致するのでネゴシエーションは発生せず、 それ自体が返されます。この問題を避けるには、「言語のない」 variant として `foo.html.html` という名前を付けることが Multiviews そして言語ネゴシエーションが動作するための保険として 役立つことが時々あります。

## Transparent Content Negotiation の拡張

Apache は transparent content negotiation プロトコル (RFC 2295) を次のように拡張しています。特定のコンテントエンコーディング のみが利用可能である variant に印を付けるために、新たに `{encoding ..}` 要素を variant リスト中に使うことができます。 リスト中のエンコードされた variant を認識できて、Accept-Encoding リクエストヘッダに従って許容されるエンコードをもった variant は、どれでも候補 variant として使用できるようにするために、 RVSA/1.0 アルゴリズム (RFC 2296) の実装が拡張されました。 RVSA/1.0 の実装では、最適な variant が見つかるまで、 計算した品質数値は小数点以下 5 桁まで丸めません。

## リンクと名前の変換に関する注意点

言語ネゴシエーションを使っている場合は、ファイルが一つ以上の拡張子を持てて、 拡張子の順番には関連性が通常はない (詳細は mod_mime を参照) ので、幾つかの異なる名前の変換を選べることになります。

典型的なファイルでは、MIME タイプ拡張子 (たとえば `html`) を持っていて、エンコーディング拡張子 (たとえば `gz`) を持っているかもしれないし、 このファイルに異なる言語 variant を用意していれば、もちろん言語拡張子 (たとえば `en`) を持っているでしょう。

例:

- foo.en.html
- foo.html.en
- foo.en.html.gz

正しいリンク、不正なリンクの両方のファイル名の例を挙げます:

ファイル名 | 正解のリンク | 不正解のリンク
--- | --- | ---
foo.html.en | foo foo.html | -
foo.en.html | foo | foo.html
foo.html.en.gz | foo foo.html | foo.gz foo.html.gz
foo.en.html.gz | foo | foo.html foo.html.gz foo.gz
foo.gz.html.en | foo foo.gz foo.gz.html | foo.html
foo.html.gz.en | foo foo.html foo.html.gz | foo.gz

上の表を見て、拡張子なしのリンク (たとえば `foo`) がいつでも使えることに気が付くでしょう。この利点は、 ドキュメントとして応答するファイルの実際のファイルタイプを隠蔽して、 リンクの参照を変更することなく後からファイルを変更できる、 たとえば `html` から `shtml` に、あるいは `cgi` に変更できる点です。

リンクに MIME タイプを使い続けたい (たとえば `foo.html`)時は、言語拡張子は (エンコーディング拡張子もあればそれも含めて) MIME タイプ拡張子の右側になければなりません (たとえば `foo.html.en`)。

## キャッシュに関する注意事項

キャッシュがある表現を保存しているときは、リクエスト URL と関連づけられています。次にその URL がリクエストされた時に、 キャッシュは保存されている表現を使用できます。しかし、リソースが サーバでネゴシエーション可能であれば、最初のリクエストでキャッシュされて続く キャッシュヒットでは間違った応答を返してしまうということになりかねません。 これを防ぐために、Apache はコンテントネゴシエーションの 後に返された応答すべてに、HTTP/1.0 クライアントでの non-cacheable とマークします。また、ネゴシエーションされた応答のキャッシュ を可能にする HTTP/1.1 プロトコルの機能も Apache はサポートします。

HTTP/1.0 準拠のクライアントからのリクエストに対しては、 (ブラウザであろうとキャッシュであろうと) ネゴシエーション を受けた応答のキャッシュを許すために、`CacheNegotiatedDocs` ディレクティブを使用できます。このディレクティブは、 主サーバ設定やバーチャルホストで与えられて、引数をとりません。 HTTP/1.1 クライアントからのリクエストには効力を持ちません。

### Apache HTTP Server