Studying HTTP

Help

Content Negotiation

内容ネゴシエーションとは何か

RFC 2616 の section 12 では、内容ネゴシエーション (コンテントネゴシエーション) というメカニズムについて記述されています。

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

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

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

内容ネゴシエーションとは、文字通りクライアントとサーバがそのリソースの「内容」について「交渉」する事で、クライアントが最も利用しやすいリソースを得られる様にするための仕組みです。 この内容ネゴシエーションの利点、及びその具体的な例については、Transparent Content Negotiation にて列挙されています。

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

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

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

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

一つのリソースについて利用可能な複数の表現を持つ事や、以降のリクエストのそれぞれに最も適した表現を返す事は、HTTP では常に可能である。 しかし、HTTP/1.1 はキャッシュフレンドリな方法でこれを行うための規定がある HTTP の最初のバージョンである。 この規定には、Vary レスポンスヘッダ、エンティティタグ、If-None-Match リクエストヘッダを含む。

例えば「日本語版」や「英語版」等のリソースの言語が異なる場合、あるいは HTML ファイルやプレーンテキスト、更にそれに gzip による圧縮等が施されている等のメディアタイプが異なる場合、これらのファイルのそれぞれはバリアント{variant} と呼ばれます。 内容ネゴシエーションとは、1 つの URL に配置された複数のバリアントをサーバやユーザエージェントが自動に選択する事で、ユーザによる入力を最小にしつつ、ユーザが最も望むリソースを返すための仕組みです。

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

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

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

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

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

ここで、実際にサーバ駆動型ネゴシエーションが行われる場合の動作について見てみましょう。 クライアントが http://www.studyinghttp.net/example に以下のようなリクエストを送ると仮定します。

 GET /example HTTP/1.1
 Host: www.studyinghttp.net
 User-Agent: H_HttpClient/20040419 H_HTTP.pm/1.11
 Accept: text/html, text/plain;q=0.8
 Accept-Charset: EUC-JP, *
 Accept-Language: ja, en;q=0.5

サーバ駆動型ネゴシエーションで使用するヘッダは主に Accept- ヘッダです。 今回の例では、Accept, Accept-Charset, Accept-Language を使用します。

このリクエストを受け取るサーバが、http://www.studyinghttp.net/example で利用できる「表現」として、以下のリソースを持っているとします。

メディアタイプ
text/html
text/plain
application/pdf
文字セット
ISO-2022-JP
Shift_JIS
EUC-JP
言語
日本語
英語
中国語

この時、サーバは品質値に基づいてサーバ駆動型ネゴシエーションを実行し、その結果以下のようなレスポンスが得られるでしょう。

 HTTP/1.1 200 OK
 Server:Apache/1.3.12
 Date: Fri, 23 Mar 2001 00:00:00 GMT
 Vary: Accept, Accept-Charset, Accept-Language
 Content-Type: text/html; charset=EUC-JP
 Content-Language: ja
 Content-Length:1234

 … 以下略 …

Vary ヘッダには、サーバ駆動型ネゴシエーションに使用したリクエストヘッダが入ります。 詳しくは、section 14.44を、見て下さい。

クライアント駆動型ネゴシエーション

ところで section 12.1 には続きがあります。それをご覧下さい。

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

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

サーバ駆動型ネゴシエーションだけでは、最適な「表現」を選択する事は難しい場合があります。 そこで、クライアントにもネゴシエーションを行わせようと考えられ、これをクライアント駆動型ネゴシエーションと言い呼び、section 12.2 にて記述されています。

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

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

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

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

例えば先の例で、もしサーバが文字セットが EUC-JP であるリソースを持っていなかった場合、以下のようなレスポンスを得られるかもしれません。

 HTTP/1.1 300 Muliple Choices
 Server:Apache/1.3.12
 Date: Fri, 23 Mar 2001 00:00:00 GMT
 Vary: Accept, Accept-Charset, Accept-Language
 Content-Type: text/html; charset=ISO-2022-JP
 Content-Language: ja
 Content-Length:999

 要求された URI のリソースは、次の中から選択できます。
 ・<a href="example.jis">ISO-2022-JP<a>
 ・<a href="example.sjis">Shift_JIS<a>

 … 以下略 …

