Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Spring Interceptor

I want to implement Spring Interceptor in order to print every received and send API XML request. I tried this test code:

@SpringBootApplication
@EntityScan(".....")
public class Application extends SpringBootServletInitializer implements WebMvcConfigurer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();

    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();

    // check if restTeamplate doesn't already have other interceptors
    if (CollectionUtils.isEmpty(interceptors)) { 
        interceptors = new ArrayList<>();
    }

    interceptors.add(new RestTemplateHeaderModifierInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}

@Bean
public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new RestTemplateHeaderModifierInterceptor());
        }
    };
} 
}

Component for logging:

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;

import io.micrometer.core.instrument.util.IOUtils;

@Component
public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor, HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        StringBuilder sb = new StringBuilder();
        sb.append("[ ");
        for (byte b : body) {
            sb.append(String.format("0x%02X ", b));
        }
        sb.append("]");

        LOGGER.debug("!!!!!!!!!!!!!!! Input " + sb.toString());

        System.out.println("!!!!!!!!!!!!!!!");

        ClientHttpResponse response = execution.execute(request, body);    
        InputStream inputStream = response.getBody();    
        String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

        LOGGER.debug("!!!!!!!!!!!!!!! result " + result);

        System.out.println("!!!!!!!!!!!!!!!");

        return response;
    }

}

But nothing is printed into the console in DEBUG mode. Any idea where I'm wrong? Probably this component is not registered or I'm missing some important configuration?

like image 737
Peter Penzov Avatar asked Nov 25 '18 15:11

Peter Penzov


2 Answers

According to your code, you registered an empty list of interceptors in your RestTemplate. Try to change your code as follows:

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();

    List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();

    // check if restTeamplate doesn't already have other interceptors
    if (CollectionUtils.isEmpty(interceptors)) { 
        interceptors = new ArrayList<>();
    }

    interceptors.add(new RestTemplateHeaderModifierInterceptor());
    restTemplate.setInterceptors(interceptors);
    return restTemplate;
}

More info is here.

That interceptor will serve outgoing requests.

For income requests, you have to inherit your interceptor from HandlerInterceptorAdapter:

public class MyIncomeRequestInterceptor extends HandlerInterceptorAdapter {
    //...
}

and then register it with WebMvcConfigurer in the following way, for example:

@Bean
public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyIncomeRequestInterceptor());
        }
    };
}

More info is here.

In both cases, it's not necessary to make beans from your interceptors (you can remove annotation @Component).

UPDATE

A working example:

@Slf4j
@RestController
@ControllerAdvice
@SpringBootApplication
public class Application implements WebMvcConfigurer, ResponseBodyAdvice<Object> {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @GetMapping("/hello")
    public ResponseEntity<?> hello() {
        return ResponseEntity.ok(Map.of("message", "hello"));
    }

    @EventListener
    public void onReady(final ApplicationReadyEvent e) {
        Map result = restTemplate().getForObject("http://localhost:8080/hello", Map.class);
        if (result != null) {
            log.info("[i] Request result: '{}'", result.get("message"));
        }
    }

    @Bean
    public RestTemplate restTemplate() {

        ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate(factory);

        var interceptors = restTemplate.getInterceptors();
        if (CollectionUtils.isEmpty(interceptors)) interceptors = new ArrayList<>();

        interceptors.add(new OutgoingInterceptor());
        restTemplate.setInterceptors(interceptors);
        return restTemplate;
    }

    @Override
    public void addInterceptors(final InterceptorRegistry registry) {
        registry.addInterceptor(new IncomingInterceptor());
    }

    @Override
    public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(final Object body, final MethodParameter returnType, final MediaType selectedContentType, final Class<? extends HttpMessageConverter<?>> selectedConverterType, final ServerHttpRequest request, final ServerHttpResponse response) {
        log.info("[i] ResponseBodyAdvice: response body {}", body);
        return body;
    }

    class OutgoingInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(final HttpRequest request, final byte[] bytes, final ClientHttpRequestExecution execution) throws IOException {
            log.info("[i] Outgoing interceptor: requested URL is '{}'", request.getURI());
            ClientHttpResponse response = execution.execute(request, bytes);
            String body = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset());
            log.info("[i] Outgoing interceptor: response body is '{}'", body);
            return response;
        }
    }

    class IncomingInterceptor implements HandlerInterceptor {
        @Override
        public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView mw) throws Exception {
            log.info("[i] Incoming interceptor: requested URL is '{}'", request.getRequestURL().toString());
        }
    }
}

