2015年3月24日火曜日

PCブラウザでネットワーク接続の有無を検知するには?

PCブラウザでネットワーク接続の有無を検知するにはどうしたらいいだろう?

一応「navigator.onLine」というAPIはある。

window.navigator.onLine - Web API インターフェイス | MDN

ブラウザのサポート具合を調べてみると、全てのブラウザというわけにはいかないが、まあまあサポートされていることがわかる。

Can I use... Support tables for HTML5, CSS3, etc

しかし、このAPIには色々と問題があることがすぐに分かった。

javascript - How to detect online/offline event cross-browser? - Stack Overflow


Chrome and Safari will detect when you go "offline" automatically - meaning that "online" events and properties will fire automatically when you unplug your network cable.

Firefox (Mozilla), Opera, and IE take a different approach, and consider you "online" unless you explicitly pick "Offline Mode" in the browser - even if you don't have a working network connection.


要するに、ChromeやSafariならネットワーク接続が切れた時点で自動的にオフラインイベントが来るが、FirefoxやOpera、IEはブラウザ側に「オフラインモード」という機能があるので、navigator.onLineの値は実際のネットワーク接続に有無ではなく「オフラインモードかどうか」の値になってしまう、という話。

つまり、全くもって役に立たない!

ではどうするか?

StackOverflowでも議論されているが、要するに「サーバーにリクエストを投げて返ってくればオンラインじゃね?」というシンプルな考え方が最も信頼できそうだ。

賢くやりたいなら、こういったライブラリもある。

cvan/navigator.onLine-that-actually-works

愚直にやりたいなら、ここが参考になる。シンプルにXMLHttpRequestを使う。

navigator.onLine alternative: serverReachable() | Louis-Rémi

このコードを参考に、少し改造してこのようにした。

/**
 * 実際にサーバーに接続できるかどうかを返す
 * URL指定が無ければ自ホストの「/? + Math.random()」
 */
function isServerReachable(url) {
  if (!url) {
    url = location.protocol + '//' + location.host + '/?' + Math.random();
  }
  console.log('ネットワーク接続を確認します:' + url);
  try {
    var req = new XMLHttpRequest();
    req.open("GET", url, false); //同期で接続
    req.send();
    if ((200 <= req.status && req.status < 300) || req.status == 304) {
      console.log('ネットワーク接続可能 status=' + req.status + '(' + req.statusText + ')');
      return true;
    } else {
      console.log('ネットワーク接続エラー status=' + req.status + '(' + req.statusText + ')');
      return false;
    }
  } catch (err) {
    console.log('ネットワーク接続エラー');
    return false;
  }
}

req.open()でのメソッドの指定はHEADにした方が高速化のためには好ましいのだろうが、ローカルのデバッグサーバーによっては著しくレスポンスが遅い場合があった。

そこで、サーバー上にゼロバイトのHTMLファイルを作り、HEADではなくGETを指定してそのファイルのURLを指定するようにした。恐らくこの方法は鉄板だと思われる。