web-dev-qa-db-ja.com

後付け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. そのため、情報を送信する必要があります。
  2. 認証チャレンジリクエストの取得。
  3. チャレンジリクエストへの応答。
  4. リクエストを再送信しようとすると、エラーがスローされます。

これを回避する方法がわからない。どんな助けも素晴らしいでしょう。

25
spierce7

最善の方法は、OkHttpのOkAuthenticatorの代わりに RequestInterceptor を使用してRetrofitに資格情報を提供することです。そのインターフェイスは、リクエストを再試行できる場合に最適に機能しますが、あなたの場合は、必要であることが判明するまでに投稿本文を既に破棄しています。

ユーザー名とパスワードを必要な形式でエンコードできるOkAuthenticatorのCredentialクラスを引き続き使用できます。必要なヘッダー名はAuthorizationです。

14
Jesse Wilson

ありがとう、ジェシー。

それが役立つ場合に備えて、基本認証用に行ったコードを以下に示します。

まず、MyApplicationクラスのinit:

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;
    }
}
39
Ferran Maylinch

ナレンの答えを拡張する:

次のように認証Stringをビルドします。

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

そして、basicAuthauthorizationとしてサービスに渡します。

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
15
Jacek Kwiecień

基本的な承認のために、次のようなヘッダーを提供できます。

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

最新バージョンのRetrofit/OkHttpでこれを行っている場合、現在のソリューションセットでは不十分です。 Retrofitは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 now-a-days。

1
spierce7