Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit / OkHttp3 400 Error Body Empty

I am using Retrofit 2 in my Android project. When I hit an API endpoint using a GET method and it returns a 400 level error I can see the error content when I use an HttpLoggingInterceptor, but when I get to the Retrofit OnResponse callback the error body's string is empty.

I can see that there is a body to the error, but I can't seem to pull that body when in the context of the Retrofit callback. Is there a way to ensure the body is accessible there?

Thanks, Adam

Edit: The response I see from the server is: {"error":{"errorMessage":"For input string: \"000001280_713870281\"","httpStatus":400}}

I am trying to pull that response from the response via: BaseResponse baseResponse = GsonHelper.getObject(BaseResponse.class, response.errorBody().string()); if (baseResponse != null && !TextUtils.isEmpty(baseResponse.getErrorMessage())) error = baseResponse.getErrorMessage();

(GsonHelper is just a helper which passes the JSON string through GSON to pull the object of type BaseResponse)

The call to response.errorBody().string() results in an IOException: Content-Length and stream length disagree, but I see the content literally 2 lines above in Log Cat

like image 521
adamacdo Avatar asked Jul 14 '16 20:07

adamacdo


2 Answers

I encountered the same problem before and I fixed it by using the code response.errorBody().string() only once. You'll receive the IOException if you are using it many times so it is advised to just use it as a one-shot stream just as the Documentation on ResponseBody says.

My suggestion is: convert the Stringified errorBody() into an Object immediately as the latter is what you're gonna be using on subsequent operations.

like image 106
ReGaSLZR Avatar answered Nov 05 '22 04:11

ReGaSLZR


As it was mentioned, you need to use response.errorBody().string() only once. But there is a way to get the error body string more than once.

TL;DR Use the code below to get error body string from response more than once.

public static String getErrorBodyString(Response<?> response) throws IOException {
    // Get a copy of error body's BufferedSource.
    BufferedSource peekSource = response.errorBody().source().peek();
    // Get the Charset, as in the original response().errorBody().string() implementation
    MediaType contentType = response.errorBody().contentType(); //
    Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
    charset = Util.bomAwareCharset(peekSource, charset);
    // Read string without consuming data from the original BufferedSource.
    return peekSource.readString(charset);
}

Explanation: This is based on the original response.errorBody().string() method implementation. It uses the copy of BufferedSource from peek() and returns the error body string without consuming it, so you can call it as many times as you need.

If you look at the response.errorBody().string() method implementation, you'll see this:

public final String string() throws IOException {
    try (BufferedSource source = source()) {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    }
}

source.readString(charset) consumes data of the error body's BufferedSource instance, that's why response.errorBody().string() returns an empty string on next calls.

To read from error body's BufferedSource without consuming it we can use peek(), which basically returns a copy of the original BufferedSource:

Returns a new BufferedSource that can read data from this BufferedSource without consuming it.

like image 1
Vladimir Kattsyn Avatar answered Nov 05 '22 04:11

Vladimir Kattsyn