java 从 OKHttp 拦截器返回错误(使用改造)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32669623/
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-11-02 20:35:43  来源:igfitidea点击:

Returning error from OKHttp interceptor (using retrofit)

javaandroidretrofitinterceptorokhttp

提问by Murat ?gat

I am using OkHttp with Retrofit to make my app's network requests. I am also using Interceptors for Authentication and retrying requests if necessary.

我正在使用 OkHttp 和 Retrofit 来发出我的应用程序的网络请求。如有必要,我还使用拦截器进行身份验证和重试请求。

The server sometimes has temporary problems, and returns an empty body although the response status is 200 OK. This causes my app to crash, because the success block of the Retrofit Callback is called, the custom object returned (and parsed with GSON) is null, and the code in success callback assumes an object is returned.

服务器有时会出现临时问题,尽管响应状态为 200 OK,但返回空正文。这会导致我的应用程序崩溃,因为调用了 Retrofit Callback 的成功块,返回的自定义对象(并使用 GSON 解析)为 null,并且成功回调中的代码假定返回了一个对象。

I have already reported this to the server team, but I want to fix it as well, without having to wrap all success callback code all over the app with null checks.

我已经向服务器团队报告了这个问题,但我也想修复它,而不必用空检查将所有成功回调代码包装在整个应用程序中。

Currenty I am inclined to two options, although any other ideas are most welcome: 1) Not returning from the interceptor (is this even possible?) and just displaying an error dialog 2) Returning something that will make Retrofit call the failure part of the callback.

目前我倾向于两种选择,尽管任何其他想法都是最受欢迎的:1)不从拦截器返回(这甚至可能吗?)并且只显示一个错误对话框 2)返回一些会使 Retrofit 调用失败部分的东西打回来。

My code is below. As you may see, I retry the request for a maximum of 3 times when an empty body is received.

我的代码如下。如您所见,当收到空正文时,我最多重试请求 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???
    }
}

Thanks a lot.

非常感谢。

回答by velval

Just had the same scenario and this post helped me implementing the solution. Thanks to @mastov to point to the right direction.

刚刚遇到了相同的情况,这篇文章帮助我实施了解决方案。感谢@mastov 指出正确的方向。

Working with a back-end api that always returns HTTP 200 even if there was an error. This was my response sample of an error

使用后端 api,即使出现错误也始终返回 HTTP 200。这是我的错误响应示例

{"status":403,"message":"Bad User credentials","time":1495597740061,"version":"1.0"}

Here is a simple implementation to complement this answer.

这是一个简单的实现来补充这个答案。

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

回答by kengura

My solution taken from okhttp3.logging.HttpLoggingInterceptor

我的解决方案取自 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")
    }
}

回答by Arul Mani

Return custom response would solve this problem. put this snippet inside interceptor before return respone

返回自定义响应将解决此问题。在返回响应之前将此代码段放入拦截器中

    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()