DOMによるWeb Application Clientの可能性

Fetchって、本当に使えるんですか?

XMLHttpRequestの代替としてPromiseを使って実装されたFetch APIですが、本当に使えるのでしょうか?

POSTでの通信やリダイレクトへの対応、中断やタイムアウトも必要です。フォームデータの送信や、検索パラメータの編集/解析も簡単な方が良いですね。

調べてみましょう。

検索パラメータ付きのURLでGET

これはFetchに限ったことではありませんが、検索パラメータを文字列編集で作成するのはめんどくさいですね。日本語の場合、URLエンコードも必要になりますし。

URLSearchParamsというAPIが用意されています。

上記の関数は、次の文字列を返します。


 

URLSearchParamsには、検索パラメータの解析機能もあります。

上記の関数は、次の文字列を返します。


 

POSTでフォームデータを送信

Fetchは、デフォルトではGETメソッドにより送信しますが、当然他のメソッドを使うケースも多いですね。

デフォルトの状態から変更したい場合は、その内容をfetch関数第2引数にオプションとして指定します。

このサンプルで、第2引数に設定している内容に関しては、説明するほどのことも無いと思いますが、もちろん他にも多くのオプションがあります。

詳細は、「Fetch の使用 - Web API | MDN」をご覧ください。

呼び出し先で、POSTされたパラメータを一覧すると、下記の様になります(PHPによる例)。

Array
(
    [param1] => 1
    [param2] => abcDEF
    [param3] => 日本語
)

リダイレクトの制御

「デフォルトだと、勝手にリダイレクトされる」という例

デフォルトでは、内部でリダイレクトを行い、最終結果が返されます。

下記の例では、status_301.phpがステータスコード301status_200.phpにリダイレクトし、status_200.php"OK"というテキストを返します。

リダイレクトが発生するURLにリクエストするJavascript

リダイレクトを発生させるphp(status_301.php)

リダイレクト先のphp(status_200.php)

「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。尤も、実際にどう動いたかが分かるわけではありませんが(^_^;

結果表示 => 

リダイレクトをエラーとする例

リダイレクトを制御する場合、送信メソッドを変更する時と同様に、オプションを設定します。

下記例では、リダイレクトが指示された場合、エラーとします。

オプションのredirectには、"follow" "error" "manual"が指定できます。

"follow"がデフォルトで、自動的にリダイレクトします。"error"は、ネットワークエラーとなり、"manual"は、アプリケーション側でリダイレクトします。

次のステータスコードを選択して、「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。

200は成功、309は存在しないURLとなります。

200300301302303304305306307308309

結果表示 => 

FirefoxとChromiumでは、エラーとなるステータスコードが若干異なるようです。

マニュアルでリダイレクトする例

マニュアルでリダイレクトさせる場合、以下のようになります。

と書くと、まるでリダイレクトが制御できるかのようですが、XSSの対策として特殊なタイプのResponseが返ります。このResponseは、ステータスコードやヘッダの内容などには一切アクセスできないので、リダイレクトが発生するステータスコードが返ったことが分かるだけです。

リダイレクトが発生するステータスコードが返ったかどうかの判定は、fetchが返したレスポンスのタイプを見ます。

タイプが'opaqueredirect'のレスポンスは、MDNに下記記述があるとおり、基本的には全ての情報にアクセスできません。

opaqueredirect: The fetch request was made with redirect: "manual". The Response's status is 0, headers are empty, body is null and trailer is empty.

「Response.type - Web API | MDN」より

次のステータスコードを選択して、「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。

200300301302303304305306307308309

結果表示 => 

ところで、308は?

ステータスコード308によるリダイレクトは、仕様が比較的最近(と言っても2015年)に追加されたので、ネット上の解説サイトでも取り扱っていない場合がたまにあります。

レスポンステキストは「308 Permanent Redirect」なので、従来からある「301 Moved Permanently」と同じ様な意味合いですが、POSTでリクエストされた場合はPOSTでリダイレクトされなければならないところが、異なります(POSTに限った訳ではなく、リクエストと同じメソッドでリダイレクトしなければならないという意味です)。

リダイレクトが発生するURLにリクエストするJavascript

リダイレクトを発生させるphp(status_308.php)

リダイレクト先のphp(status_200_post.php)

結果表示 => 

POSTでリクエストしたときは、ちゃんとPOSTでリダイレクトされて、フォームデータもリダイレクト時に最初のレスポンス時と同じものが送信されるようです。まあ、当たり前ですね。

中断とタイムアウト

当初、Fetch APIにはタイムアウトや中断(アボート)の機能が存在しなかったので、XMLHttpRequestからダウングレードした等と言われていましたが、最近になってAbortControllerとAbortSignalというインターフェイスがAPIとして追加になりました。Fetch APIの一部という形ではなく、DOMの汎用的な機能として追加されたようです。

中断(アボート)をさせる例

以下の例では、status_200_wait.phpは内部で5秒間スリープしてから'OK'という文字列を返します。

スリープしている間にsample8_stopを呼び出すと、fetchが例外を投げて中断します。

Fetchを中断させる準備としては、AbortControllerのインスタンスを作成し、そこからAbortSignalのインスタンスであるsignalを取得して、fetchにオプションとして渡します。

実際に中断させるためには、AbortControllerabortメソッドを呼び出します。

結果表示 => 

AbortSignalは、一旦中断(アボート)するとアボート状態に移行し元に戻すことができないので、Fetchを実行する度に作成する必要があります。

また、複数のfetchの呼び出しに同じAbortSignalを渡すことで、複数の通信を同時に中断させることもできます。

アボートのAPIが追加される以前にも、様々な方法で中断する実装がなされてきましたが、AbortSignalを使った中断の利点はネットワークレベル(ソケットのレベル)で正しく切断されることでしょう。

abortメソッドが呼ばれた時点でFINパケットがクライアントから飛ぶので、サーバ側で継続していた処理が終了してクライアントにレスポンスを送信すると、Connection reset by peer.のエラーが発生します。

これを検知できれば、サーバ側で既にクライアントが接続を切っていることが分かるので、トランザクションをロールバックして処理が無かったことにもできます。尤も最近のWebアプリケーションで使われるサーバ側の実装では、通信エラーの検知が難しいので、使える場面は少ないかも知れませんが。

タイムアウトをさせる例

これは、タイマを使って中断させるだけなので、簡単です。

結果表示 => 

ご意見・ご質問