JavaScript/XMLHttpRequest

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Wikipedia
ウィキペディアXMLHttpRequestの記事があります。

XMLHttpRequest (XHR) とは、ウェブサーバと通信を行うためのJavaScriptのオブジェクトです。

fetch API[編集]

Ajax/XMLHttpRequestは、古くから使われている技術ですが、すでにAjax/XMLHttpRequestの欠点や問題点を解消した fetch API がモダンブラウザに実装されているので、新しく覚えるあるいは新規にコードを起こす場合は、Ajax/XMLHttpRequestではなく、 fetch APIを使うべきです。

fetch APIを使うと、下記のようなawait async promiseを使って非同期な並行・並列処理をメソッドチェインで簡素に表現できます。

fetch.html
<!DOCTYPE html>
<html>
<head>
    <title>fetch API</title>
    <script>
        fetch('./hello.txt')
            .then(response => response.text())
            .then(data => document.body.innerHTML += `<div>${data}<\/div>`)
    </script>
</head>
<body>
    <h1>XMLHttpRequest</h1>
</body>
</html>

Ajax[編集]

CGIなどの仕組みを使ってサーバからデータを取得するにはウェブページを遷移する必要がありますが、XMLHttpRequestを用いるとページの遷移なしに、動的かつ非同期にデータを取得することができます。このような技術はAjax (asynchronous JavaScript and XML) と呼ばれます。Ajaxを駆使するとJavaScriptを用いてGoogle マップのようなインタラクティブなウェブアプリケーションを構築することができます。

XMLHttpRequestはその名に反してXMLドキュメントでないデータもやり取りすることができます。また、HTTP以外にFTP通信も行うことができます[1](ローカルホストではローカルファイルも取得可能)。ただし、XMLHttpRequestを用いて通信できるのは同じドメインのサーバに限られるため、外部のサーバと通信をすることはできません。これを同一生成元ポリシー(どういつせいせいもとポリシー、same origin policy)といいます。


XMLHttpRequest[編集]

サンプルコードおよび前提の解説[編集]

まず、これらの機能は、webサーバを仲介して行う必要がある。よってコードをテストするには、まずwebサーバ(ApacheやXAMPPなど)を立ち上げて、そこのドキュメントルートで行う必要がある。ドキュメントルートの説明については、『PHP/確実に動作させるまで』に説明しておいたので、webサーバに詳しくなければ参照してもらいたい。(もしwebサーバの使い方が全く分からない場合、JavaScriptの学習はいったん中断して、上記リンク先のPHPの単元を先に学習したほうが良いだろう。)

とりあえず、サーバの設定や構成の準備がうまく行ってるかどうかを、次のコードで試してみよう。下記プログラムは、XMLHttpRequestの動作確認用のプログラムである。(本来なら動作確認のほかに、サーバとのデータ送受信などを行いたいのだが、コードが長くなるので、今回はまだ動作確認だけにしている。)

ただし、アクセス先のテキストファイルとして hello.txt を test.html と同じディレクトリに用意してもらいたい。 今後の作業のため、hello.txt には「Hello world!」と書いておく(単にテキストファイルや画像を読み込むだけなら HTML の通常の機能でも可能だが、今回は動作確認なので、そのような単純な事例にしている)。 なお、実用の場合には サーバのPHPプログラム(rubyなど別言語でも構わない)と、もっと複雑な通信をしたりするだろう。

test.html
<!DOCTYPE html>
<html>
<head>
    <title>XMLHttpRequest</title>
    <script>
        const req = new XMLHttpRequest();
        req.responseType = 'text';

        const ReadyStateStrings = []
        for (const property in XMLHttpRequest) {
            ReadyStateStrings[XMLHttpRequest[property]] = property
        }
        console.log(ReadyStateStrings)
        var start = Date.now()
        for (const event in ["loadstart", "load", "loadend", "error", "abort", "timeout"]) {
            req.addEventListener(event, ev => document.body.innerHTML += `<div>${Date.now() - start}(ms): event: ${event}<\/div>`)
        }
        req.addEventListener("readystatechange", (ev) => {
            document.body.innerHTML += `<div>${Date.now() - start}(ms): status = ${req.status}, readyState = ${ReadyStateStrings[req.readyState]}<\/div>`

            if (req.readyState === 4 && req.status === 200) {
                document.body.innerHTML += `<div>${Date.now() - start}(ms): hello.txt => ${req.responseText}<\/div>`
            }
        });
        req.open('GET', './hello.txt');
        req.send(null);
    </script>
