web-dev-qa-db-ja.com

OkHttp応答の本文文字列に2回アクセスすると、IllegalStateException:closed

OkHttpライブラリを介してhttp呼び出しを実装します。すべて正常に動作しますが、応答の文字列として2回本文にアクセスすると、IllegalStateExceptionがスローされることに気付きました。つまり、(たとえば)Log.d("TAG", response.body().string())を実行し、その後、実際にprocessResponse(response.body().string())のような文字列を使用したいのです。しかし、その2番目の呼び出しは、メッセージclosedで例外をスローします。

文字列に2回アクセスすると失敗する可能性はありますか?いくつかの値(ヘッダー、ボディ、ステータスコードなど)を保存するためだけに、ラッパー/ダミーオブジェクトを追加する必要なく、その応答を処理したいと思います。

39
degill

応答のstringメソッドは、入力(ネットワーク)ストリームを読み取り、それを文字列に変換します。そのため、文字列を動的に構築し、それを返します。 2回目に呼び出すと、ネットワークストリームはすでに消費されており、使用できなくなります。

stringの結果をString変数に保存し、必要な回数だけアクセスする必要があります。

38
Greg Ennis

更新:

mugwortが指摘 よりシンプルで軽量なAPIが利用可能になりました( GitHubの問題を参照 ):

String responseBodyString = response.peekBody(Long.MAX_VALUE).string();
Log.d("TAG", responseBodyString);

元の答え:

問題の説明については、 Greg Ennis 'answer を参照してください。

ただし、結果を変数に簡単に渡すことができず、応答本文に2回アクセスする必要がある場合は、別のオプションがあります。

読み込む前にバッファを複製します。それにより、元のバッファは空にも閉じられません。このスニペットをご覧ください:

ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // request the entire body.
Buffer buffer = source.buffer();
// clone buffer before reading from it
String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8"))
Log.d("TAG", responseBodyString);

このアプローチは、プロジェクト内の HttpLoggingInterceptor で使用されます okhttp 正方形自体。

57
Peter F

ところで、グレッグエニスの答えに加えて、ウォッチウィンドウでresponse.body()。string()を忘れたときに、これがいつ起こったかを知ることができます。そのため、デバッガーの下では、本体がウォッチを読み取り、その後ネットワークストリームが閉じられました。

3
Alexandr

ser2011622 および Greg Ennis の答えを少し拡大して、本文の完全なクローンを作成するのに役立つメソッドを作成しました。本体は個別に。 Retrofit2でこのメソッドを自分で使用します

/**
 * Clones a raw buffer so as not to consume the original
 * @param rawResponse the original {@link okhttp3.Response} as returned
 *                    by {@link Response#raw()}
 * @return a cloned {@link ResponseBody}
 */
private ResponseBody cloneResponseBody(okhttp3.Response rawResponse) {
    final ResponseBody responseBody = rawResponse.body();
    final Buffer bufferClone = responseBody.source().buffer().clone();
    return ResponseBody.create(responseBody.contentType(), responseBody.contentLength(), bufferClone);
}
1
Nick Cardoso

response.peekBody(Long.MAX_VALUE);を呼び出して、メモリ内の応答全体をバッファし、その軽量コピーを取得できます。無駄がずっと少なくなります。 GitHubでこの問題を参照

1
mugwort
ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something

このコードは応答本文を取得し、バッファーを消費しません。 この問題 に追加された新しいAPIです

1
ironman