Повторная установка запроса POST с базовой аутентификацией HTTP: "Невозможно повторить потоковое тело HTTP"
Я использую Retrofit для выполнения базового запроса POST, и я предоставляю базовый @Body для запроса.
@POST("/rest/v1/auth/login")
LoginResponse login(@Body LoginRequest loginRequest);
Когда я создаю интерфейс для Retrofit, я предоставляю свой собственный пользовательский 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
Лучше всего предоставить свои учетные данные для переоснащения через RequestInterceptor вместо OkHttp OkAuthenticator
. Этот интерфейс лучше всего работает, когда запрос можно повторить, но в вашем случае мы уже выбросили тело сообщения к тому времени, когда выясним, что это необходимо.
Вы можете продолжать использовать класс учетных данных OkAuthenticator, который может кодировать ваше имя пользователя и пароль в требуемом формате. Имя заголовка, которое вы хотите, это Authorization
.
Ответ 2
Спасибо, Джесси.
На всякий случай это помогает, вот код, который я сделал для Basic auth.
Сначала init в 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);
И затем 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;
}
}
Ответ 3
Расширение ответа Naren:
Вы создаете 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)
Ответ 4
Для базовой авторизации вы можете предоставить заголовок, например:
@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
Ответ 5
Если вы делаете это с помощью последней версии 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:
// 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.