我正在使用OkHttp和Retrofit来制作应用程序的网络请求。我还使用拦截器进行身份验证,并在必要时重试请求。
服务器有时会出现临时问题,虽然响应状态为200 OK,但返回空体。这会导致我的应用程序崩溃,因为调用了Retrofit Callback的成功块,返回(并使用GSON解析)的自定义对象为null,成功回调中的代码假定返回一个对象。
我已经向服务器团队报告了这个问题,但是我也希望修复它,而不必使用null检查将所有成功回调代码包装在整个应用程序中。
当前我倾向于两种选择,尽管欢迎任何其他想法: 1)不从拦截器返回(这是否可能?)并且只显示错误对话框 2)返回会使Retrofit调用失败的部分回调。
我的代码如下。正如您所看到的,当收到空体时,我会重试该请求最多3次。
@Override
public Response intercept(Chain chain) throws IOException
{
// First
Request request = chain.request();
Response response = chain.proceed(request);
....
....
....
// Retry empty body response requests for a maximum of 3 times
Integer retryMaxCount = 3;
MediaType contentType = response.body().contentType();
String bodyString = response.body().string();
while (bodyString.length() == 0 && retryMaxCount > 0)
{
//Empty body received!, Retrying...
retryMaxCount--;
response = chain.proceed(request);
bodyString = response.body().string();
}
if (bodyString.length() != 0)
{
// Create and return new response because it was consumed
ResponseBody newResponseBody = ResponseBody.create(contentType, bodyString);
return response.newBuilder().body(newResponseBody).build();
}
else
{
// WHAT TO WRITE HERE???
}
}
非常感谢。
答案 0 :(得分:3)
刚刚有相同的情况,这篇文章帮助我实施了解决方案。感谢@mastov指出了正确的方向。
使用后端api,即使出现错误也始终返回HTTP 200。这是我的错误响应示例
{"status":403,"message":"Bad User credentials","time":1495597740061,"version":"1.0"}
这是一个简单的实现,以补充这个答案。
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
ResponseBody body = response.body();
// Only intercept JSON type responses and ignore the rest.
if (body != null && body.contentType() != null && body.contentType().subtype() != null && body.contentType().subtype().toLowerCase().equals("json")) {
String errorMessage = "";
int errorCode = 200; // Assume default OK
try {
BufferedSource source = body.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = body.contentType().charset(Charset.forName("UTF-8"));
// Clone the existing buffer is they can only read once so we still want to pass the original one to the chain.
String json = buffer.clone().readString(charset);
JsonElement obj = new JsonParser().parse(json);
// Capture error code an message.
if (obj instanceof JsonObject && ((JsonObject) obj).has("status")) {
errorCode = ((JsonObject) obj).get("status").getAsInt();
}
if (obj instanceof JsonObject && ((JsonObject) obj).has("message")) {
errorMessage= ((JsonObject) obj).get("message").getAsString();
}
} catch (Exception e) {
Log.e(TAG, "Error: " + e.getMessage());
}
// Check if status has an error code then throw and exception so retrofit can trigger the onFailure callback method.
// Anything above 400 is treated as a server error.
if(errorCode > 399){
throw new Exception("Server error code: " + errorCode + " with error message: " + errorMessage);
}
}
return response;
}
答案 1 :(得分:1)
返回自定义响应将解决此问题。将此代码段放入拦截器中,然后再返回
if(response.body() == null)
return new Response.Builder()
.code(404)
.body(response.body())
.protocol(Protocol.HTTP_2)
.message("Token has expired please login once again")
.request(chain.request())
.build()
答案 2 :(得分:1)
你应该抛出 IOException
。在这种情况下,retrofit2 将使用 onFailure
路径。
答案 3 :(得分:0)
我的解决方案来自okhttp3.logging.HttpLoggingInterceptor
class ErrorResponseInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
val code = response.code()
if (code in 400..500) {
responseBody(response)?.also { errorString ->
// error string here is a body of server error
}
}
return response
}
private fun responseBody(response: Response): String? {
val responseBody = response.body() ?: return null
val contentLength = responseBody.contentLength()
if (contentLength == 0L) {
return null
}
val source = responseBody.source()
source.request(Long.MAX_VALUE) // Buffer the entire body.
var buffer = source.buffer()
val headers = response.headers()
if ("gzip".equals(headers.get("Content-Encoding"), ignoreCase = true)) {
var gzippedResponseBody: GzipSource? = null
try {
gzippedResponseBody = GzipSource(buffer.clone())
buffer = okio.Buffer()
buffer.writeAll(gzippedResponseBody)
} finally {
gzippedResponseBody?.close()
}
}
val charset: Charset = responseBody.contentType()?.charset(UTF8) ?: UTF8
return buffer.clone().readString(charset)
}
private companion object {
val UTF8: Charset = Charset.forName("UTF-8")
}
}