Доступ к строке тела ответа OkHttp дважды приводит к ошибке IllegalStateException: закрыто
Я реализую свои http-вызовы через библиотеку OkHttp. Все работает отлично, но я заметил, что, когда я получаю доступ к телу в виде строки ответа дважды, будет выбрано IllegalStateException
.
То есть, я делаю (например): Log.d("TAG", response.body().string())
, и после этого я действительно хочу использовать эту строку, например processResponse(response.body().string())
. Но этот второй вызов выдает исключение с сообщением closed
.
Как возможно, что доступ к строке дважды приводит к ошибке? Я хочу обработать этот ответ без необходимости добавлять объект-оболочку/фиктивный объект только для сохранения некоторых значений (например, заголовка, тела, кода состояния).
Ответы
Ответ 1
Метод string
в ответе будет читать поток ввода (сети) и преобразовать его в строку. Поэтому он динамически строит строку и возвращает ее вам. Во второй раз, когда вы его вызываете, сетевой поток уже потребляется и больше не доступен.
Вы должны сохранить результат string
в переменной String, а затем получить доступ к нему столько раз, сколько необходимо.
Ответ 2
Обновить:
Как указывает mugwort, теперь доступен более простой API, дружественный к памяти (см. Проблему с GitHub):
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);
Этот подход используется в HttpLoggingInterceptor в проекте okhttp самим квадратом.
Ответ 3
Кстати, помимо ответа Грега Энниса, я могу сказать, что случилось, когда это произошло со мной, когда я забыл response.body(). string() в окне просмотра. Таким образом, под отладчиком тело читало часы, и после этого сетевой поток закрывался.
Ответ 4
Вы можете вызвать response.peekBody(Long.MAX_VALUE);
буферизовать весь ответ в памяти и получить его облегченную копию. Это будет намного менее расточительно. Смотрите эту проблему на GitHub.
Ответ 5
ResponseBody body = response.peekBody(Long.MAX_VALUE);
String content = body.string();
//do something
этот код получает тело ответа и не использует буфер. это новый API, добавленный в этом выпуске
Ответ 6
Развернувшись немного на ответах user2011622 и Грег Эннис, я создал метод, который поможет вам чтобы создать полный клон тела, позволяя вам потреблять каждую копию тела отдельно. Я сам использую этот метод с 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);
}
Ответ 7
if (response.isSuccessful()) {
Gson gson = new Gson();
String successResponse = gson.toJson(response.body());
Log.d(LOG_TAG, "successResponse: " + successResponse);
} else {
try {
if (null != response.errorBody()) {
String errorResponse = response.errorBody().string();
Log.d(LOG_TAG, "errorResponse: " + errorResponse);
}
} catch (Exception e) {
e.printStackTrace();
}
}