AndroidのHttpURLConnectionのPOSTでハマる
やっぱりOkHttpにしときゃよかった。
AndroidでHTTP通信する方法は色々とあるんだけども、Google様は過去を振り返らないタイプらしい。
要約すると、
標準APIならFroyo以降はHttpURLConnection使っとけよ。それ以外のやつは飽きたら捨てる。
なんてジゴロなんでしょう。でも流石に家柄の良い本妻のHttpURLConnectionクラスはDeprecatedにするわけにはいかないですものね。
あれ? 過去にHttpClient使った開発があった気がする。
それはもう忘却の彼方なので忘れましょう。ということで忠告通り、とりあえず外部ライブラリに頼らずに標準APIのHttpURLConnectionを使ってみようかなと思ったら速攻ハマった。
今回のお悩み
内容
「HttpURLConnectionクラスを使って、HTTP(POST)を連続で実行すると2回目でIOException食らう。」
GETの場合は問題ない。POSTの場合も2回目を少し間を空けてれば問題ない。
あ、面倒くさいパターンだこれ。
サンプルコード
こんな感じでJSONをパラメータにPOSTして結果をJSONでもらうというありがちなやつ。
private void doPost() { String url = "http://xxx.xxx.xxx.xxx"; String requestJSON = "JSON文字列"; HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(url).openConnection(); conn.setRequestMethod("POST"); conn.setDoInput(true); conn.setDoOutput(true); conn.setFixedLengthStreamingMode(requestJSON.getBytes().length); conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); Log.i("OSA030","doPost start.:" + conn.toString()); conn.connect(); DataOutputStream os = new DataOutputStream(conn.getOutputStream()); os.write(requestJSON.getBytes("UTF-8")); os.flush(); os.close(); if( conn.getResponseCode() == HttpURLConnection.HTTP_OK ){ StringBuffer responseJSON = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inputLine; while ((inputLine = reader.readLine()) != null) { responseJSON.append(inputLine); } Log.i("OSA030", "doPost success"); } }catch(IOException e){ Log.e("OSA030","error orz:" + e.getMessage(), e); }finally { if( conn != null ){ conn.disconnect(); } } }
で2回連続呼ぶとこうなる。
05-22 03:12:14.773 1202-1225/com.hatenablog.osa030 I/OSA030﹕ doPost start.:libcore.net.http.HttpURLConnectionImpl:http://xxx.xxx.xxx.xxx 05-22 03:12:19.797 1202-1225/com.hatenablog.osa030 I/OSA030﹕ doPost success 05-22 03:12:19.797 1202-1225/com.hatenablog.osa030 I/OSA030﹕ doPost start.:libcore.net.http.HttpURLConnectionImpl:http://xxx.xxx.xxx.xxx 05-22 03:12:19.813 1202-1225/com.hatenablog.osa030 E/OSA030﹕ error orz:null java.io.EOFException at libcore.io.Streams.readAsciiLine(Streams.java:203) at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:560 at libcore.net.http.HttpEngine.readResponse(HttpEngine.java:813) at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:274) at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:486)
2回目のレスポンスコードを取得しようとして例外が吐かれている。
バージョン毎の発生状況
この症状がでるのは特定のバージョンだけ*1。
Version | 発生 |
4.1.1 | する |
4.2.2 | する |
4.3 | する |
4.4.4 | しない |
5.0.0 | しない |
5.1.0 | しない |
対応
最初はKeep-Alive関連かなと思ったけど、同じコードでもAndroidのバージョンによって事象が発生有無が異なるし、同じ処理をOkHttpで置き換えると問題がでていたバージョンのAndroidでも動作するのでこれはAPIのバグではないかなとか思い始めて
探したらこれと同じみたい。
ということで、以下をヘッダに追加する。
if ( Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2 && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ){ Log.i("OSA030","setConnection -> close"); conn.setRequestProperty("Connection", "close"); }
Keep-Aliveを無効にすれば問題ないということは結局のところKeep-Alive関連の処理がよろしくないのであろう。でもこれって毎回コネクションはるのでHTTPSだとコスト高くない? なくなくない? とかオジサンは思うんだけど。因みに事象がでるものと出ないものは実際に処理してるHttpURLConnectionImplインスタンスが異なる。
でる | libcore.net.http.HttpURLConnectionImpl |
でない | com.android.okhttp.internal.http.HttpURLConnectionImpl |
やっぱり時代はOkHttpなのか。なら外部ライブラリとして最新使ったほうがいいんじゃねえのかとか言い出したら元も子もないので、もう暫くはHttpURLConnectionで頑張ってみようと思う。
ぶっちゃけOkHttpならこんな簡単にかけるんだけども。
private void doPost() { String url = "http://xxx.xxx.xxx.xxx"; String requestJSON = "JSON文字列"; try { OkHttpClient client = new OkHttpClient(); Request request = prepareRequest(url) .post(RequestBody.create( MediaType.parse("application/json; charset=utf-8"), requestJSON ) ) .build(); Log.i("OSA030", "doPost start.:" + conn.toString()); Response response = client.newCall(request).execute(); if (response.isSuccessful()){ response.body().string(); Log.i("OSA030", "doPost success"); } }catch(IOException e){ Log.e("OSA030", "error orz:" + e.getMessage(), e); } }
- ガッツ石松が言い出したら時代はもうOkHttp
- 作者: ガッツ石松
- 出版社/メーカー: 幻冬舎
- 発売日: 2004/12
- メディア: 単行本
- クリック: 2回
- この商品を含むブログ (2件) を見る
*1:Genymotionでしか試しておりません