Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit POST request w/ Basic HTTP Authentication: "Cannot retry streamed HTTP body"

I'm using Retrofit to do a basic POST request, and I'm providing a basic @Body for the request.

@POST("/rest/v1/auth/login")
LoginResponse login(@Body LoginRequest loginRequest);

When I'm building the interface for Retrofit I'm providing my own custom OkHttpClient, and all that I'm doing to it is adding my own custom authentication:

    @Provides
    @Singleton
    public Client providesClient() {
        OkHttpClient httpClient = new OkHttpClient();

        httpClient.setAuthenticator(new OkAuthenticator() {
            @Override
            public Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
                return getCredential();
            }

            @Override
            public Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
                return getCredential();
            }
        });

        return new OkClient(httpClient);
    }

This works great when I'm sending requests directly with OKHttp, and other GET requests with retrofit but when I use retrofit to do a POST request I get the following error:

Caused by: java.net.HttpRetryException: Cannot retry streamed HTTP body
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:324)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:508)
            at com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:94)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:49)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:357)
            at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:282)
            at $Proxy3.login(Native Method)
            at com.audax.paths.job.LoginJob.onRunInBackground(LoginJob.java:41)
            at com.audax.library.job.AXJob.onRun(AXJob.java:25)
            at com.path.android.jobqueue.BaseJob.safeRun(BaseJob.java:108)
            at com.path.android.jobqueue.JobHolder.safeRun(JobHolder.java:60)
            at com.path.android.jobqueue.executor.JobConsumerExecutor$JobConsumer.run(JobConsumerExecutor.java:172)
            at java.lang.Thread.run(Thread.java:841)

I've played around with it. If I remove the authentication, and point to a server that doesn't require the authentication, then it works fine.

  1. So I must be sending the information.
  2. Getting the Authentication challenge request.
  3. Responding to the challenge request.
  4. Trying to resend the request again, and then the error is being thrown.

Not sure how to get around this. Any help would be wonderful.

like image 925
spierce7 Avatar asked Sep 11 '25 07:09

spierce7


2 Answers

Thanks, Jesse.

Just in case it helps, here is the code I did for Basic auth.

First, the init in MyApplication class:

ApiRequestInterceptor requestInterceptor = new ApiRequestInterceptor();
requestInterceptor.setUser(user); // I pass the user from my model

ApiService apiService = new RestAdapter.Builder()
            .setRequestInterceptor(requestInterceptor)
            .setServer(Constants.API_BASE_URL)
            .setClient(new OkClient()) // The default client didn't handle well responses like 401
            .build()
            .create(ApiService.class);

And then the ApiRequestInterceptor:

import android.util.Base64;
import retrofit.RequestInterceptor;

/**
 * Interceptor used to authorize requests.
 */
public class ApiRequestInterceptor implements RequestInterceptor {

    private User user;

    @Override
    public void intercept(RequestFacade requestFacade) {

        if (user != null) {
            final String authorizationValue = encodeCredentialsForBasicAuthorization();
            requestFacade.addHeader("Authorization", authorizationValue);
        }
    }

    private String encodeCredentialsForBasicAuthorization() {
        final String userAndPassword = user.getUsername() + ":" + user.getPassword();
        return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
like image 107
Ferran Maylinch Avatar answered Sep 12 '25 22:09

Ferran Maylinch


Extending Naren's answer:

You build auth String like this:

String basicAuth = "Basic " + Base64.encodeToString(String.format("%s:%s", "your_user_name", "your_password").getBytes(), Base64.NO_WRAP);

And then you pass basicAuth to service as authorization.

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
like image 44
Jacek Kwiecień Avatar answered Sep 12 '25 21:09

Jacek Kwiecień