Search code examples
javahttpclientinterceptor

Refresh Access Token via Interceptor in apache httpclient


Using the interceptor, I try to update the access token. But the code doesn't work... How can I update the access token using the http client interceptor?

try (CloseableHttpClient httpClient = HttpClients.custom().
            addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "a1", (request, scope, chain) -> {
                ClassicHttpResponse response = chain.proceed(request, scope);
                if (response.getCode() == HttpStatus.SC_UNAUTHORIZED) {
                    userAuthBean.updateAccessTokenFromAPI();
                    request.addHeader("Authorization", "Bearer " + userAuthBean.getAccessToken());
                    chain.proceed(request, scope);
                }
                return response;
            })
            .build())
    {
        HttpGet request = new HttpGet(uri);
        request.addHeader("Authorization", "Bearer " + userAuthBean.getAccessToken());
        try (CloseableHttpResponse response = httpClient.execute(request)) {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                result = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                System.out.println(result);
            }
        } catch (IOException | ParseException ioException) {
            ioException.printStackTrace();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;

Solution

  • This code for retrying the request in case if 403 is obtained, ideally should not be in interceptor. Interceptors are to be generally written straightforward which manipulates the current* request/response and cause the call chain to process. They make your code hideous, and make debugging a lot more plain since the logic is hidden from plain-sight in most of the cases.

    Generally, these interceptors can be best used to log the requests(To ELK/Kafka or wherever) and add missing attributes/headers if any for *every request going through the http client (Even this is a code-smell IMO).

    Instead, what you need is chaining of Function as shown below,

      public static void main(String[] args) throws IOException {
        getWithAuthTokenRetry.apply(new Request.Builder().url("http://google.com").build());
      }
    
      public static Function<Request, Response> get =
          (req) -> {
            OkHttpClient okHttpClient = new OkHttpClient();
            Response res = null;
            try {
              res = okHttpClient.newCall(req).execute(); // Simulate calls!!
            } catch (IOException e) {
              e.printStackTrace();
            }
            return res;
          };
    
      public static Function<Request, Response> getWithAuthTokenRetry =
          request -> {
            Function<Response, Response> authenticateAndRetryFunction =
                res -> {
                  if (res != null && res.code() == 200) { //403 Here
                    String authToken = "Generated AuthToken"; // Simulating new AuthToken
                    Request updatedRequest = request.newBuilder().header("Auth", authToken).build();
                    System.out.println(updatedRequest.headers());
                    res = get.apply(updatedRequest);
                  }
                  return res;
                };
            return get.andThen(authenticateAndRetryFunction).apply(request);
          };
    

    This is a very broad implementation, which does not deal with Exceptions properly, but I guess those can be figured out as per requirement.

    Also, ClosableHttpClient need not be created for every request, create a instance of HttpComponentsClientHttpRequestFactory with ClosableHttpClient.