Retrofit: POST request not receiving response body

Created on 6 Apr 2016  Â·  6Comments  Â·  Source: square/retrofit

I have an API for users, and whenever you create a user, it returns the user created. I can see this behavior using a Client such as Postman:

POST {endpoint}/users
Body:

{"email":"[email protected]","experience":0, ...}

Response Body:

{
  "name": "Kevin Grant",
  "email": "[email protected]",
  ...
}
Content-Length → 1014
Content-Type → application/hal+json;charset=UTF-8
Date → Sat, 02 Apr 2016 22:23:41 GMT
Location → {endpoint}/users/5700466d288714814cccc661
...

However, when POSTing with Retrofit, the post is made correctly and 201 Created is received, but the Content-Length is always 0:

@POST("users")
Observable<User> createUser(@Body User user);
Api.getService().createUser(user)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError(e -> Log.i(TAG, "ERROR: " + e.getMessage()))
            .subscribe(this::logUser);

The previous code results in the logs:

04-05 19:40:31.004  D/OkHttp: --> POST {endpoint}/users HTTP/1.1
04-05 19:40:31.004  D/OkHttp: Content-Type: application/json; charset=UTF-8
04-05 19:40:31.004  D/OkHttp: Content-Length: 337
04-05 19:40:31.014  D/OkHttp: {"averages":null,"email":"[email protected]","experience":0,"friends":null,"googleId":"12345","id":null,"imageUrl":"url.com","incomingRequests":null,"level":0,"matchLosses":0,"matchWins":0,"matches":null,"name":"Kevin","outgoingRequests":null,"records":null,"registrationToken":"1233213","series":null,"seriesLosses":0,"seriesWins":0}
04-05 19:40:31.014  D/OkHttp: --> END POST (337-byte body)

04-05 19:40:31.544  D/OkHttp: <-- 201 Created {endpoint}/users (530ms)
04-05 19:40:31.544  D/OkHttp: Content-Length: 0
04-05 19:40:31.544  D/OkHttp: Location: {endpoint}/users/5704771f2887369e3b9325a3
04-05 19:40:31.544  D/OkHttp: Server: Microsoft-IIS/8.0
04-05 19:40:31.544  D/OkHttp: X-Powered-By: ASP.NET
04-05 19:40:31.544  D/OkHttp: Set-Cookie: ARRAffinity=a03ce629965ea3ff1ee2a6b1d2c9ce9e2200484a00fde7db350decd224781f9f;Path=/;Domain={endpoint}
04-05 19:40:31.544  D/OkHttp: Date: Wed, 06 Apr 2016 02:40:31 GMT
04-05 19:40:31.544  D/OkHttp: OkHttp-Sent-Millis: 1459910431421
04-05 19:40:31.544  D/OkHttp: OkHttp-Received-Millis: 1459910431547
04-05 19:40:31.544  D/OkHttp: <-- END HTTP (0-byte body)
04-05 19:40:31.554 26552-26552/ I/SECOND ACTIVITY: ERROR: No content to map due to end-of-input
                                                                                    at [Source: java.io.InputStreamReader@c3a1fa2; line: 1, column: 0]

Which shows the request was successfully made, but shows an empty response body. This of course throws an error, trying to parse the response into a User object.

This seems like an issue to me, as as far as I can tell, and after research, my code is effectively correct.

Enhancement

Most helpful comment

Not sure whether it's reasonable to always do this. That would prevent you from accessing the optional entity from the 201 itself.

I need to do more research.

For now change your type to Observable<Response<Void>> and use the Location header of the Response to flatMap a follow-up request using an endpoint like:

@GET
Observable<User> lookupUser(@Url String url);

All 6 comments

I would guess Postman is making a follow-up request using the 201's Location header value and hiding that redirect from you.

Not sure whether it's reasonable to always do this. That would prevent you from accessing the optional entity from the 201 itself.

I need to do more research.

For now change your type to Observable<Response<Void>> and use the Location header of the Response to flatMap a follow-up request using an endpoint like:

@GET
Observable<User> lookupUser(@Url String url);

Jake, the quick response is greatly appreciated. Your solution worked perfectly! Thank you so much.

You can also leverage the OkHttp's ability to follow redirects for 3xx codes using the interceptor below added as a network interceptor to OkHttpClient.

package pl.matbos.exemplary.util;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.http.Header;

import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_SEE_OTHER;

/**
 * To utilize it simply add a header to your endpoint using {@link Header} annotation:
 * <pre><code>
 * interface MyService {
 *   &#64;POST("user")
 *   &#64;Headers(Follow201LocationInterceptor.FOLLOW_HEADER)
 *   Observable&lt;User&gt; createUser(@Body String name)
 * }
 * </code></pre>
 * <p>
 * It has to be used as a network interceptor, otherwise it will not work.
 */
public class Follow201LocationInterceptor implements Interceptor {

    private static final String FOLLOW_REDIRECT = "follow-redirect";
    public static final String FOLLOW_HEADER = FOLLOW_REDIRECT + ":true";
    private static final String LOCATION = "Location";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        boolean shouldRedirect = request.header(FOLLOW_REDIRECT) != null;

        if (shouldRedirect) {
            request = request.newBuilder().removeHeader(FOLLOW_REDIRECT).build();
        }

        Response response = chain.proceed(request);
        if (shouldRedirect && response.code() == HTTP_CREATED && response.header(LOCATION) != null) {
            return response.newBuilder().code(HTTP_SEE_OTHER).build();
        }
        return response;
    }
}

And adding(to methods that should redirect) the following header @Headers(Follow201LocationInterceptor.FOLLOW_HEADER).
So the endpoint definition would look like this:

    @POST("user")
    @Headers(Follow201LocationInterceptor.FOLLOW_HEADER)
    Observable<User> registerUser();

The interceptor (and optional marker header) is a great way to opt in to this behavior. Retrofit can't do anything automatic here because the 201 might return some body that the application-layer cares about.

Hello there,

Even I'm not able to get a response using retrofit, while it gives a success response as:

Response{protocol=http/1.1, code=201, message=Created, url=http://test.some_url.in/api/upload/user/PostUserImage/}

and the image is successfully uploaded to server but
Nothing is shown either in response body or using Gson:

I'm using following code:

`
private void uploadImage(byte[] imageBytes) {

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    RetrofitInterface retrofitInterface = retrofit.create(RetrofitInterface.class);

    RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), imageBytes);

    MultipartBody.Part body = MultipartBody.Part.createFormData("image", "unnamed.jpg", requestFile);
    Call<Response> call = retrofitInterface.uploadImage(body);
    mProgressBar.setVisibility(View.VISIBLE);
    call.enqueue(new Callback<Response>() {
        @Override
        public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {

            mProgressBar.setVisibility(View.GONE);

            Log.v("keys","-----response---------"+response);
            if (response.isSuccessful()) {

                Response responseBody = response.body();
                mBtImageShow.setVisibility(View.VISIBLE);
                assert responseBody != null;

            } else {

                ResponseBody errorBody = response.errorBody();

                Gson gson = new Gson();

                assert errorBody != null;
                Response errorResponse = gson.fromJson(errorBody.toString(), Response.class);


            }
        }

        @Override
        public void onFailure(Call<Response> call, Throwable t) {

            mProgressBar.setVisibility(View.GONE);
            Log.d(TAG, "onFailure: "+t.getLocalizedMessage());
        }
    });
} `

Please help how can I get the server response??????

Was this page helpful?
0 / 5 - 0 ratings