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

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/xml 이 application/json 보다 더 우선해서 만들어지게 된다.

 

계속 진행하면 똑같은 이유로 각 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

다른 사람들도 같은 문제 때문에 많이 겪은 듯 했다. 단순히 의존성을 추가함으로써 기존에 작동되던 요청이 갑자기 안되니 그럴 수 있겠구나 생각했지만, 이에 대해 왜 그런 일이 벌어지는지 분석하지 않는 이상 알기 어려울 것 같았다.