Android 使用基本 HTTP 身份验证改造 POST 请求:“无法重试流式 HTTP 正文”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21370620/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 04:26:34  来源:igfitidea点击:

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

androidhttp-authenticationretrofitokhttp

提问by spierce7

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

我正在使用 Retrofit 来执行基本的 POST 请求,并且我正在为该请求提供一个基本的 @Body。

@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:

当我为 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);
    }

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:

当我直接使用 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)

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.
  1. 所以我必须发送信息。
  2. 获取身份验证质询请求。
  3. 响应质询请求。
  4. 再次尝试重新发送请求,然后抛出错误。

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

不知道如何解决这个问题。任何帮助都会很棒。

采纳答案by Jesse Wilson

Your best bet is to provide your credentials to Retrofit via a RequestInterceptorinstead of OkHttp's OkAuthenticator. That interface works best when the request can be retried, but in your case we've already thrown out the post body by the time we find out that's necessary.

最好的办法是通过RequestInterceptor而不是 OkHttp 的OkAuthenticator. 当可以重试请求时,该接口效果最佳,但在您的情况下,当我们发现有必要时,我们已经抛出了帖子正文。

You can continue to use OkAuthenticator's Credential class which can encode your username and password in the required format. The header name you want is Authorization.

您可以继续使用 OkAuthenticator 的 Credential 类,它可以以所需的格式对您的用户名和密码进行编码。您想要的标题名称是Authorization.

回答by Ferran Maylinch

Thanks, Jesse.

谢谢,杰西。

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

以防万一它有帮助,这是我为基本身份验证所做的代码。

First, the init in MyApplicationclass:

首先,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);

And then the ApiRequestInterceptor:

然后是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;
    }
}

回答by Jacek Kwiecień

Extending Naren's answer:

扩展纳伦的回答:

You build auth Stringlike this:

String像这样构建身份验证:

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

And then you pass basicAuthto service as authorization.

然后你传递basicAuth给 service as authorization

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

回答by Naren

For basic authorization you can provide a header like:

对于基本授权,您可以提供如下标题:

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

回答by spierce7

If you are doing this with the latest version of Retrofit / OkHttp, the current set of solutions aren't sufficient. Retrofit no longer offers a RequestInterceptor, so you need to use OkHttp's interceptors to accomplish a similar task:

如果您使用最新版本的 Retrofit / OkHttp 执行此操作,则当前的一组解决方案是不够的。Retrofit 不再提供 RequestInterceptor,因此需要使用 OkHttp 的拦截器来完成类似的任务:

Create your interceptor:

创建你的拦截器:

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

You'd need to add the interceptor to your OkHttp 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 didn't test the above code, so some slight modifications may need to be made. I program in Kotlin for Android now-a-days.

上面的代码我没有测试,所以可能需要做一些细微的修改。我现在在 Android 的 Kotlin 中编程。