两次访问 OkHttp 响应的 body 字符串会导致 IllegalStateException: closed
Accessing body string of an OkHttp Response twice results in IllegalStateException: closed
我通过 OkHttp 库实现我的 http 调用。一切正常,但我注意到,当我两次访问 body 作为响应字符串时,将抛出 IllegalStateException
。
也就是说,我这样做(例如):Log.d("TAG", response.body().string())
之后我实际上想像 processResponse(response.body().string())
这样使用该字符串。但是第二次调用会抛出消息 closed
的异常。
访问一个字符串两次怎么可能失败呢?我想处理该响应而无需添加 wrapper/dummy object 只是为了保存一些值(如 header、body、状态代码)。
响应上的string
方法将读取输入(网络)流并将其转换为字符串。所以它动态地构建字符串并 returns 给你。第二次调用时,网络流已经被消耗掉了,不可用了。
您应该将 string
的结果保存到一个字符串变量中,然后根据需要多次访问它。
更新:
如 there is a simpler, more lightweight API available now (see GitHub issue):
String responseBodyString = response.peekBody(Long.MAX_VALUE).string();
Log.d("TAG", responseBodyString);
原答案:
有关问题的解释,请参阅 。
然而,如果您不能轻松地将结果传递到变量中,但仍需要访问响应主体两次,您还有另一种选择:
在读取之前克隆缓冲区。这样,原始缓冲区既不会清空也不会关闭。请参阅此片段:
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);
Square 自己在 HttpLoggingInterceptor in project okhttp 中使用了这种方法。
顺便说一句,除了 Greg Ennis 的回答外,我还能说出有一次我在手表 window 中忘记了 response.body().string() 时发生了什么。所以在调试器下,body 正在读入手表,然后网络流被关闭。
稍微扩展 user2011622 and Greg Ennis 的答案,我创建了一个方法来帮助您创建 body 的完整克隆,允许您使用 [=14= 的每个副本] 分别地。我自己使用这种方法与 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);
}
您可以调用 response.peekBody(Long.MAX_VALUE);
在内存中缓冲整个响应并获取它的轻量级副本。浪费会少很多。 See this issue on GitHub.
ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something
此代码获取响应正文并且不会消耗缓冲区。这是在 this issue
中添加的新 api
我通过 OkHttp 库实现我的 http 调用。一切正常,但我注意到,当我两次访问 body 作为响应字符串时,将抛出 IllegalStateException
。
也就是说,我这样做(例如):Log.d("TAG", response.body().string())
之后我实际上想像 processResponse(response.body().string())
这样使用该字符串。但是第二次调用会抛出消息 closed
的异常。
访问一个字符串两次怎么可能失败呢?我想处理该响应而无需添加 wrapper/dummy object 只是为了保存一些值(如 header、body、状态代码)。
响应上的string
方法将读取输入(网络)流并将其转换为字符串。所以它动态地构建字符串并 returns 给你。第二次调用时,网络流已经被消耗掉了,不可用了。
您应该将 string
的结果保存到一个字符串变量中,然后根据需要多次访问它。
更新:
如
String responseBodyString = response.peekBody(Long.MAX_VALUE).string();
Log.d("TAG", responseBodyString);
原答案:
有关问题的解释,请参阅
然而,如果您不能轻松地将结果传递到变量中,但仍需要访问响应主体两次,您还有另一种选择:
在读取之前克隆缓冲区。这样,原始缓冲区既不会清空也不会关闭。请参阅此片段:
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);
Square 自己在 HttpLoggingInterceptor in project okhttp 中使用了这种方法。
顺便说一句,除了 Greg Ennis 的回答外,我还能说出有一次我在手表 window 中忘记了 response.body().string() 时发生了什么。所以在调试器下,body 正在读入手表,然后网络流被关闭。
稍微扩展 user2011622 and Greg Ennis 的答案,我创建了一个方法来帮助您创建 body 的完整克隆,允许您使用 [=14= 的每个副本] 分别地。我自己使用这种方法与 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);
}
您可以调用 response.peekBody(Long.MAX_VALUE);
在内存中缓冲整个响应并获取它的轻量级副本。浪费会少很多。 See this issue on GitHub.
ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something
此代码获取响应正文并且不会消耗缓冲区。这是在 this issue
中添加的新 api