spring-bootexternalresttemplatespring-resttemplate

Logging both ClientHttpRequestInterceptor and HttpServletRequest (or HandlerInterceptor)


I'm developing an API using Spring Boot 3.1.0 to crawl data, I called this localhost:8080/crawl. Inside this, I also use RestTemplate to call an external API called third-party.com/data.

I want to track API calls both my URL and third-party URL.

Here is my table in Oracle:

create table API_CALLS_TRACKING
(
    ID                           NUMBER(20)   not null
        constraint API_CALLS_TRACKING_PK
            primary key,
    CLIENT_USERNAME              VARCHAR2(20),
    FROM_IP                      VARCHAR2(30),
    API_URI                      VARCHAR2(100),
    REQUEST_METHOD               VARCHAR2(10),
    QUERY_PARAMS                 VARCHAR2(200),
    PATH_PARAMS                  VARCHAR2(500),
    REQUEST_PAYLOAD              VARCHAR2(500),
    USER_AGENT                   VARCHAR2(1000),
    STATUS_CODE                  NUMBER(10),
    MESSAGE                      VARCHAR2(100),
    THIRTY_SERVICE_API_URI       VARCHAR2(100),
    THIRTY_SERVICE_RESPONSE_TIME NUMBER(10),
    CREATED_AT                   TIMESTAMP(6) not null,
    MODIFIED_AT                  TIMESTAMP(6) not null
)

Where THIRTY_SERVICE_API_URI is 3rd-party URL (ex: third-party.com/data) and THIRTY_SERVICE_RESPONSE_TIME is exactly response time of 3rd-party service (I'm using StopWatch to watch response time like this post).

Here is RestTemplateInterceptor:

@Component
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {

    final static Logger log = LoggerFactory.getLogger(RestTemplateInterceptor.class);

    @Autowired
    private ApiCallsTrackingRepo apiCallsTrackingRepo;

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        StopWatch stopwatch = StopWatch.createStarted();
        ClientHttpResponse response = execution.execute(request, body);
        stopwatch.stop();
        ApiCallsTracking apiCallsTracking = ApiCallsTracking
                .builder()
                .requestMethod(request.getMethod().name())
                .thirtyServiceAPIURI(request.getURI().toString())
                .queryParams(request.getURI().getQuery())
                .statusCode(response.getStatusCode().value())
                .message(response.getStatusText())
                .thirtyResponseTime(stopwatch.getTime(TimeUnit.MILLISECONDS))
                .build();
        apiCallsTrackingRepo.save(apiCallsTracking);
        return response;
    }
}

But I can't pass HttpServletRequest to interceptor class. Here is my business flow: MyController <-> call 3rd-API via RestTemplate <-> 3rd-API

Please help me to get request information from my API in interceptor class or guide me a trick to do this.

I want to get HttpServletRequest information in interceptor class. Or another word, I want to track both my API and 3rd-API.


Solution

  • To track the both your API & the 3rd party api using RestTemplate and HttpServletRequest in your interceptor class, you can make use of ThreadLocal variables to store the HttpServletRequest information and retrieve it within the interceptor.

    First, you need to create a ThreadLocal variable in a separate class to store the HttpServletRequest:

    public class RequestHolder {
        private static final ThreadLocal<HttpServletRequest> REQUEST_THREAD_LOCAL = new ThreadLocal<>();
    
        public static void setRequest(HttpServletRequest request) {
            REQUEST_THREAD_LOCAL.set(request);
        }
    
        public static HttpServletRequest getRequest() {
            return REQUEST_THREAD_LOCAL.get();
        }
    
        public static void clear() {
            REQUEST_THREAD_LOCAL.remove();
        }
    }
    

    you can update your interceptor class to store the HttpServletRequest in the RequestHolder:-

    @Component
    public class RestTemplateInterceptor implements ClientHttpRequestInterceptor 
    {
    
        final static Logger log = LoggerFactory.getLogger(RestTemplateInterceptor.class);
    
        @Autowired
        private ApiCallsTrackingRepo apiCallsTrackingRepo;
    
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            HttpServletRequest httpRequest = RequestHolder.getRequest();
    
            String clientUsername = "";         String fromIp = httpRequest.getRemoteAddr();
            String apiUri = httpRequest.getRequestURI();
            String requestMethod = httpRequest.getMethod();
            String queryParams = httpRequest.getQueryString();
            String pathParams = "";
            String requestPayload = ""; 
            String userAgent = httpRequest.getHeader("User-Agent");
    
            StopWatch stopwatch = StopWatch.createStarted();
            ClientHttpResponse response = execution.execute(request, body);
            stopwatch.stop();
    
            ApiCallsTracking apiCallsTracking = ApiCallsTracking
                    .builder()
                    .clientUsername(clientUsername)
                    .fromIp(fromIp)
                    .apiUri(apiUri)
                    .requestMethod(requestMethod)
                    .queryParams(queryParams)
                    .pathParams(pathParams)
                    .requestPayload(requestPayload)
                    .userAgent(userAgent)
                    .statusCode(response.getStatusCode().value())
                    .message(response.getStatusText())
                    .thirtyServiceAPIURI(request.getURI().toString())
                    .thirtyServiceResponseTime(stopwatch.getTime(TimeUnit.MILLISECONDS))
                    .build();
    
            apiCallsTrackingRepo.save(apiCallsTracking);
    
            return response;
        }
    }
    

    To populate the HttpServletRequest in the RequestHolder, you can create an HandlerInterceptor implementation as follows:-

    @Component
    public class RequestInterceptor implement HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            RequestHolder.setRequest(request); 
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
            RequestHolder.clear();
        }
    }
    

    Make sure to the register this interceptor in your Spring Boot configuration. You can do this by implementing the WebMvcConfigurer interface and overriding the addInterceptors method:-

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Autowired
        private RequestInterceptor requestInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(requestInterceptor);
        }
    }
    

    With this setup, the HttpServletRequest information will be stored in the RequestHolder by the RequestInterceptor, and you can retrieve it in the

    RestTemplateInterceptor to track both your API and the third-party API.