Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing body string of an OkHttp Response twice results in IllegalStateException: closed

Tags:

android

okhttp

Update:

As mugwort points out there is a simpler, more lightweight API available now (see GitHub issue):

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

Original Answer:

For explanation of the issue see Greg Ennis' answer.

However, if you can not easily pass the result in a variable, but still need to access the response body twice you have another option:

Clone the buffer before reading it. By that the original buffer is neither emptied nor closed. See this snippet:

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);

This approach is used in HttpLoggingInterceptor in project okhttp by square themselves.


The string method on the response will read the input (network) stream and convert it into a string. So it dynamically builds the string and returns it to you. The second time you call it, the network stream has already been consumed and is no longer available.

You should save the result of string into a String variable, and then access it as many times as needed.


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

this code get the response body and won't consume the buffer. it is a new api added in this issue


Expanding a little on the answers of user2011622 and Greg Ennis, I created a method which helps you to create a complete clone of the body, allowing you to consume each copy of the body separately. I use this method myself with 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);
}

BTW in addition to Greg Ennis' answer I can tell what onetime this happened to me when I forgot response.body().string() in the watch window. So under the debugger the body was reading into the watch and the network stream was closed after that.


You can call response.peekBody(Long.MAX_VALUE); to buffer the entire response in memory and get a lightweight copy of it. It'll be much less wasteful. See this issue on GitHub.