</head>
<body>
    <h1>XMLHttpRequest</h1>
</body>
</html>
実行結果(ブラウザ)
XMLHttpRequest
41(ms): status = 200, readyState = HEADERS_RECEIVED
41(ms): status = 200, readyState = LOADING
43(ms): status = 200, readyState = DONE
43(ms): hello.txt => Hello world!

ミスの原因[編集]

通信に成功せずに表示結果が上記の成功例と異なる場合、なんらかの失敗をしている。

よくあるミス

・ダブルクリック起動してしまっているミス
失敗する場合、原因は色々とありうるが、特にあるミスとして、 http://localhost/test.html のアクセスではなく、htmlファイルを直接ダブルクリックして起動してしまっていないかを注意してみよう。

単にドキュメントルートの中にhtmlを入れて、そのhtmlファイルをダブルクリック起動しても、失敗する。

必ず、ドキュメントルートにhtmlファイルを入れた上で、さらに必ず http://localhost/test.html にアクセスしているかを確認しよう。

・サーバ立ち上げていないミス
また、そもそもwebサーバが立ち上がっていない場合も当然ながら上記コードの動作確認には失敗するので、ローカルホスト自体http://localhostにもアクセスしてみて、確認してみよう。サーバが立ち上がっているなら、webサーバ(XAMPPまたはApacheなど)の用意している画面(ロゴマークなどがある画面)が表示されるハズであるので、その画面が表示されるか確認してみよう。

技術的な解説[編集]

HTTPコマンド[編集]

「GET」とは、HTTPコマンドの一種です。HTTPコマンドには、主にデータ取得を目的とするために通信する「GET」と、データ送信のために通信する「POST」という、2種類があります。

なお、GETもPOSTもどちらとも、相手先サーバとの通信のために、送信をしています。このため、原理的には「GET」で送信をすることも可能です[2]。もっとも、なるべく本来の目的にあったHTTPコマンドを選んで使うほうが良いプログラムではあるでしょう。

また、GETコマンドは送信URLの末尾にデータを付加して

?キー名=値&

の形式でデータ送信できるのですが、しかし日本語などの2バイト文字や「&」や「?」を正しく扱えません。

それらの文字は encodeURIComponent() 関数などでエンコードができます。


readyState プロパティ[編集]

readyState プロパティについては、次表のように仕様が決まっています。

readyState プロパティの意味
戻り値 意味
XMLHttpRequest.UNSENT(数値 0) オブジェクトが構築された。
XMLHttpRequest.OPENED(数値 1) open()メソッドが正常に呼び出されました。この状態では、setRequestHeader()を使ってリクエスト・ヘッダーを設定し、send()メソッドを使ってフェッチを開始することができます。
XMLHttpRequest.HEADERS_RECEIVED(数値 2) すべてのリダイレクト(もしあれば)が行われ、応答のすべてのヘッダーが受信されていること。
XMLHttpRequest.LOADING(数値 3) レスポンスボディの受信中です。
XMLHttpRequest.DONE(数値 4) データの転送が完了した、または転送中に何か問題が発生した(無限リダイレクトなど)。

HTTPステータスコードは、いくつもありますが、今回のコードに関係ありそうな主要なものは下記の通りです。下記の表以外にもいくつもありますが、要するに「200」であれば「成功」ですし、「200」以外なら通常は失敗です。

また、「XMLHttpRequest.DONE」でデータの転送が完了、あるいは転送中に何か問題が発生し(無限リダイレクトなど)異常終了です。


なお、普通の通信なら readyState プロパティは XMLHttpRequest.UNSENT → XMLHttpRequest.OPENED → XMLHttpRequest.HEADERS_RECEIVED → XMLHttpRequest.LOADING → XMLHttpRequest.DONE の順番で変化していきます(サンプルコードの表示結果もこのような順番になっています)。

status プロパティ[編集]

status プロパティの意味
戻り値 意味
200 成功
404 リクエストされたリソースが見つからない (Not Found)
URLなどが間違っているのが原因
500 サーバーのエラー。サーバーダウンの場合もこれに該当。

上述の readyState プロパティおよび status プロパティの説明を合わせて考えれば、readyState 「4」および status 「200」ならば、とりあえず通信の環境構築には成功しています。

send()メソッドなど[編集]

コード末尾のほうにある send() メソッドは、ここでは「通信を開始せよ」程度の意味である。「GET」コマンドでも「POST」コマンドでも、どちらの場合でも、send() メソッドによって通信を開始する。

