Requête POST post post Retrofit avec authentification HTTP de base: "impossible de réessayer Le corps HTTP en streaming"

J'utilise Retrofit pour faire une demande de poste de base, et je fournis un @Body de base pour la demande.

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

quand je construis l'interface pour le Retrofit, je fournis mon propre OkHttpClient personnalisé, et tout ce que je lui fais est d'ajouter ma propre authentification personnalisée:

    @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);
    }

cela fonctionne très bien quand j'envoie des requêtes directement avec OKHttp, et d'autres requêtes GET avec retrofit mais quand j'utilise retrofit pour faire une requête POST Je reçois l'erreur suivante:

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)

j'ai joué avec. Si je supprime l'authentification, et pointe vers un serveur qui n'a pas besoin de l'authentification, alors il fonctionne très bien.

  1. donc je dois envoyer l'information.
  2. s'assurer de l'Authentification de la demande de récusation.
  3. en réponse à la demande de contestation.
  4. essayer de renvoyer la demande à nouveau, et puis l'erreur est levée.

Je ne sais pas comment contourner cela. Toute aide serait merveilleux.

25
demandé sur spierce7 2014-01-27 03:23:32

5 réponses

votre meilleur pari est de fournir vos justificatifs D'identité pour la mise à niveau via un RequestInterceptor au lieu de OkAuthenticator D'OkHttp . Cette interface fonctionne mieux lorsque la requête peut être retrievée, mais dans votre cas, nous avons déjà jeté le corps de la poste au moment où nous découvrons que c'est nécessaire.

vous pouvez continuer à utiliser la classe de justificatifs D'identité D'OkAuthenticator qui peut encoder votre nom d'utilisateur et mot de passe dans le format requis. L'en-tête nom que vous voulez est Authorization .

14
répondu Jesse Wilson 2014-01-26 23:46:44

Merci, Jesse.

juste au cas où il aide, voici le code que j'ai fait pour l'auth de base.

tout D'abord, le init dans MyApplication classe:

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);

puis le 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;
    }
}
39
répondu Ferran Maylinch 2014-08-27 10:10:12

Extension de Naren réponse:

Vous construisez auth String comme ceci:

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

et puis vous passez basicAuth pour servir comme authorization .

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
13
répondu Jacek Kwiecień 2014-12-18 11:05:42

pour l'autorisation de base vous pouvez fournir un en-tête comme:

@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
6
répondu Naren 2014-10-27 14:55:31

si vous faites cela avec la dernière version de Retrofit / OkHttp, les solutions actuelles ne sont pas suffisantes. Retrofit n'offre plus D'intercepteur request, vous devez donc utiliser les intercepteurs D'OkHttp pour accomplir une tâche similaire:

créez votre intercepteur:

public class HttpAuthInterceptor implements Interceptor {
  private String httpUsername;
  private String httpPassword;

  public HttpAuthInterceptor(String httpUsername, String httpPassword) {
    this.httpUsername = httpUsername;
    this.httpPassword = httpPassword;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request newRequest = chain.request().newBuilder()
        .addHeader("Authorization", getAuthorizationValue())
        .build();

    return chain.proceed(newRequest);
  }

  private String getAuthorizationValue() {
    final String userAndPassword = "httpUsername" + ":" + httpPassword;
    return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
  }
}

vous devez ajouter l'intercepteur à votre Client OkHttp:

// Create your client
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new HttpAuthInterceptor("httpUsername", "httpPassword"))
    .build();

// Build Retrofit with your client
Retrofit retrofit = new Retrofit.Builder()
    .client(client)
    .build();

// Create and use your service that now authenticates each request.
YourRetrofitService service = retrofit.create(YourRetrofitService.class);

I n'a pas testé le code ci-dessus, donc quelques légères modifications peuvent être nécessaires. Je programme à Kotlin pour Android now-a-days.

1
répondu spierce7 2016-12-12 16:04:51