10

In my application I implemented Retrofit to call WebServices and I'm using OkHttp to use Interceptor and Authenticator. Some requests need token, and I have implemented Authenticator interface to handle the refresh (following the official documentation). But I have the following issue : time to time in my app I have to call more than one request at once. Because of that, for one of them I will have the 401 error.

Here is my code for request calls :

public static <S> S createServiceAuthentication(Class<S> serviceClass, boolean hasPagination) {

        final String jwt = JWT.getJWTValue(); //Get jwt value from Realm

        if (hasPagination) {
            Gson gson = new GsonBuilder().
                    registerTypeAdapter(Pagination.class, new PaginationTypeAdapter()).create();

            builder =
                    new Retrofit.Builder()
                            .baseUrl(APIConstant.API_URL)
                            .addConverterFactory(GsonConverterFactory.create(gson));
        }

        OkHttpClient.Builder httpClient =
                new OkHttpClient.Builder();

        httpClient.addInterceptor(new AuthenticationInterceptor(jwt));
        httpClient.authenticator(new Authenticator() {
            @Override
            public Request authenticate(Route route, Response response) throws IOException {
                if (responseCount(response) >= 2) {
                    // If both the original call and the call with refreshed token failed,
                    // it will probably keep failing, so don't try again.
                    return null;
                }

                if (jwt.equals(response.request().header("Authorization"))) {
                    return null; // If we already failed with these credentials, don't retry.
                }

                APIRest apiRest = createService(APIRest.class, false);
                Call<JWTResponse> call = apiRest.refreshToken(new JWTBody(jwt));
                try {
                    retrofit2.Response<JWTResponse> refreshTokenResponse = call.execute();
                    if (refreshTokenResponse.isSuccessful()) {

                        JWT.storeJwt(refreshTokenResponse.body().getJwt());

                        return response.request().newBuilder()
                                .header(CONTENT_TYPE, APPLICATION_JSON)
                                .header(ACCEPT, APPLICATION)
                                .header(AUTHORIZATION, "Bearer " + refreshTokenResponse.body().getJwt())
                                .build();
                    } else {
                        return null;
                    }
                } catch (IOException e) {
                    return null;
                }
            }
        });

        builder.client(httpClient.build());
        retrofit = builder.build();

        return retrofit.create(serviceClass);
    }

    private static int responseCount(Response response) {
        int result = 1;
        while ((response = response.priorResponse()) != null) {
            result++;
        }
        return result;
    }

The issue is simple, the first request will refresh the token successfully but others will failed because they will try to refresh a token already refreshed. The WebService return an error 500. Is there any elegant solution to avoid this ?

Thank you !

Guimareshh
  • 1,214
  • 2
  • 15
  • 26

2 Answers2

1

If I understand your issue, some requests are sent while the token is being updated, this gives you an error.

You could try to prevent all the requests while a token is being updated (with a 'synchronized' object) but this will not cover the case of an already sent request.

Since the issue is difficult to avoid completely, maybe the right approach here is to have a good fallback behavior. Handling the error you get when you've made a request during a token update by re-running the request with the updated token for instance.

Rafi Panoyan
  • 822
  • 9
  • 21
-2

Write Service.

public class TokenService extends Service {

private static final String TAG = "HelloService";

private boolean isRunning = false;
OkHttpClient client;
JSONObject jsonObject;
public static String URL_LOGIN = "http://server.yoursite";
String phone_number, password;

@Override
public void onCreate() {
    Log.i(TAG, "Service onCreate");
    jsonObject = new JSONObject();
    client = new OkHttpClient();

    SharedPreferences pref_phone = getSharedPreferences("Full_Phone", MODE_PRIVATE);
    phone_number = pref_phone.getString("Phone", "");

    SharedPreferences pref_password = getSharedPreferences("User_Password", MODE_PRIVATE);
    password = pref_password.getString("Password", "");

    isRunning = true;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    Log.i(TAG, "Service onStartCommand");
    try {
        jsonObject.put("phone_number", phone_number);
        jsonObject.put("password", password);

    } catch (JSONException e) {
        e.printStackTrace();
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (; ; ) {
                try {
                    Thread.sleep(1000 * 60 * 2);
                } catch (Exception e) {
                }

                if (isRunning) {

                    AsyncTaskRunner myTask = new AsyncTaskRunner();
                    myTask.execute();

                } else {

                    Log.d("CHECK__", "Check internet connection");

                }
            }
        }
    }).start();

    return Service.START_STICKY;
}

@Override
public IBinder onBind(Intent arg0) {
    Log.i(TAG, "Service onBind");
    return null;
}

@Override
public void onDestroy() {

    isRunning = false;
    Log.i(TAG, "Service onDestroy");
}

String post(String url, JSONObject login) {
    try {
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, login.toString());
        okhttp3.Request request = new okhttp3.Request.Builder()
                .url(url)
                .post(body)
                .build();

        okhttp3.Response response = client.newCall(request).execute();

        try {
            return response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "";
}

String response;

private class AsyncTaskRunner extends AsyncTask<String, String, String> {

    @Override
    protected void onPreExecute() {
    }

    @Override
    protected String doInBackground(String... params) {

        try {
            response = post(
                    URL_LOGIN, jsonObject);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        Log.d("---OKHTTP---", response);
    }
}

}

Fakhriddin Abdullaev
  • 4,169
  • 2
  • 35
  • 37
  • Can I understand your response ? For me it's completely out of the subject. Moreover I'm using retrofit like I said, not AyncTask – Guimareshh Jun 06 '17 at 11:06