Retrofit: OkHttpCall problems

Created on 13 Jul 2015  路  22Comments  路  Source: square/retrofit

Hello, guys.

I tried use Retrofit 2.0 + OkHttp 2.5 in my project.
Before that I used Retrofit 1.9 with OkHttp 2.5 - I had not problems.

About my Retrofit configuration:

  1. I added GsonConverter and GsonConverterFactory from your retrofit-connectors sample.
  2. I created my Gson object with Type adapters.
  3. I created my custom OkHttpClient with Application interceptor(for adding headers)
  4. I created my custom BaseUrl with my service endpoint.
  5. I created ServiceAPI interface with method like this :
    @GET("/api/v1/users/{fbid}/init/")
    Call getAppData(@Path("fbid") String fbid, @Query("delta_since_timestamp") long delta, @Query("chat_enabled") long chat);
  6. I created Retorfit object like this :
    private static final Retrofit RETROFIT_MAIN = new Retrofit.Builder().baseUrl
    (sMainEndpoint).client(generateNewOkClient(sMainEndpoint.getUrl()))
    .converterFactory(GsonConverterFactory.create(gsonBuilder)).callbackExecutor
    (Executors.newFixedThreadPool(2))
    .build();
    I added custom Executor only for tests.
  7. I created my service like this:
    private static final ServiceAPI SERVICE_MAIN = RETROFIT_MAIN.create(ServiceAPI.class);

Before next note it's ok.

  1. Now I want use getAppData() method:
    Call call = getService().getAppData(playerFbid, delta, BuildConfig.CHAT_VERSION);
    call.enqueue(new Callback() {
    @Override
    public void onResponse(Response response) {
    AppData appData = response.body();
    if (appData != null) {

```
}
}

   @Override
   public void onFailure(Throwable error) {

   }

});
```

After that I alwayshave problem in this code (OkHttpCall class, parseResponse method):

// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
    .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
    .build();

After this code I always have Exception like this -
IllegalStateException -"Cannot read raw response body of a converted body."

This exception was generated in NoContentResponseBody class, source() method.
It very strange because I have Response with valid http url and code = 200(Result=OK).

Why I have this exception? May be I have wrong Retrofit configuartion or problem contains in Retrofit 2.0 library?

Thank you for attension.

Most helpful comment

I'm having the same issue without NewRelic when I try to read the raw response body, for some reason I need to have both the converted response and save the raw JSON string as well. So I implemented a ResponseCallback that extends Callback and in onResponse(Response response) I can receive the converted body as type T, but when I try to read the raw response response.raw().body().string(), I get the following exception:

java.lang.IllegalStateException: Cannot read raw response body of a converted body.
at retrofit.NoContentResponseBody.source(NoContentResponseBody.java:41)
at com.squareup.okhttp.ResponseBody.bytes(ResponseBody.java:54)
at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:83)
at .....ResponseCallback.getRawBodyAsString(ResponseCallback.java:181)
at .....ResponseCallback.onResponse(ResponseCallback.java:81)
at retrofit.ExecutorCallAdapterFactory$ExecutorCallback$1.run(ExecutorCallAdapterFactory.java:84)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5696)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1028)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)

By the way I was using NewRelic, but it's completely disabled now until they fix the conflict.

All 22 comments

if your response is not successful (response.isSuccess() is false), you have to use response.errorBody() instead

Hello felipecsl,
As I wrote earlier I have good Response like this:

