본문 바로가기

Spring Boot

[Spring Boot] LoggerFilter만들기

728x90

Class를 생성하여 Filter 인터페이스를 상속을 받습니다

 

Filter 인터페이스 내용

import java.io.IOException;

public interface Filter {
    // Filter 생성 시 호출되는 메소드
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
    
    // Filter 삭제 시 호출되는 메소드
    default void destroy() {
    }
}

doFilter 메서드:

  • doFilter(ServletRequest request, ServletResponse response) 메서드를 호출하여 체인의 다음 필터로 요청과 응답을 전달합니다.
  • 이 메서드가 호출되지 않으면 요청이 다음 필터 또는 서블릿으로 전달되지 않고 현재 필터에서 처리됩니다.
  • 각 필터는 필요에 따라 doFilter 메서드 호출 전에 요청을 처리하거나, 호출 후에 응답을 처리할 수 있습니다.

doFilter()메소드를 활용하여 LoggerFilter 만들기

import jakarta.servlet.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component // 빈으로 등록
public class LoggerFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 서비스 로직 시작 전
        filterChain.doFilter(servletRequest, servletResponse);
        // 서비스 로직 종료 후
    }
    
}

 

ServletRequest: 요청의 정보를 가진 객체

  • 클라이언트가 서버에 보낸 요청에 대한 모든 정보를 포함합니다.
  • 요청 파라미터, 속성, 세션 정보, 요청 본문 등의 데이터를 접근할 수 있는 메서드를 제공합니다.

ServletResponse: 응답의 정보를 가진 객체

  • 서버가 클라이언트에게 보낼 응답에 대한 모든 정보를 포함합니다.
  • 응답 본문 작성, 상태 코드 설정, 콘텐츠 타입 지정 등의 작업을 수행할 수 있는 메서드를 제공합니다.

FilterChain: 필터 체인을 통해 요청과 응답을 전달하는 객체

  • 필터 체인은 요청이 들어올 때 여러 필터를 차례대로 적용할 수 있게 해줍니다.
  • 각 필터는 요청을 처리하고, 체인의 다음 필터로 요청을 전달하거나, 필요에 따라 응답을 생성할 수도 있습니다.
  • 필터 체인을 통해 요청이 최종적으로 서블릿에 도달하거나, 응답이 클라이언트에게 반환되기 전에 여러 단계의 처리를 거칠 수 있습니다.

 

실습

도서관 사서가 책을 저장하는 프로그램 만들기

@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class BookRequest {

    private String bookName; // 책 이름

    private String category; // 카테고리

    private String author;   // 저자

    private LocalDate publicationDate; // 발행 날짜

}
@Slf4j
@RestController
@RequestMapping("/api/book")
public class BookApiController {

    @PostMapping("")
    public BookRequest saveBook(
            @RequestBody
            BookRequest bookRequest
    ) {
        log.info("저장하는 책 정보 : {}", bookRequest.toString());

        return bookRequest;
    }

}
@Slf4j
@Component
public class LoggerFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        var req = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
        var res = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);


        filterChain.doFilter(req, res);

        var reqJson = new String(req.getContentAsByteArray());
        log.info("request : {}", reqJson);
        var resJson = new String(res.getContentAsByteArray());
        log.info("response : {}", resJson);
        res.copyBodyToResponse();
    }

}

 

Logger Filter 클래스 코드 설명

var req = new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
var res = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
  • (HttpServletRequest)로 형변환
    • ServletRequest는 HTTP 프로토콜뿐 아니라 다양한 프로토콜을 처리할 수 있는 인터페이스입니다.
    • HTTP로 처리하기 위해 ServletRequest를 상속받는 HttpServletRequest로 형변환해줍니다.
  • ContentCachingRequestWrapper로 객체 생성하기
    • HttpServletRequest 인터페이스를 구현한 클래스인 HttpServletRequestWrapper라는 클래스로 요청한 데이터를 확인할 수 있습니다.
    • 하지만 HttpServletRequestWrapper로 데이터를 보기 위해서는 데이터를 추출하는 BufferedReader로 반환하는 getReader() 메소드 밖에 없습니다.
    • HttpServletRequestWrapper를 상속받은 ContentCachingRequestWrapper에 있는 메소드로 추출한 데이터를 다시 객체에 넣을 수 있습니다.
  • new String(getContentAsByteArray())
    • 객체에 있는 데이터를 byte array로 추출합니다.
    • String 클래스는 인자로 받을 수 있는 값 중 byte[]를 인자로 받을 수 있습니다.
    • byte[]를 String으로 바꾸어 내용을 확인합니다.
  • res.copyBodyToResponse()
    • 해당 메소드는 추출한 데이터를 다시 복원시키는 메소드입니다.
    • 해당 메소드를 사용하지 않으면 응답하는 데이터가 존재하지 않습니다.
    • 해당 메소드를 사용하기 위해 ContentCachingResponseWrapper 클래스를 사용합니다.

 

결과 화면

클라이언트가 서버에 보내는 데이터

{
    "name": "Spring boot 열공하자",  // bookName과 일치하지 않음
    "category": "IT",
    "author": "철수",
    "publicationDate": "2024-07-17"  // snake_case 형식과 일치하지 않음
}

 

서버가 받는 데이터

저장하는 책 정보 : BookRequest(bookName=null, category=IT, author=철수, publicationDate=null)

 

서버의 LoggerFilter에서 확인한 클라이언트가 보낸 데이터

 

request : {
    "name": "Spring boot 열공하자",
    "category": "IT",
    "author": "철수",
    "publicationDate": "2024/07/17"
}

 

서버가 클라이언트에게 보낸 데이터

response : {"book_name":null,"category":"IT","author":"철수","publication_date":null}

 

클라이언트가 받은 response

{
    "book_name": null,
    "category": "IT",
    "author": "철수",
    "publication_date": null
}
728x90