使用基本 HTTP 身份验证改造 POST 请求:“无法重试流式 HTTP 正文”
Posted
技术标签:
【中文标题】使用基本 HTTP 身份验证改造 POST 请求:“无法重试流式 HTTP 正文”【英文标题】:Retrofit POST request w/ Basic HTTP Authentication: "Cannot retry streamed HTTP body" 【发布时间】:2014-02-17 16:29:30 【问题描述】:我正在使用 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】:谢谢,杰西。
以防万一,这是我为基本身份验证所做的代码。
首先,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;
【讨论】:
什么是类 ApiService?ApiService
是我的 API 的接口。您将根据所需的 REST API 实现您的接口。同样User
是我们用户的模型类。您将创建自己的。【参考方案2】:
扩展纳伦的答案:
你像这样构建身份验证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)
【讨论】:
如果您需要向所有传出请求添加相同的凭据,那么必须在您的 Retrofit 接口中添加@Header("Authorization")
不是很好,是吗?跨度>
下面的方法可以很容易的生成basicAuth。 var basicAuth = Credentials.basic("YOUR_USERNAME", "YOUR_PASSWORD")【参考方案3】:
最好的办法是通过 RequestInterceptor 而不是 OkHttp 的 OkAuthenticator
向 Retrofit 提供凭据。当可以重试请求时,该界面效果最佳,但在您的情况下,当我们发现有必要时,我们已经丢弃了帖子正文。
您可以继续使用 OkAuthenticator 的 Credential 类,该类可以将您的用户名和密码编码为所需的格式。您想要的标题名称是Authorization
。
【讨论】:
工作就像一个魅力。谢谢一堆。几个小时以来,我一直在用头撞墙。 :-) @Jesse Wilson(或者是@jesse-wilson)我在 OkHttp 中遇到了同样的问题。如果我不使用 Retrofit,你也有推荐吗?谢谢 @cottonBallPaws 您可以在出现身份验证问题时手动重试请求。 @JesseWilson 你的链接坏了 是的。该建议仅适用于 Retrofit 1.x。在 Retrofit 2.x 中,你需要一个 OkHttp 拦截器。 github.com/square/okhttp/wiki/Interceptors【参考方案4】:对于基本授权,您可以提供如下标题:
@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
【讨论】:
【参考方案5】:如果您使用最新版本的 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 编程。
【讨论】:
以上是关于使用基本 HTTP 身份验证改造 POST 请求:“无法重试流式 HTTP 正文”的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Golang 中使用摘要身份验证进行 HTTP POST?