To log the response body of every metod of controllers IMO it's better to use ResponseBodyAdvice implementation with @ControllerAdvice annotation (see above in the code).

Result:

2019-01-16 14:05:07.260  : [i] Outgoing interceptor: requested URL is 'http://localhost:8080/hello'
2019-01-16 14:05:07.366  : [i] ResponseBodyAdvice: response body {message=hello}
2019-01-16 14:05:07.383  : [i] Incoming interceptor: requested URL is 'http://localhost:8080/hello'
2019-01-16 14:05:07.387  : [i] Outgoing interceptor: response body is '{"message":"hello"}'
2019-01-16 14:05:07.402  : [i] Request result: 'hello'

Repo: sb-web-interceptors-demo

like image 131
Cepr0 Avatar answered Oct 20 '22 05:10

Cepr0


Applying AOP Aspect Oriented Programming for logging, you can do this:

  1. Create an Aspect to intercept before enter to Controller.
  2. Create point cut expression for before and after

Aspect for logging:

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;

@Aspect
@Component
public class LoggingAspect {

    private static final String CONTROLLER_EXPRESION = "within(@org.springframework.stereotype.Controller *) && execution(* *.*(..))";
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    /**
     * Before -> Any resource annotated with @Controller annotation and all method
     * and function taking HttpServletRequest as first parameter.
     *
     * @param joinPoint
     * @param request
     */
    @Before(CONTROLLER_EXPRESION)
    public void logBefore(JoinPoint joinPoint, HttpServletRequest request) {

        log.debug("Entering in Method : {}", joinPoint.getSignature().getName());
        log.debug("Class Name :  {}", joinPoint.getSignature().getDeclaringTypeName());
        log.debug("Arguments :  {}", Arrays.toString(joinPoint.getArgs()));
        log.debug("Target class : {}", joinPoint.getTarget().getClass().getName());

        if (null != request) {
            log.debug("Start Header Section of request ");
            log.debug("Method Type : {}", request.getMethod());
            Enumeration headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement().toString();
                String headerValue = request.getHeader(headerName);
                log.debug("Header Name: {} Header Value : {}", headerName, headerValue);
            }
            log.debug("Request Path info : {}", request.getServletPath());
            log.debug("End Header Section of request ");
        }
    }

    /**
     * After -> All method within resource annotated with @Controller annotation.
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(pointcut = CONTROLLER_EXPRESION, returning = "result")
    public void logAfter(JoinPoint joinPoint, Object result) {
        String returnValue = this.getValue(result);
        log.debug("Method Return value : {}", returnValue);
    }

    /**
     * After -> Any method within resource annotated with @Controller annotation and throws an exception ...Log it 
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(pointcut = CONTROLLER_EXPRESION, throwing = "exception")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable exception) {
        log.error("An exception has been thrown in {} {}", joinPoint.getSignature().getName(), " ()");
        log.error("Cause : {}", exception.getCause());
    }

    /**
     * Around -> Any method within resource annotated with @Controller annotation. 
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(CONTROLLER_EXPRESION)
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();
        try {
            String className = joinPoint.getSignature().getDeclaringTypeName();
            String methodName = joinPoint.getSignature().getName();
            Object result = joinPoint.proceed();
            long elapsedTime = System.currentTimeMillis() - start;
            log.debug("Method {}.{} () execution time :  {} ms", className, methodName, elapsedTime);

            return result;
        } catch (IllegalArgumentException e) {
            log.error("Illegal argument {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName());
            throw e;
        }
    }

    private String getValue(Object result) {
        String returnValue = null;
        if (null != result) {
            if (result.toString().endsWith("@" + Integer.toHexString(result.hashCode()))) {
                returnValue = ReflectionToStringBuilder.toString(result);
            } else {
                returnValue = result.toString();
            }
        }
        return returnValue;
    }
}
like image 30
Jonathan JOhx Avatar answered Oct 20 '22 04:10

Jonathan JOhx