Spring Filter에서 request Body 가공하기

 

목적 : 컨텐츠 검색시 싱글쿼터( ' )가 포함된 검색어가 있는경우 \를 붙여주기

마이바티스를 사용할수 밖에 없는 쿼리이며 mysql은 자바처럼 싱글쿼터를 \을 붙여줘야 인식하기 때문에

특정 컨트롤러에서 치환해도 되지만 공부할겸 필터에서 가공하는 코드를 작성해봤다.

 

@ServletComponentScan
@SpringBootApplication
public class TestApplication {

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

}
  • @ServletComponentScan 등록
  • 등록해야 @WebFilter, @WebListener and @WebServlet 주석을 scan 할 수 있다.
  • 해당 어노테이션은 스프팅 부트의 임베디드 서블릿 컨테이너에서만 작동한다.

 

@Slf4j
//asyncSupported 없으면 에러
@WebFilter(urlPatterns = {"/v1/test/contents/searchlist", "/v1/test/profile/list"}, asyncSupported = true)
//@Configuration //이거있으면 필터두번실행됨
public class FiltersConfig extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
        final HttpServletRequest requestWrapper = new RequestWrapper(req);
        filterChain.doFilter(requestWrapper, res);
        //filterChain.doFilter(req, res);
    }
}
  • @ComponentScan을 하면 필터가 두번실행된다.
  • "@WebFiter + @ServletComponentScan" 조합으로 한번 등록이 됬는데 한번더 등록하기 때문이다.
  • Webfilter의 속성인 urlPatterns에 필터를 탈 url을 지정해준다
  • asyncSupported가 없으면 에러가 뜨는데 잘 모르겠다 
  • 따로 만든 HttpServletRequest 클래스에서 실질적으로 가공이 일어난다. 이를 doFilter의 인자로 던져주면 된다.

 

@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    private final ObjectMapper objectMapper;
    //private final String body;
    private byte[] rawData;
    private byte[] newData;
//    private ByteArrayInputStream bis;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.objectMapper = new ObjectMapper();
        StringBuilder stringBuilder = new StringBuilder();
        //objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        try {
            InputStream inputStream = request.getInputStream();
            this.rawData = IOUtils.toByteArray(inputStream);
        } catch (IOException e) {
            throw e;
        }

        //요청값 없을때 체크
        try {
            log.info("URL : {}", request.getRequestURI());
            // json 형식으로 유입된 HttpServletRequest를 JSOBObject 형태로 return
            JSONObject jsonObject = MyUtil.readJSONStringFromRequestBody(rawData);
            log.info("jsonObject toString {} ", jsonObject.toString());
            String search = (String) jsonObject.get("search"); //jsonbody의 key값 
            String t_idx = (String) jsonObject.get("t_idx"); //jsonbody의 key값
            jsonObject.put("t_idx", Integer.parseInt(t_idx));
            log.info("search {} ", search);

            if (request.getRequestURI().contains("searchlist")) {
                System.out.println("Gd");
                ContentsListReqDTO _reqDTO = objectMapper.readValue(jsonObject.toString().getBytes(StandardCharsets.UTF_8), ContentsListReqDTO.class);
                if (_reqDTO.getSearch().contains("\'")) {
                    _reqDTO.setSearch(_reqDTO.getSearch().replace("\'", "\\'"));
                    jsonObject.put("search", _reqDTO.getSearch());
                }
            }
            //this.bis = new ByteArrayInputStream(httpRequestBodyByteArray);
            newData = jsonObject.toString().getBytes(StandardCharsets.UTF_8);

            //stringBuilder.append(objectMapper.readValue(jsonObject.toString().getBytes(StandardCharsets.UTF_8), String.class));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //this.body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream bis = new ByteArrayInputStream(newData);
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return bis.available() == 0;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() {
                return bis.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    @Override
    public ServletRequest getRequest() {
        return super.getRequest();
    }

//    public byte[] convertToByteArrays(Object obj) throws Exception {
//        if (obj == null) return null;
//        ByteArrayOutputStream bos = new ByteArrayOutputStream();
//        ObjectOutputStream oos = new ObjectOutputStream(bos);
//        oos.writeObject(obj);
//        oos.flush();
//        return bos.toByteArray();
//    }

//    public Object convertToObject() throws IOException {
//        if (httpRequestBodyByteArray.length == 0) return null; // body가 비어있더라도 잘 처리하도록..
//        return objectMapper.readValue(httpRequestBodyByteArray, Object.class);
//    }
}
  • 톰캣에서 request body를 한번 읽으면 또 읽지 못하도록 막아놨다. (request.getInputStream() 부분)
  • 한번 읽고 byte[] rawData 여기에 임시저장한다.
  • MyUtil.readJSONStringFromRequestBody()에 rawData를 보내서 key-value 형태를 JSONObject형태로 받는다.
  • 특정 Controller에서 받는 DTO를 가공해야하니 jsoinObject를 해당 DTO로 변환 후 가공처리를 한다.
    • 이부분이 찾기 어려웠는데, objectMapper.readValue()를 써서 첫번째 인자는 byte값, 두번재인자는 클래스 타입을 넘겨주면 되더라
  • 가공된걸 바이트 형태로 변환후 newData에 담는다.
  • getInputStream(), getReader()를 오버라이딩하여 필터에서 newDate를 반환하도록 세팅한다.

 

public class MyUtil {

    // json 형식으로 유입된 HttpServletRequest를 JSOBObject 형태로 return
    public static JSONObject readJSONStringFromRequestBody(byte[] rawData) throws JsonProcessingException {
        StringBuffer json = new StringBuffer();
        ObjectMapper mapper = new ObjectMapper();
        String line = null;
        InputStream is = null;
        try {
            is = new ByteArrayInputStream(rawData);
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            while((line = reader.readLine()) != null) {
                json.append(line);
            }

        }catch(Exception e) {
            e.printStackTrace();
            //log.info("Error reading JSON string: " + e.toString());
        }

        System.out.println(json.toString());
        Map<String, String> map = mapper.readValue(json.toString(), Map.class);

        JSONObject jObj = new JSONObject(map);
        return jObj;
    }
    
}

 

 

 

참조

1. https://taetaetae.github.io/2020/04/06/spring-boot-filter/

 

스프링 부트에 필터를 '조심해서' 사용하는 두 가지 방법

웹 어플리케이션에서 필터를 사용하면 중복으로 처리되는 내용을 한곳에서 처리할 수 있다거나 서비스의 다양한 니즈를 충족시키기에 안성맞춤인 장치인것 같다. 필터란 무엇인가 에 대한 내

taetaetae.github.io

 

2. https://www.baeldung.com/spring-servletcomponentscan