Android 2.3 の WebViwe で GET によるクロスドメインリクエストが最初の1回しか成功しない
現象
Unity + WebView 環境において、XHR を使ったクロスドメインリクエストに失敗します。
Async GET なら xhr.readyState = 4 で xhr.status = 0 となり失敗します。
Sync GET なら NETWORK_ERR: XMLHttpRequest Exception 101 でエラーになります。
同様の問題は POST では発生しません。
テストコード
<!DOCTYPE html><html><head><meta charset="utf-8"><title></title> <meta name="viewport" content="width=device-width, user-scalable=no"> <script> var api1 = "http://dev.example.com/api/ping"; // GET var api3 = "http://dev.example.com/api/login"; // POST // --- async ajax --- function async(method, url, data) { function _ajax() { var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { alert(xhr.responseText); } else { alert("FAIL: " + xhr.status); // FAIL } } }; xhr.open(method, url, true); if (method === "POST") { xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } xhr.send(data ? data : null); } _ajax(); } // --- sync ajax --- function sync(method, url, data) { function _ajax() { var xhr = new XMLHttpRequest; xhr.open(method, url, false); if (method === "POST") { xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } xhr.send(data ? data : null); if (xhr.status === 200) { alert(xhr.responseText); } else { alert("FAIL: " + xhr.status); // FAIL } } _ajax(); } </script> </head><body> <button onclick="async('GET', api1, null)">call async GET api</button> <button onclick=" sync('GET', api1, null)">call sync GET api</button> <button onclick="async('POST', api3, 'token=')">call async POST api</button> <button onclick=" sync('POST', api3, 'token=')">call sync POST api</button> </body></html>
Android 2.3, GET + Sync リクエストで "NETWORK_ERR: XMLHttpRequest Exception 101" が発生(WebViewと標準ブラウザの両方で発生)
Android 2.3, Get + Async リクエストの2回目で xhr.status = 0 が発生
発生条件
- Android 2.3.4 端末。Android 4.0.4 端末では発生せず
- Unity + WebView 環境。Titanium や PhoneGap 環境では未確認
- サーバ側でレスポンスヘッダに Access-Control-Allow-Origin: * を返すように設定ずみ
なお、 xhr.withCredentials = true なども試行しましたが効果はありませんでした。
http://caniuse.com/#feat=xhr2 によると、Android 2.3 ブラウザはそもそも XMLHttpRequest Lv2 の CrossDomainRequest をサポートしていないと…
対策
POST は動作するため、GET を JSONP で代用します。
JSONP リクエストを受け付けない場合は、以下のように API を追加し回避します。
現在の API が GET http://dev.example.com/api/hogehoge なら
JSONP用のAPI は http://dev.example.com/api/hogehoge.json?callback=MyFunctionName のようにします(Twitter APIを参考に)。
URLの拡張子が json で callback=MyFunctionName があったら、戻り値を
function MyFunctionName() { return ...; }
の形で返してもらうようにします。
MyFunctionName の部分は毎回ユニークな名前になります。
条件マトリクス
ブラウザと GET/POST, Async/Sync の各条件で、クロスドメインリクエストが可能かどうかのマトリクスです。
OS Version | ブラウザ | Async/Sync | METHOD | クロスドメインリクエスト |
Android 2.3.4 | 標準ブラウザ | Async | GET | ○ |
Android 2.3.4 | 標準ブラウザ | Sync | GET | × *1 |
Android 2.3.4 | 標準ブラウザ | Async/Sync | POST | ○ |
Android 2.3.4 | WebView | Async | GET | × *2 |
Android 2.3.4 | WebView | Sync | GET | × *3 |
Android 2.3.4 | WebView | Async/Sync | POST | ○ |
Android 4.0.4 | 標準ブラウザ | Async/Sync | GET/POST | ○ |
Android 4.0.4 | WebView | Async/Sync | GET/POST | ○ |
iOS 5.1.1 | Mobile Safari | Async/Sync | GET/POST | ○ |
iOS 5.1.1 | WebView | Async/Sync | GET/POST | ○ |
Android 2.3.4 - NW-Z1050
Android 4.0.4 - Galaxy Nexus
iOS 5.1.1 - iPhone4S
ヒントとリンク
- WebView のキャッシュをクリアすると、また最初の一回だけアクセスできるようになります。
- When can I use... Support tables for HTML5, CSS3, etc
- javascript - Using Twitter REST API in JSON Format with AJAX - Stack Overflow
- Ajax GET request with authorization header and CORS on android 2.3.3 - Stack Overflow
- HTTP access control (CORS) | Mozilla Developer Network
*1:NETWORK_ERR: XMLHttpRequest Exception 101で常に失敗
*2:初回だけ成功するが、2回目以降のリクエストは常に失敗
*3:NETWORK_ERR: XMLHttpRequest Exception 101で常に失敗