4 분 소요


When logging request information, one approach is to add logs to every REST API controller. However, writing logging code in every API controller can be inefficient.

I’m going to explain how to use Spring Interceptor to intercept requests before they reach the controller’s Handler and perform separate processing.

What is an Interceptor?

As the word suggests, Interceptor means “to catch” or “intercept.” When a Client sends a request to a Server, the Request object first passes through the DispatcherServlet before being delivered to the Controller.

At this point, by placing an Interceptor between the DispatcherServlet and Controller, you can retrieve the Request object in advance.

Advantages of Using Interceptor?

  • Increases code reusability by eliminating duplicate code through common code usage
  • Reduces the risk of code omission since you don’t have to perform repetitive tasks individually

Types of Handler Interceptor Methods

  • preHandle()
    • A function that runs before the request enters the Controller. The Controller proceeds normally only when the return value is true; if false, execution is terminated.
  • postHandle()
    • Executes after the request enters the Controller but before the View is rendered
  • afterCompletion()
    • Executes after the request enters the Controller and after the View has been successfully rendered
  • afterConcurrentHandlingStarted()
    • A function used for asynchronous requests - replaces postHandle() and afterCompletion() methods

Using Handler Interceptor

As explained above, Handler Interceptor is an already existing interface. Therefore, I’m going to implement preHandle using method overriding.

I’ll set this up in 4 steps:

  1. Create a Handler Interceptor implementation and log all requests
  2. Register the implementation so SpringBoot can recognize it
  3. Wrap HttpServletRequest so that requests that can only be read once can be read multiple times
  4. Create a servlet filter that filters HttpServlet requests

Below is my project structure.

.
├── HELP.md
├── README.md
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── springboothandlerinterceptorsimple
    │   │               ├── SpringBootHandlerInterceptorSimpleApplication.java
    │   │               ├── common
    │   │               │   ├── MyLoggingInterceptor.java
    │   │               │   ├── RequestServletFilter.java
    │   │               │   └── RequestServletWrapper.java
    │   │               ├── config
    │   │               │   └── Configuration.java
    │   │               └── controller
    │   │                   └── MyTestController.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── example
                    └── springboothandlerinterceptorsimple
                        └── SpringBootHandlerInterceptorSimpleApplicationTests.java

Creating Handler Interceptor

package com.example.springboothandlerinterceptorsimple.common;


import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class MyLoggingInterceptor implements HandlerInterceptor {

    Logger logger = LoggerFactory.getLogger(MyLoggingInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (Objects.equals(request.getMethod(), "POST")) {
            Map<String, Object> inputMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);

            logger.info("Request Info: " + inputMap);
            logger.info("Request URL: " + request.getRequestURL());

            return true;
        } else {

            logger.info("Request Info: " + request.getQueryString());
            logger.info("Request URL: " + request.getRequestURL());
            return true;
        }
    }
}

Registering Handler Interceptor

Create a Config package and set it up as follows.

package com.example.springboothandlerinterceptorsimple.config;

import com.example.springboothandlerinterceptorsimple.common.MyLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Component
public class Configuration implements WebMvcConfigurer {

    @Autowired
    private MyLoggingInterceptor myLoggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(myLoggingInterceptor);
    }

}

Creating Servlet Wrapper

In Spring, a request can only be read once.

Let’s wrap this request object so it can be read in multiple places.

package com.example.springboothandlerinterceptorsimple.common;

import java.io.IOException;
import java.io.StringReader;
import java.util.Scanner;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class RequestServletWrapper extends HttpServletRequestWrapper {

    private String requestData = null;

    public RequestServletWrapper(HttpServletRequest request) {

        super(request);

        try (Scanner s = new Scanner(request.getInputStream()).useDelimiter("\\A")) {

            requestData = s.hasNext() ? s.next() : "";

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        StringReader reader = new StringReader(requestData);

        return new ServletInputStream() {

            private ReadListener readListener = null;

            @Override
            public int read() throws IOException {

                return reader.read();
            }

            @Override
            public void setReadListener(ReadListener listener) {
                this.readListener = listener;

                try {
                    if (!isFinished()) {

                        readListener.onDataAvailable();
                    } else {

                        readListener.onAllDataRead();
                    }
                } catch (IOException io) {

                    io.printStackTrace();
                }

            }

            @Override
            public boolean isReady() {

                return isFinished();
            }

            @Override
            public boolean isFinished() {

                try {
                    return reader.read() < 0;
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return false;

            }
        };
    }

}
  • Create a constructor that receives HttpServletRequest object and extracts it as a string
    • StringReader reader = new StringReader(requestData);
  • Redefine InputStream with read(), setReadListener(), isFinished(), isReady() implemented

Creating Servlet Filter

package com.example.springboothandlerinterceptorsimple.common;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Component;

@Component
public class RequestServletFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest wrappedRequest = new RequestServletWrapper((HttpServletRequest) request);

        chain.doFilter(wrappedRequest, response);

    }

}
  • Requests are wrapped using the wrapper object and this wrapper object is passed to the filter chain
  • When reading the HttpServletRequest object, it reads the wrapper object even without specific mention (MyLoggingInterceptor class)

Creating Controller

Create a controller class that can receive requests by creating API endpoints.

package com.example.springboothandlerinterceptorsimple.controller;

import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
public class MyTestController {

    @GetMapping("/test-one")
    public Map<String, Object> firstAPI(@RequestParam Map<String, Object> request) {
        return request;
    }

    @PostMapping("/test-two")
    public Map<String, Object> secondAPI(@RequestBody Map<String, Object> request) {
        return request;
    }
}

Testing Requests

Let’s run the server and execute some requests to verify that logging is working correctly.

I created two endpoints in the controller:

  • test-one : GET
  • test-two : POST
  1. GET - localhost:8080/test-one?id=1&name=wool
    • Result
      INFO 14244 --- [nio-8080-exec-7] c.e.s.common.MyLoggingInterceptor        : Request Info: id=1&name=wool
      INFO 14244 --- [nio-8080-exec-7] c.e.s.common.MyLoggingInterceptor        : Request URL: http://localhost:8080/test-one
      
  2. POST - localhost:8080/test-two
    • body : application/json'
      {
        "id":1,
        "name":"wool",
        "phone":"01012341234",
        "mail":"hello@mail.com"
      }
      
    • Result
      INFO 14244 --- [nio-8080-exec-9] c.e.s.common.MyLoggingInterceptor        : Request Info: {id=1, name=wool, phone=01012341234, mail=hello@mail.com}
      INFO 14244 --- [nio-8080-exec-9] c.e.s.common.MyLoggingInterceptor        : Request URL: http://localhost:8080/test-two
      

댓글남기기