계기

회사에서 내가 만들어둔 라이브러리를 다른 팀에서 사용하고 있는 경우가 있었는데, 어느 날 갑자기 다른 팀에서 아래와 같은 메세지가 나타난다고 확인 요청이 왔다.

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 에 이미 존재했다.

 

https://stackoverflow.com/questions/47894619/spring-resttemplate-message-converter-priority-when-posting

 

Spring RestTemplate message converter priority when posting

What is the most convenient way to influence the priority of the message converters Spring applies when POSTing with RestTemplate? Use case: I want to ensure a given entity is POSTed as JSON rathe...

stackoverflow.com

다른 사람들도 같은 문제 때문에 많이 겪은 듯 했다.