계기
회사에서 내가 만들어둔 라이브러리를 다른 팀에서 사용하고 있는 경우가 있었는데, 어느 날 갑자기 다른 팀에서 아래와 같은 메세지가 나타난다고 확인 요청이 왔다.
java.lang.IllegalStateException: org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: "{"errors":["failed to parse JSON input: invalid character '\u003c' looking for beginning of value"]}<EOL>"
이게 갑자기 나타날리가 없어서 어떤 작업을 한 후에 나타났냐고 물으니, build.gradle 에 아래와 같은 의존성을 추가하면 위와 같은 에러가 나타난다고 했다.
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.3'
그 분이 전달한 Stacktrace 를 살펴보니 RestTemplate 의 exchange 를 사용한 부분에서 예외가 발생하고 있었다.
RestTemplate 을 사용하고 있었으므로 단순히 요청을 로그로 찍어보니 요청 Header 에서 Content-Type 이 application/json 이 아닌 application/xml 으로 전달하고 있어서 나중에 응답 데이터를 받고 역직렬화를 할 때 오류가 나는 것이었다. 즉, 원인은 역직렬화 문제였다.
그런데 왜?
의존성을 추가할 때 라이브러리의 auto configruation으로 자동 설정될 수는 있다. 근데 그보다 왜 하필이면 RestTemplate 을 이용할 때 저 의존성을 추가한다고 무슨 일이 벌어져서 저런 일이 일어나는지가 너무나도 궁금했다.
그래서 내부 코드를 분석해봤다.
분석
문제의 부분이다. 즉, 여기서 exchange 메서드를 실행하다가 실패했다. 따라가봤다.
위 라인에서 사실상 요청 데이터가 만들어지고, 그 아래에 있는 776번 라인에서 요청 데이터가 전송된다.
AcceptHeaderRequestCallback 을 상속하는 HttpEntityRequestCallback 클래스에서 이를 처리하고 있다.
실질적인 Request 처리는 위와 같이 부모 클래스에서 처리하고 있으므로 super 를 따라가보면...
현재 등록된 MessageConverter 들 중에서 GenericConverter 에서 읽을 수 있는 경우, 이를 accept 로 추가하는 부분이 보인다.
그런데 한 가지 특징이 MessageConverter 를 보았을 때 순서가 이상했다. 위 사진에서 등록된 Converter 들을 보았을 때, MappingJackson2XmlHttpMessageConverter 가 MappingJackson2HttpMessageConverter 보다 더 우선하고 있었던 것이었다.
아래를 보면 위 로직이 실행될 때 accept header가 만들어지는 부분에서 우선 순위로 인해 application/json이 아닌 application/xml로 만들어지게 된다.
계속 진행하면 똑같은 이유로 각 MessageConverter 들을 찾을 때 사용하기 적절한 Converter 를 찾고 나면 write 할 때 해당 Converter 를 사용하도록 하고 바로 반환하는 로직이 있다.
실제로 Converter 를 계속 타고 가다보면 AbstractGenericHttpMessageConverter 가 있고, 위 부분에서 Content-Type 을 설정하는 부분이 있다. 이를 통해 MessageConverter 가 이용될 경우, 로드된 순서가 다르면 요청이 다르게 전송될 수 있다.
해결
원인이 되었던 RestTemplate 를 사용한 코드에 직접 Header 에 Content-Type 을 넣고 요청해서 처리되도록 하였다.
이걸 알고 나중에 인터넷에서 찾아보니 나처럼 Header 에 넣고 요청하거나 RestTemplate 을 Bean 으로 등록할 때 임시적으로 MessageConverter 의 순서를 바꾸어서 요청하도록 처리하는 방법들이 stackoverflow 에 이미 존재했다.
다른 사람들도 같은 문제 때문에 많이 겪은 듯 했다.
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring] OpenFeign @SpringQueryMap 사용 시 별도 파라미터로 사용하기 (0) | 2024.11.15 |
---|---|
[JPA] QueryDSL에서 enumPath 사용하기 (0) | 2023.11.30 |
[Spring] 컬럼이 많은 상황에서 Reflection 시 ConversionService 활용해보기 (0) | 2022.11.21 |
[Spring] AutoConfiguration 직접 만들어서 라이브러리로 만들어보기 (0) | 2022.11.11 |
[JPA] QueryDSL에서 오늘 날짜 활용해서 사용하기 (feat. Oracle) (0) | 2022.10.24 |