Response{protocol=http/1.1, code=200, message=OK, url=https://myservice.com/api/v1/users/100004607503701/init/?delta_since_timestamp=1&chat_enabled=3}

(myservice.com - alias for example)

When I can find response.isSuccess() in your code?

https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/Response.java#L92-L94

Oh if your response is actually successful, then I think the problem is that this behavior changed on Retrofit 2.0, since now you can't read the response after it has been already converted. I'm not sure if there's a workaround for this limitation

Sorry, but I don't understand. Is it mean that I will always have this Exception? "you can't read the response after it has been already converted" - where I can find this code? Can you explain this moment?

hmm.. so you're using a raw type (Call) for your service method, you should actually be using Call<YourType> in order to have the body converted to the type of your response body. Then. you'd have Response<YourType> in your onResponse() method. I'm not sure this is the reason why you're getting the exception, but it's a red flag

Sorry, I'm mistaken
I used this method :
"Call getAppData(@Path("fbid") String fbid, @Query("delta_since_timestamp") long delta, @Query("chat_enabled") long chat);"

After I used this code:
"Call call = getService().getAppData(playerFbid, delta, BuildConfig.CHAT_VERSION);
call.enqueue(new Callback() {
@Override
public void onResponse(Response response) {
AppData appData = response.body();
if (appData != null) {

    }
}

@Override
public void onFailure(Throwable error) {

}

});"

May be I have problem with GsonConverter? But with Retrofit 1.9 I had not this problem

Paste the entirety of your onResponse callback.

Hello, Jake
getAppData() method declaration

@GET("/api/v1/users/{fbid}/init/")
    Call<AppData> getAppData(@Path("fbid") String fbid, @Query("delta_since_timestamp") long delta, @Query("chat_enabled") long chat);

Call getAppData() method

Call<AppData> call = getService().getAppData(playerFbid, delta, BuildConfig.CHAT_VERSION);
call.enqueue(new Callback<AppData>() {
            @Override
            public void onResponse(Response<AppData> response) {
                AppData appData = response.body();
                if (appData != null) {
                   // do something
                }
            }

            @Override
            public void onFailure(Throwable error) {
                error.getMessage
            }
        });

I always received Throwable error in onFailure() method, but I have never got Response in onResponse() method. Do you want see any other code?

I think I found reason why I got this issue - problem in Newrelic agent.

OkHttpCall class, private Response parseResponse(com.squareup.okhttp.Response rawResponse) method:

     ResponseBody rawBody = rawResponse.body();

    // Remove the body's source (the only stateful object) so we can pass the response along.
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

instead of com.squareup.okhttp.Response.Builder app try use com.newrelic.agent.android.instrumentation.okhttp2.ResponseBuilderExtension class from Newrelic

 public Builder body(ResponseBody body) {
        BufferedSource source = body.source();
        boolean length = false;
        Buffer buffer = new Buffer();

        try {
            source.readAll(buffer);
        } catch (IOException var6) {
            log.error("IOException reading from source: ", var6);
        }

        return this.impl.body(new PrebufferedResponseBody(body, buffer));
    }

body.source() throw IllegalStateException:

 java.lang.IllegalStateException: Cannot read raw response body of a converted body.
            at retrofit.NoContentResponseBody.source(NoContentResponseBody.java:41)
            at com.newrelic.agent.android.instrumentation.okhttp2.ResponseBuilderExtension.body(ResponseBuilderExtension.java:70)
            at com.newrelic.agent.android.instrumentation.okhttp2.OkHttp2Instrumentation.body(OkHttp2Instrumentation.java:33)
            at retrofit.OkHttpCall.parseResponse(OkHttpCall.java:124)
            at retrofit.OkHttpCall.access$000(OkHttpCall.java:25)
            at retrofit.OkHttpCall$1.onResponse(OkHttpCall.java:90)
            at com.newrelic.agent.android.instrumentation.okhttp2.CallbackExtension.onResponse(CallbackExtension.java:45)
            at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:170)
            at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:818)

Do you have any suggestion which can help me?

It sounds like you need to put the NewRelic stuff in an OkHttp interceptor. It shouldn't know anything about Retrofit and it definitely shouldn't be sitting on top of it. If it's doing this automatically (I know they like to do dirty things for "easy" integration) then it's a bug on their side. And if it's not a bug, it's a usage question and since I have never used New Relic you're unlikely to get a good answer here.

I would suggest posting on StackOverflow so that other users of both see it. The amount of people who will see it here is very few and the number who know about New Relic is probably close to 0.

Sorry, but I don't understand how I can put NewRelic stuff in an OkHttp interceptor. To be clear - I used Application Interceptor in my OkHttp client - I add several headers to request. Can you give me some code example about "NewRelic stuff"?

I have never used New Relic and I have no idea what they're doing. This is why I suggested you post elsewhere like StackOverflow or on their support forum so that those who work on/with New Relic can help.

If you are using an application interceptor this should work fine because it happens before Retrofit removes the body. I don't see how New Relic would get access to the raw response with the removed body.

@JakeWharton I've run into this too and am currently working with NewRelic on a fix. You mention that you don't use NewRelic... how should a dev using Retrofit monitor network requests? My first thought is to use OkHttp Interceptors and then batch requests to a monitoring service, which is possibly what NewRelic is trying to do. Is there a better or different way?

Apologies if this is the wrong place to ask, but I'm not sure where else to post such a question.

That's exactly what we do, yes. We pass API call data from an OkHttp interceptor along like any other analytics data which gets batched automatically and sent somewhere (as all analytics services should be doing).

I'm not against NewRelic or anything. I just have zero exposure to it and since v2.0 is undergoing development I don't really have the time to find out what they're doing.

I'm having the same issue without NewRelic when I try to read the raw response body, for some reason I need to have both the converted response and save the raw JSON string as well. So I implemented a ResponseCallback that extends Callback and in onResponse(Response response) I can receive the converted body as type T, but when I try to read the raw response response.raw().body().string(), I get the following exception:

java.lang.IllegalStateException: Cannot read raw response body of a converted body.
at retrofit.NoContentResponseBody.source(NoContentResponseBody.java:41)
at com.squareup.okhttp.ResponseBody.bytes(ResponseBody.java:54)
at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:83)
at .....ResponseCallback.getRawBodyAsString(ResponseCallback.java:181)
at .....ResponseCallback.onResponse(ResponseCallback.java:81)
at retrofit.ExecutorCallAdapterFactory$ExecutorCallback$1.run(ExecutorCallAdapterFactory.java:84)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5696)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1028)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)

By the way I was using NewRelic, but it's completely disabled now until they fix the conflict.

just write "onResponse(Response ResponseBody response)" to convert your response to ResponseBody

@ialmetwally did you find a way to do that?

but when I try to read the raw response response.raw().body().string(), I get the following exception:

I faced the same issue today (I don't use NewRelic too). Calling body() from response instead of raw() works fine.
response.body().string()

@ghuiii, I also used a solution as @st0rmtroop3r offered.
Also you can get a stream with val buffer = response.body()?.byteInputStream().

Was this page helpful?
0 / 5 - 0 ratings