web-dev-qa-db-ja.com

OKHttpインターセプターからエラーを返す(後付けを使用)

アプリのネットワークリクエストを行うために、レトロフィットでOkHttpを使用しています。また、認証にインターセプターを使用し、必要に応じてリクエストを再試行しています。

サーバーで一時的な問題が発生することがあり、応答ステータスが200 OKであるにもかかわらず、空の本体が返されます。これにより、アプリがクラッシュします。これは、Retrofit Callbackのsuccessブロックが呼び出され、返された(GSONで解析された)カスタムオブジェクトがnullであり、successコールバックのコードがオブジェクトが返されたと想定しているためです。

これはすでにサーバーチームに報告していますが、アプリ全体のすべての成功コールバックコードをnullチェックでラップすることなく、これも修正したいと思います。

現在、私は2つのオプションに傾いていますが、他のアイデアは大歓迎です: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???
    }
}

どうもありがとう。

14
Murat Ögat

同じシナリオがあり、この投稿はソリューションの実装に役立ちました。正しい方向を指し示す@mastovに感謝します。

エラーが発生した場合でも常にHTTP200を返すバックエンドAPIを使用します。これは私のエラーの応答サンプルでした

{"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;
    }
4
velval

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")
    }
}
1
kengura

カスタム応答を返すと、この問題は解決します。応答を返す前に、このスニペットをインターセプター内に配置します

    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()
0
Arul Mani