2014-01-26 5 views
25

Я использую «Модернизацию» для выполнения базового запроса POST, и я предоставляю базовое @Body для запроса.Модернизация POST-запроса с базовой HTTP-аутентификацией: «Не удается перезапустить потоковый объект HTTP»

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

Когда я создаю интерфейс для Модернизированного я обеспечиваю свою собственную OkHttpClient, и все, что я делаю для него добавляет свою собственную пользовательскую проверку подлинности:

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

это работает здорово, когда я посылаю запросы непосредственно OKHttp и другими запросы GET с дооснащением, но когда я использую переоснащение сделать запрос POST, я получаю следующее сообщение об ошибке:

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) 

Я играл с ним. Если я удалю аутентификацию и укажу на сервер, который не требует проверки подлинности, то он отлично работает.

  1. Поэтому я должен отправить информацию.
  2. Получение запроса на проверку подлинности.
  3. Реагирование на вызов.
  4. Пытается повторно отправить запрос еще раз, а затем выдается ошибка.

Не знаете, как обойти это. Любая помощь будет замечательной.

ответ

13

Ваш лучший выбор - предоставить свои полномочия для модернизации через RequestInterceptor, а не 0H. Этот интерфейс работает лучше всего, когда запрос можно повторить, но в вашем случае мы уже выбросили тело сообщения к тому времени, когда выясним, что это необходимо.

Вы можете продолжать использовать класс учетных данных OkAuthenticator, который может кодировать ваше имя пользователя и пароль в требуемом формате. Имя заголовка, которое вы хотите, - Authorization.

+0

Работали как очарование. Огромное спасибо. Я несколько секунд стучал головой о стену. :-) – spierce7

+0

@ Джесс Уилсон (или это @ jesse-wilson) У меня такая же проблема в OkHttp. Есть ли у вас рекомендация, если я не пользуюсь Retrofit? Спасибо – cottonBallPaws

+0

@cottonBallPaws, вы можете вручную повторить запрос, когда есть проблема с аутентификацией. –

38

Спасибо, Джесси.

На всякий случай это помогает, вот код, который я сделал для Basic auth.

Во-первых, процесс инициализации MyApplication класса:

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

И тогда 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; 
    } 
} 
+0

Что такое класс ApiService? – Zoran

+0

'ApiService' - это интерфейс для моего API. Вы должны реализовать свой интерфейс в зависимости от вашего желаемого API REST. Аналогично, «Пользователь» является модельным классом для наших пользователей. Вы бы создали свой собственный. –

+0

Если вы прочитали документ RetroFit, он должен быть ясным. Ура! –

6

Для базовой авторизации Вы можете указать заголовок, как:

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback) 
13

Продление Naren'S ответ:

Вы строите AUTH String так:

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

А потом вы проходите basicAuth обслуживать, как authorization.

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback) 
+3

Если вам нужно добавить те же учетные данные в * all * исходящие запросы, не очень приятно добавить '@Header (« Авторизация »)' на все ваши интерфейсы Retrofit, не так ли? – Jonik

0

Если вы делаете это с использованием последней версии Retrofit/OkHttp, текущего набора решений недостаточно. не Модифицированные больше не предлагает RequestInterceptor, так что вам нужно использовать перехватчики OkHttp, чтобы выполнить аналогичную задачу:

Создать свой перехватчик:

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

Вы должны были бы добавить перехватчик к вашему OkHttp Client :

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

Я не проверял выше код, поэтому некоторые незначительные изменения, возможно, должны быть сделаны. Сейчас я программирую в Kotlin для Android.