Returning error from OKHttp interceptor (using retrofit)

Murat Ögat picture Murat Ögat · Sep 19, 2015 · Viewed 16.9k times · Source

I am using OkHttp with Retrofit to make my app's network requests. I am also using Interceptors for Authentication and retrying requests if necessary.

The server sometimes has temporary problems, and returns an empty body although the response status is 200 OK. This causes my app to crash, because the success block of the Retrofit Callback is called, the custom object returned (and parsed with GSON) is null, and the code in success callback assumes an object is returned.

I have already reported this to the server team, but I want to fix it as well, without having to wrap all success callback code all over the app with null checks.

Currenty I am inclined to two options, although any other ideas are most welcome: 1) Not returning from the interceptor (is this even possible?) and just displaying an error dialog 2) Returning something that will make Retrofit call the failure part of the callback.

My code is below. As you may see, I retry the request for a maximum of 3 times when an empty body is received.

@Override
public Response intercept(Chain chain) throws IOException
{
    // First
    Request request = chain.request();
    Response response = chain.proceed(request);

    ....
    ....
    ....

    // Retry empty body response requests for a maximum of 3 times
    Integer retryMaxCount = 3;
    MediaType contentType = response.body().contentType();
    String bodyString = response.body().string();

    while (bodyString.length() == 0 && retryMaxCount > 0)
    {
        //Empty body received!, Retrying...

        retryMaxCount--;
        response = chain.proceed(request);
        bodyString = response.body().string();
    }

    if (bodyString.length() != 0)
    {
        // Create and return new response because it was consumed
        ResponseBody newResponseBody = ResponseBody.create(contentType, bodyString);
        return response.newBuilder().body(newResponseBody).build();
    }
    else
    {
        // WHAT TO WRITE HERE???
    }
}

Thanks a lot.

Answer

velval picture velval · May 24, 2017

Just had the same scenario and this post helped me implementing the solution. Thanks to @mastov to point to the right direction.

Working with a back-end api that always returns HTTP 200 even if there was an error. This was my response sample of an error

{"status":403,"message":"Bad User credentials","time":1495597740061,"version":"1.0"}

Here is a simple implementation to complement this answer.

public Response intercept(Chain chain) throws IOException {
        Request request   = chain.request();
        Response response = chain.proceed(request);
        ResponseBody body = response.body();
        // Only intercept JSON type responses and ignore the rest.
        if (body != null && body.contentType() != null && body.contentType().subtype() != null && body.contentType().subtype().toLowerCase().equals("json")) {
            String errorMessage = "";
            int errorCode       = 200; // Assume default OK
            try {
                BufferedSource source = body.source();
                source.request(Long.MAX_VALUE); // Buffer the entire body.
                Buffer buffer   = source.buffer();
                Charset charset = body.contentType().charset(Charset.forName("UTF-8"));
                // Clone the existing buffer is they can only read once so we still want to pass the original one to the chain.
                String json     = buffer.clone().readString(charset);
                JsonElement obj = new JsonParser().parse(json);
                // Capture error code an message.
                if (obj instanceof JsonObject && ((JsonObject) obj).has("status")) {
                    errorCode   = ((JsonObject) obj).get("status").getAsInt();
                }
                if (obj instanceof JsonObject && ((JsonObject) obj).has("message")) {
                    errorMessage= ((JsonObject) obj).get("message").getAsString();
                }
            } catch (Exception e) {
                Log.e(TAG, "Error: " + e.getMessage());
            }
            // Check if status has an error code then throw and exception so retrofit can trigger the onFailure callback method.
            // Anything above 400 is treated as a server error.
            if(errorCode > 399){
                throw new Exception("Server error code: " + errorCode + " with error message: " + errorMessage);
            }
        }

        return response;
    }