61

I implement my http calls via the OkHttp library. Everything works fine, but I noticed that, when I access the body as a string of the response twice an IllegalStateException will be thrown. That is, I do (for example): Log.d("TAG", response.body().string()) and after that I actually want to use that string like processResponse(response.body().string()). But that second call throws the exception with the message closed.

How can it be possible that accessing a string twice results in a failure? I want to process that response without the need to add a wrapper/dummy object just for saving some values (like header, body, statuscode).

degill
  • 1,285
  • 2
  • 13
  • 19

6 Answers6

101

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.

Peter F
  • 3,633
  • 3
  • 33
  • 45
45

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.

Greg Ennis
  • 14,917
  • 2
  • 69
  • 74
  • 1
    I was hoping that I could just reuse that `Response` object. Before using OkHttp I returned a custom ApiResult object where I just saved all relevant information, but its just a duplicate IMO. – degill Jan 13 '15 at 13:03
  • maybe you should checkout this [issue](https://github.com/square/okhttp/issues/1240) on github. – penkzhou Jan 13 '15 at 13:04
  • I see your point. I guess they just wanted to keep it as low level as possible without caching the result that may never be called again – Greg Ennis Jan 13 '15 at 13:08
  • @penkzhou well, that issue just states that you cannot use it twice. No futher information. But still, thanks – degill Jan 13 '15 at 13:35
  • What about saving it as an InputStream? – IgorGanapolsky Jun 10 '18 at 19:31
  • I was having the same problem. Although this implementation has its justifications, it's non-intuitive to developers without trawling the code in okhttp3 – angryITguy Jan 16 '20 at 04:02
11
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

ironman
  • 179
  • 2
  • 5
4

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);
}
Community
  • 1
  • 1
Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
  • Just call the method with the response, you'll get another copy of the response body back but the original will remain available (open) – Nick Cardoso Mar 27 '17 at 11:05
  • look in the sources and implement the same thing i have for prior (new isntance from cloned source), i'm not familiar with it – Nick Cardoso Mar 27 '17 at 11:14
2

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.

Alexandr
  • 695
  • 2
  • 9
  • 18
1

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.

axm__
  • 2,463
  • 1
  • 18
  • 34
mugwort
  • 19
  • 1