send() メソッドによって

xhr.onreadystatechange = function(){      }

で指定した関数の内部が実行される。なお onreadystatechange プロパティは「オン・レディ・ステイト・チェンジ」である。


document.write("
" + xhr.responseText + "
");
にある responseText プロパティは、通信で帰って来たテキストを意味するプロパティである。

高度なコード[編集]

イベントリスナーによるイベントハンドリング
const req = new XMLHttpRequest();
req.addEventListener("progress", (ev) => {
  if (ev.lengthComputable) {
    const percent = ev.loaded / ev.total * 100;
    // 進捗表示
    console.log("${percent}%転送しました。")
  }
  else {
    // 長さ不明
    console.log("転送中。")
  }
});
req.addEventListener("loadstart", (ev) => console.log("転送を開始しました。"));
req.addEventListener("load", (ev) => console.log("転送しました。"));
req.addEventListener("loadend", (ev) => console.log("転送が終了しました。"));
req.addEventListener("error", (ev) => console.log("ファイルの転送中にエラーが発生しました。"));
req.addEventListener("abort", (ev) => console.log("ユーザーが転送をキャンセルしました。"));
req.addEventListener("timeout", (ev) => console.log("転送がタイムアウトしました。"));
req.addEventListener("readystatechange", (ev) => {
    switch (req.readyState) {
    case XMLHttpRequest.UNSENT:
        console.log("UNSENT");
        break;
    case XMLHttpRequest.OPENED:
        console.log("OPENED");
        break;
    case XMLHttpRequest.HEADERS_RECEIVED:
        console.log("HEADERS_RECEIVED");
        break;
    case XMLHttpRequest.LOADIND:
        console.log("LOADIND");
        break;
    case XMLHttpRequest.DONE:
        const status = req.status;
        if (status === 0 || (status >= 200 && status < 400)) {
            console.log(req.responseText);
        }
        else {
            console.log(req.responseXML);
            console.log(req.responseText);
        }
        break;
    default :
        console.log(`${req.readyState} は仕様にないXMLHttpRequest.readyState`);
    }
});
req.open('GET', './example.xml', false);
req.send(null);
※ イベントリスナーは open() を呼び出す前に追加する必要があります(もし、そうしないのであれば progress イベントは発火しません)。

資料[編集]

XMLHttpRequestの静的プロパティ[編集]

XMLHttpRequest.UNSENT
0 : number
XMLHttpRequest.OPENED
1 : number
XMLHttpRequest.HEADERS_RECEIVED
2 : number
XMLHttpRequest.LOADING
3 : number
XMLHttpRequest.DONE
4 : number
XMLHttpRequest.arguments
null : object
XMLHttpRequest.caller
null : object
XMLHttpRequest.length
0 : number
XMLHttpRequest.name
"XMLHttpRequest" : string

XMLHttpRequestのインスタンスプロパティ ​[編集]

XMLHttpRequest.prototype.DONE
XMLHttpRequest.prototype.HEADERS_RECEIVED
XMLHttpRequest.prototype.LOADING
XMLHttpRequest.prototype.OPENED
XMLHttpRequest.prototype.UNSENT
XMLHttpRequest.prototype.abort
XMLHttpRequest.prototype.constructor()
XMLHttpRequest.prototype.getAllResponseHeaders
XMLHttpRequest.prototype.getResponseHeader
XMLHttpRequest.prototype.onreadystatechange
XMLHttpRequest.prototype.open
XMLHttpRequest.prototype.overrideMimeType
XMLHttpRequest.prototype.readyState
XMLHttpRequest.prototype.response
XMLHttpRequest.prototype.responseText
XMLHttpRequest.prototype.responseType
XMLHttpRequest.prototype.responseURL
XMLHttpRequest.prototype.responseXML
XMLHttpRequest.prototype.send
XMLHttpRequest.prototype.setRequestHeader
XMLHttpRequest.prototype.status
XMLHttpRequest.prototype.statusText
XMLHttpRequest.prototype.timeout
XMLHttpRequest.prototype.upload
XMLHttpRequest.prototype.withCredentials

脚注[編集]

  1. ^ 2021年6月の時点でディフォルトでFTPスキームを(かつてのgopherのように)無効にするブラウザが増えつつある。https://blog.chromium.org/2020/09/chrome-86-improved-focus-highlighting.html
  2. ^ 山田、P398

外部リンク[編集]

このページ「JavaScript/XMLHttpRequest」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。