aop, interceptor, filter 로 id 존재하는지 검사
상황 :
- 컨트롤러에 id를 받는 부분이 있다.
- 쿼리스트링으로 직접 받는 api도 있고, RequestBody로 dto안에서 받아내는 부분도 있다.
- 해당 id를 통해 DB에 data가 존재하는지 확인하고싶다.
aop 구현시
- 간단하게 어노테이션으로 구현
- 쿼리스트링으로 받는 경우는 간편하게 어노테이션 붙이는 것만으로 해결 다.
문제 1
- RequestBody로 받아내는 경우의 api마다 받아내는 dto가 다르다.
해결방법 :
1. dto 부모를 만들어 해당 dto 안에 uid 넣어서 @Around 내부에 해당 dto 선언하여 처리하는 방법
2. aop 구현부 내부에 if else로 instance 구분하여 처리하여 꺼내는 방법
문제 2
- 현재 프로젝트에는 에러시에도 200을 던지고 리턴코드로 구분
- aop를 사용하는 경우 throw를 던져야 하는데 정해진 형식을 맞추려면 많은 작업해야함.
interceptor 구현시
- 스프링 interceptor 등록 후 적용할 url만 등록하면 쉽게 가능
- 쿼리스트링은 문제가 안된다.
문제
- RequestBody로 받아내는 경우 dto를 구분안해도 되지만, interceptor에서 해당 json을 읽어낸 경우
컨트롤러에서 읽을 수가 없다. (stream이 닫힘)
- 한번 읽은 데이터는 다시 읽을 수 없는 것으로 보임.
- 넘겨주는 방식으로 하려면 컨트롤러 부분 대거 공사들어가야함
filter 구현시
- 인터셉터와 비슷하나 작동하는 위치가 다르다.
RequestBody로 받아내는 경우 dto를 구분안해도 되지만,
interceptor에서 해당 json을 읽어낸 경우 컨트롤러에서 읽을 수가 없다. (stream이 닫힘)
결국은 이게 문제였다.
해결 :
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
private final String requestData;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestData = requestDataByte(request);
}
private String requestDataByte(HttpServletRequest request) throws IOException {
ServletInputStream inputStream = request.getInputStream();
byte[] rawData = StreamUtils.copyToByteArray(inputStream);
return new String(rawData);
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(this.requestData.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException();
}
@Override
public int read() {
return inputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
래퍼로 감싸고, 필터 내부에서 복사해서 사용하는 느낌으로 가야한다.
한번 읽어오면 사라지기 때문
@Slf4j
@Component
@RequiredArgsConstructor
public class CheckUidFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String uid = null;
String contentType = request.getContentType();
RequestWrapper requestWrapper = new RequestWrapper(request);
// json
if (contentType != null && contentType.contains("application/json")) {
StringBuilder sb = getBodyToStringBuilder(response, filterChain, requestWrapper);
JSONObject json = null;
try {
json = new JSONObject(sb.toString());
} catch (JSONException e) {
throw new RuntimeException(e);
}
uid = json.optString("id"); // JSON 데이터에서 id 추출
} else { //param
uid = request.getParameter("id");
}
try {
//비즈니스 로직
log.info("NO_EXIST_CLIENT");
return;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
filterChain.doFilter(requestWrapper, response);
}
private StringBuilder getBodyToStringBuilder(HttpServletResponse response, FilterChain filterChain, RequestWrapper requestWrapper) throws IOException, ServletException {
StringBuilder sb = new StringBuilder();
BufferedReader br = null;
//한줄씩 담을 변수
String line = "";
try {
ServletInputStream inputStream = requestWrapper.getInputStream();
if (inputStream != null) {
br = new BufferedReader(new InputStreamReader(inputStream));
while ((line = br.readLine()) != null) {
sb.append(line);
}
}
} catch (IOException e) {
log.debug("body에 요청이 없습니다.");
filterChain.doFilter(requestWrapper, response);
return null;
}
return sb;
}