返すべきリソースが無いとサーバが判断した場合に、サーバは現在所有する "表現" のリストを提供できます。 ここでサーバは、リクエストを果たすために、そのリクエストを有効であるとして、そこからクライアントに更なる動作を求める場合には 300 (Muliple Choices) を、またそのリクエストは無効であるとして、クライアントにリクエストの再実行を求める場合には 406 (Not Acceptable) ステータスコードをそれぞれ返します。

このようなユーザに手動入力を求める画面で、ユーザエージェントが自動的に処理する事がクライアント駆動型ネゴシエーションなのです。

ネゴシエーションに JavaScript を使う

Web サイトがクライアント駆動型ネゴシエーションを実現させる方法の一つとして JavaScript があり、実際広く使用されています。 その中でもよく使われるものに、OS やブラウザの種類、そのバージョン等を判別するスクリプトがあります。 mozilla.org 内の Determining Browser Type and Version with JavaScript では、"Ultimate client-side JavaScript client sniff" というブラウザバージョン探知コードが載せられています。

 <!--
 // 究極のクライアントサイド JavaScript クライアント探知. Version 3.03
 // (C) Netscape Communications 1999-2001.  再利用及び配付を認める
 // 99 年  5 月 17 日改訂:is_nav5up と is_ie5up を追加 (下記参照)。
 // 00 年 12 月 20 日改訂:is_gacko の追加と is_nav5up を is_nav6up へ変更
 //                        また IE5.5 Opera4&5 HotJava3 AOLTV も追加サポート
 // 01 年  2 月 22 日改訂:IE 5.x, Opera4 のための Javascript 探知の修正
 //                        Opera5 探知の修正
 //                        winME と win2k の追加サポート
 //                        browser-type-oo.js との同調{synch}
 // 01 年  3 月 26 日改訂:Opera 探知の修正
 // 01 年 10 月 02 日改訂:IE6 探知の追加

 // あなたの JavaScript クライアントについて知りたいと思っているが、尋
 // ねる事を恐れる事の全てである。以下の "is_" 変数を生成する。
 // (1) ブラウザのベンダ:
 //     is_nav, is_ie, is_opera, is_hotjava, is_webtv, is_TVNavigator, is_AOLTV
 // (2) ブラウザのバージョン番号:
 //     is_major (メジャーバージョン番号を示す整数値: 2, 3, 4 ...)
 //     is_minor (完全なるバージョン番号を示す小数値: 2.02, 3.01, 4.04 ...)
 // (3) ブラウザのベンダとメジャーバージョン番号
 //     is_nav2, is_nav3, is_nav4, is_nav4up, is_nav6, is_nav6up, is_gecko, is_ie3,
 //     is_ie4, is_ie4up, is_ie5, is_ie5up, is_ie5_5, is_ie5_5up, is_ie6, is_ie6up, is_hotjava3, is_hotjava3up,
 //     is_opera2, is_opera3, is_opera4, is_opera5, is_opera5up
 // (4) JavaScript バージョン番号:
 //     is_js (完全なる JavaScript バージョン番号を示す小数値: 1, 1.1, 1.2 ...)
 // (5) OS プラットフォームとバージョン:
 //     is_win, is_win16, is_win32, is_win31, is_win95, is_winnt, is_win98, is_winme, is_win2k
 //     is_os2
 //     is_mac, is_mac68k, is_macppc
 //     is_unix
 //     is_sun, is_sun4, is_sun5, is_suni86
 //     is_irix, is_irix5, is_irix6
 //     is_hpux, is_hpux9, is_hpux10
 //     is_aix, is_aix1, is_aix2, is_aix3, is_aix4
 //     is_linux, is_sco, is_unixware, is_mpras, is_reliant
 //     is_dec, is_sinix, is_freebsd, is_bsd
 //     is_vms
 //
 // userAgent 文字列の詳細なリストについては
 // http://www.it97.de/JavaScript/JS_tutorial/bstat/navobj.html と
 // http://www.it97.de/JavaScript/JS_tutorial/bstat/Browseraol.html を
 // 参照。
 // 
 // 注: あなたは新しいバージョンのブラウザがリリースされた時、その働
 // きを "消し" たり、止めたりするための Nav4 や IE4 のコード
 // を望まないだろうから、あなたが新しいバージョン上で働かせたいコード
 // の中でバージョンをチェックするための条件分岐中では、is_ie5 や
 // is_opera5 の代わりにis_ie5up ("IE5.0 以降") や is_opera5up
 // ("Opera5.0 以降")を使うべきである。

    // 探査を簡単にするため全ての文字を小文字に置換する
    var agt=navigator.userAgent.toLowerCase();

    // *** ブラウザのバージョン ***
    // 注: IE5 は 4 を返すので、IE5 の判別のためには is_ie5up を使用せよ。
    var is_major = parseInt(navigator.appVersion);
    var is_minor = parseFloat(navigator.appVersion);

    // 注: Opera や WebTV は Navigator を名乗る。我々は厳密なクライアント判
    // 別を行うが、もしあなたがこの偽称を許すのであれば、opera や webtv のた
    // めの判別部分は取り除け。
    var is_nav  = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1)
                && (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1)
                && (agt.indexOf('webtv')==-1) && (agt.indexOf('hotjava')==-1));
    var is_nav2 = (is_nav && (is_major == 2));
    var is_nav3 = (is_nav && (is_major == 3));
    var is_nav4 = (is_nav && (is_major == 4));
    var is_nav4up = (is_nav && (is_major >= 4));
    var is_navonly      = (is_nav && ((agt.indexOf(";nav") != -1) ||
                          (agt.indexOf("; nav") != -1)) );
    var is_nav6 = (is_nav && (is_major == 5));
    var is_nav6up = (is_nav && (is_major >= 5));
    var is_gecko = (agt.indexOf('gecko') != -1);


    var is_ie     = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
    var is_ie3    = (is_ie && (is_major < 4));
    var is_ie4    = (is_ie && (is_major == 4) && (agt.indexOf("msie 4")!=-1) );
    var is_ie4up  = (is_ie && (is_major >= 4));
    var is_ie5    = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.0")!=-1) );
    var is_ie5_5  = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.5") !=-1));
    var is_ie5up  = (is_ie && !is_ie3 && !is_ie4);
    var is_ie5_5up =(is_ie && !is_ie3 && !is_ie4 && !is_ie5);
    var is_ie6    = (is_ie && (is_major == 4) && (agt.indexOf("msie 6.")!=-1) );
    var is_ie6up  = (is_ie && !is_ie3 && !is_ie4 && !is_ie5 && !is_ie5_5);

    // 既知のバグ: AOL4 上では、IE3 が埋めこまれたブラウザや、これが
    // 開かれた最初のブラウザウィンドウの場合、false を返す。従って、
    // is_aol, is_aol3, is_aol4 は 100% 信頼できるものではない。
    var is_aol   = (agt.indexOf("aol") != -1);
    var is_aol3  = (is_aol && is_ie3);
    var is_aol4  = (is_aol && is_ie4);
    var is_aol5  = (agt.indexOf("aol 5") != -1);
    var is_aol6  = (agt.indexOf("aol 6") != -1);

    var is_opera = (agt.indexOf("opera") != -1);
    var is_opera2 = (agt.indexOf("opera 2") != -1 || agt.indexOf("opera/2") != -1);
    var is_opera3 = (agt.indexOf("opera 3") != -1 || agt.indexOf("opera/3") != -1);
    var is_opera4 = (agt.indexOf("opera 4") != -1 || agt.indexOf("opera/4") != -1);
    var is_opera5 = (agt.indexOf("opera 5") != -1 || agt.indexOf("opera/5") != -1);
    var is_opera5up = (is_opera && !is_opera2 && !is_opera3 && !is_opera4);

    var is_webtv = (agt.indexOf("webtv") != -1);

    var is_TVNavigator = ((agt.indexOf("navio") != -1) || (agt.indexOf("navio_aoltv") != -1));
    var is_AOLTV = is_TVNavigator;

    var is_hotjava = (agt.indexOf("hotjava") != -1);
    var is_hotjava3 = (is_hotjava && (is_major == 3));
    var is_hotjava3up = (is_hotjava && (is_major >= 3));

    // *** JAVASCRIPT バージョンチェック ***
    var is_js;
    if (is_nav2 || is_ie3) is_js = 1.0;
    else if (is_nav3) is_js = 1.1;
    else if (is_opera5up) is_js = 1.3;
    else if (is_opera) is_js = 1.1;
    else if ((is_nav4 && (is_minor <= 4.05)) || is_ie4) is_js = 1.2;
    else if ((is_nav4 && (is_minor > 4.05)) || is_ie5) is_js = 1.3;
    else if (is_hotjava3up) is_js = 1.4;
    else if (is_nav6 || is_gecko) is_js = 1.5;
    // 注: 将来において、新しいバージョンの JS がリリースされた時にはこ
    // のコードを更新せよ。現在のために、将来のバージョンの Nav や IE が
    // *少なくとも* JS 1.x は利用可能であろう事を示すためにいくつかの上方
    // 互換性の提供を試みる。> や >= を使って JS バージョンの互換性
    // を常にチェックせよ。
    else if (is_nav6up) is_js = 1.5;
    // 注: mac 上の ie5up は 1.4 である
    else if (is_ie5up) is_js = 1.3

    // HACK: 他ブラウザにはこの概念が無い; 故に > や >= を使って JS バージョンを
    // 常にチェックせよ。
    else is_js = 0.0;

    // *** プラットフォーム ***
    var is_win   = ( (agt.indexOf("win")!=-1) || (agt.indexOf("16bit")!=-1) );
    // 注: Opera 3.0 上では、userAgent 文字列はすべての Win32 上で
    //        "Windows 95/NT4" を含むので、Win95 と WinNT の区別はできない。
    var is_win95 = ((agt.indexOf("win95")!=-1) || (agt.indexOf("windows 95")!=-1));

    // 16 bit でコンパイルされたバージョンか?
    var is_win16 = ((agt.indexOf("win16")!=-1) ||
               (agt.indexOf("16bit")!=-1) || (agt.indexOf("windows 3.1")!=-1) ||
               (agt.indexOf("windows 16-bit")!=-1) );

    var is_win31 = ((agt.indexOf("windows 3.1")!=-1) || (agt.indexOf("win16")!=-1) ||
                    (agt.indexOf("windows 16-bit")!=-1));

    var is_winme = ((agt.indexOf("win 9x 4.90")!=-1));
    var is_win2k = ((agt.indexOf("windows nt 5.0")!=-1));

    // 注: Win98 は確実には判別できないであろう。それは以下にて現れる:
    //       - Nav 4.x 以下では userAgent 中には単なる "Windows" を得るだろう。
    //       - Mercury クライアント上では、32-bit バージョンでは "Win98" を返す
    //         が、Win98 上で動く 16-bit バージョンでは依然 "Win95" を返すであろう。
    var is_win98 = ((agt.indexOf("win98")!=-1) || (agt.indexOf("windows 98")!=-1));
    var is_winnt = ((agt.indexOf("winnt")!=-1) || (agt.indexOf("windows nt")!=-1));
    var is_win32 = (is_win95 || is_winnt || is_win98 ||
                    ((is_major >= 4) && (navigator.platform == "Win32")) ||
                    (agt.indexOf("win32")!=-1) || (agt.indexOf("32bit")!=-1));

    var is_os2   = ((agt.indexOf("os/2")!=-1) ||
                    (navigator.appVersion.indexOf("OS/2")!=-1) ||
                    (agt.indexOf("ibm-webexplorer")!=-1));

    var is_mac    = (agt.indexOf("mac")!=-1);
    // mac の ie5 js version を調べる
    if (is_mac && is_ie5up) is_js = 1.4;
    var is_mac68k = (is_mac && ((agt.indexOf("68k")!=-1) ||
                               (agt.indexOf("68000")!=-1)));
    var is_macppc = (is_mac && ((agt.indexOf("ppc")!=-1) ||
                                (agt.indexOf("powerpc")!=-1)));

    var is_sun   = (agt.indexOf("sunos")!=-1);
    var is_sun4  = (agt.indexOf("sunos 4")!=-1);
    var is_sun5  = (agt.indexOf("sunos 5")!=-1);
    var is_suni86= (is_sun && (agt.indexOf("i86")!=-1));
    var is_irix  = (agt.indexOf("irix") !=-1);    // SGI
    var is_irix5 = (agt.indexOf("irix 5") !=-1);
    var is_irix6 = ((agt.indexOf("irix 6") !=-1) || (agt.indexOf("irix6") !=-1));
    var is_hpux  = (agt.indexOf("hp-ux")!=-1);
    var is_hpux9 = (is_hpux && (agt.indexOf("09.")!=-1));
    var is_hpux10= (is_hpux && (agt.indexOf("10.")!=-1));
    var is_aix   = (agt.indexOf("aix") !=-1);      // IBM
    var is_aix1  = (agt.indexOf("aix 1") !=-1);
    var is_aix2  = (agt.indexOf("aix 2") !=-1);
    var is_aix3  = (agt.indexOf("aix 3") !=-1);
    var is_aix4  = (agt.indexOf("aix 4") !=-1);
    var is_linux = (agt.indexOf("inux")!=-1);
    var is_sco   = (agt.indexOf("sco")!=-1) || (agt.indexOf("unix_sv")!=-1);
    var is_unixware = (agt.indexOf("unix_system_v")!=-1); 
    var is_mpras    = (agt.indexOf("ncr")!=-1);
    var is_reliant  = (agt.indexOf("reliantunix")!=-1);
    var is_dec   = ((agt.indexOf("dec")!=-1) || (agt.indexOf("osf1")!=-1) ||
           (agt.indexOf("dec_alpha")!=-1) || (agt.indexOf("alphaserver")!=-1) ||
           (agt.indexOf("ultrix")!=-1) || (agt.indexOf("alphastation")!=-1));
    var is_sinix = (agt.indexOf("sinix")!=-1);
    var is_freebsd = (agt.indexOf("freebsd")!=-1);
    var is_bsd = (agt.indexOf("bsd")!=-1);
    var is_unix  = ((agt.indexOf("x11")!=-1) || is_sun || is_irix || is_hpux ||
                 is_sco ||is_unixware || is_mpras || is_reliant ||
                 is_dec || is_sinix || is_aix || is_linux || is_bsd || is_freebsd);

    var is_vms   = ((agt.indexOf("vax")!=-1) || (agt.indexOf("openvms")!=-1));

//--> end hide JavaScript

このようなコードを用いる事によって、ユーザに自分の環境を入力させる手間を省く事ができ、その処理を自動化する事でユーザの負担を軽減する事ができ、かつブラウザに応じた最適のリソースが提供できるようになります。

但し、ネゴシエーションに JavaScript を用いる場合は、入力数を減らす事ができる等、利用者の簡便のために用いるべきであり、すなわち、ブラウザが JavaScript に対応していない、あるいは対応していてもそれが有効になっていない設定になっている場合にも配慮し、JavaScript を使わない同等の代替手段も提供しなければいけません。

参照文献


Copyright © 1999-2005 H-Hash, All Rights Reserved.