初老のボケ防止日記

おっさんのひとりごとだから気にしないようにな。

AndroidのHttpURLConnectionのPOSTでハマる

やっぱりOkHttpにしときゃよかった。

AndroidでHTTP通信する方法は色々とあるんだけども、Google様は過去を振り返らないタイプらしい。

qiita.com

要約すると、

標準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のバグではないかなとか思い始めて
探したらこれと同じみたい。

stackoverflow.com


ということで、以下をヘッダに追加する。

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

人生はOK牧場!

人生はOK牧場!

*1:Genymotionでしか試しておりません