토이프로젝트 개발 중 발생한 문제와 그 해결방법에 대하여 기술합니다.
오늘을 기준으로 가장 빠른 휴일을 알려주는 위젯을 만들기 위해 휴일정보 API 를 작성하려 한다.
데이터 소스는 공공기관 API를 통해 하루에 한번씩 업데이트 하도록 합니다.
공공기관 API 를 사용하여 데이터를 가져온다. (server to server 통신)
https://www.data.go.kr/data/15012690/openapi.do
한국천문연구원_특일 정보
(천문우주정보)국경일정보, 공휴일정보, 기념일정보, 24절기정보, 잡절정보를 조회하는 서비스 입니다. 활용시 날짜, 순번, 특일정보의 분류, 공공기관 휴일 여부, 명칭을 확인할 수 있습니다.
www.data.go.kr
해당 API 는 get방식으로 요청하며 반환값은 xml 이다.
위 기능의 개발에서 발생한 문제는 크게 3가지였다.
1. RestTemplate을 사용한 GET요청 시 Encoding 으로 인한 오류
2. xml파싱 과정에서의 parser 관련오류
3. XmlMapper 사용 중 kotlin환경으로 인한 오류
1. RestTemplate을 사용한 GET요청 시 Encoding 으로 인한 오류
RestTemplate 을 통해 호출할 시 기본 URL 및 전달 인자를 셋팅하여야 한다.
Url 호출 시 UriComponentsBuilder 를 사용하여 URI 를 생성하여야 하며 이 과정에서 Url 을 인코딩하는데
Get 방식으로 호출하는 과정에서 포함되어있는 API 키가 인코딩되는 문제가 발생하였다.
따라서 build 메소드 호출 시 true 값을 인자로 추가하여 불필요한 인코딩이 발생하지 않도록 한다.
private fun generateUri(request: HolidayHttpRequest): URI{
val httpBody: MultiValueMap<String, String> = LinkedMultiValueMap()
httpBody["serviceKey"] = API_KEY
httpBody["solYear"] = request.solYear
httpBody["solMonth"] = request.solMonth
return UriComponentsBuilder.fromHttpUrl(END_POINT_URL).queryParams(httpBody).build(true).toUri()
}
프레임워크 내부 구현부에는 아래와 같이 기술되어 있다.
[이미 인코딩되어 있는지 여부를 인자로 받는다.] 라고 기술되어 있다.
/**
* Variant of {@link #build()} to create a {@link UriComponents} instance
* when components are already fully encoded. This is useful for example if
* the builder was created via {@link UriComponentsBuilder#fromUri(URI)}.
* @param encoded whether the components in this builder are already encoded
* @return the URI components
* @throws IllegalArgumentException if any of the components contain illegal
* characters that should have been encoded.
*/
public UriComponents build(boolean encoded) {
return buildInternal(encoded ? EncodingHint.FULLY_ENCODED :
(this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE));
}
또한 restTemplate 을 호출할때 주의사항으로 인자를 반드시 URI 로 주어야 한다.
url으로 해당 메서드를 호출하는 경우 이 단계에서도 인코딩이 추가로 발생한다.
return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, String::class.java)
마지막으로 restTemplate 인스턴스에 인코딩을 설정 해 주어야 한다.
restTemplate.messageConverters.add(0,StringHttpMessageConverter(StandardCharsets.UTF_8))
2. xml파싱 과정에서의 parser 관련오류
기본적으로 json 타입의 반환값을 처리하는 함수만 제공되며 xml 데이터 처리를 통해서는 라이브러리를 추가해야 한다.
gradle 에 의존성을 추가하도록 한다.
https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.0")
이후 XmlMapper 클래스를 사용하여 인코딩을 한다.
3. XmlMapper 사용 중 kotlin환경으로 인한 오류
해당 오류는 kotlin 환경으로 인해 발생한 오류이다. 해당 오류는 2가지의 원인으로 인하여 발생한다.
그 중 첫번째인 호출 단계의 오류이며 원인은 다음과 같다.
public <T> T readValue(String content, Class<T> valueType)
throws JsonProcessingException, JsonMappingException
{
_assertNotNull("content", content);
return readValue(content, _typeFactory.constructType(valueType));
}
호출 시 인자로 xml형식의 문자열, 해당 타입을 받을 데이터 클래스 를 받는다.
문제는 코들린에서의 데이터 클래스는 보통 data class Foo( ... ) 와 같이 data 타입을 생성하는데 이 data class 는 일반 클래스와 구조가 조금 다르다.
xml parser, json parser 는 기본 생성자를 사용하여 동작하는 절차가 있는데 data 클래스는 기본 생성자가 생성되지 않는다.
그리고 뭔가 더 이유가 있다고 하는데 정확하지 않아 거기까지는 기술하지 않는다.
해당 문제에 대한 해결 방법은 아래의 stackoverflow를 참조한다.
https://stackoverflow.com/questions/71458854/kotlin-deserializing-with-jackson-xmlmapper
Kotlin - Deserializing with Jackson XmlMapper
I'm having some trouble to parse correctly an XML file with Jackson XML Mapper. Suppose I have this simple XML: <Test> <Simple> <x>I am x</x> <y>I ...
stackoverflow.com
작성 중인 전체 코드이다.
'springboot' 카테고리의 다른 글
springboot kotlin coroutine 활용한 PromiseAll 같은 병렬처리 (0) | 2022.11.28 |
---|---|
springboot kotlin jsonParse관련 문제 해결 (0) | 2022.11.27 |
스프링부트 + 코틀린 데이터베이스 연동 설정 (0) | 2022.09.01 |
스프링부트 + kotlin - 기본 어노테이션 학습 (0) | 2022.09.01 |
BeanDefinitionStoreException 발생원인 및 해결 방법 (0) | 2022.08.31 |