feign Decoder 오버라이드를 통해 날씨 API 요청에 대한 text/xml 응답 처리하기

공병주(Chris)·2023년 9월 25일
0
post-thumbnail

공공데이터 포털 단기예보 text/xml 응답 처리하기

Dandi에서 공공 데이터 포털 데이터 중 기상청의 단기예보 API를 통해 날씨를 받아오는 작업을 Batch를 통해 진행하고 있습니다.

데이터 요청시 application/json으로 데이터를 받을지, application/xml로 받을지 요청시에 선택할 수 있습니다.

queryString에 ?dataType=JSON 혹은 ?dataType=XML을 추가해주면됩니다.

JSON으로 요청할 시에 날씨 데이털가 아래와 같은 형식으로 응답됩니다.

HTTP/1.1 200 OK (1062ms)
access-control-allow-origin: *
content-language: ko-KR 
content-type: application/json;charset=UTF-8
date: Fri, 22 Sep 2023 07:09:29 GMT 
server: NIA API Server 
set-cookie: JSESSIONID=TdcdMtk3DSg88eYGFiJrFGcnUYryTJFgrNZcnWyX2xVTDRj3nKqHhX3FBmcclaEL.amV1c19kb21haW4vbmV3c2t5Mw==; Path=/1360000/VilageFcstInfoService_2.0; HttpOnly; Domain=apis.data.go.kr 
transfer-encoding: chunked
{
	"response":
		{
			"header":
			{
				"resultCode":"00",
				"resultMsg":"NORMAL_SERVICE"
			}, 
			"body":
				{
					"dataType":"JSON",
					"items":{
						"item":[
							{
								"baseDate":"20230922",
								"baseTime":"1400",
								"category":"TMP",
								"fcstDate":"20230922",
								"fcstTime":"1500",
								"fcstValue":"26",
								"nx":52,
								"ny":60
							}
						}
					}
				}
		}
}

문제상황

하지만, JSON으로 요청을 해서 데이터를 잘 받아오는 와중에 간헐적으로, 불규칙적인 시점에 아래와 같은 예외가 발생했습니다.

Caused by: feign.codec.DecodeException: 
		Could not extract response: 
				no suitable HttpMessageConverter found for response type 
				[class dandi.dandi.weather.adapter.out.kma.dto.WeatherResponses] 
				and content type [text/xml;charset=UTF-8]

JSON으로 요청했는데, text/html으로 응답이 온다니? 당황스러웠습니다.

따라서, 실제 응답 값 로그를 확인해보니 아래와 같은 값이 응답되는 것을 확인할 수 있었습니다.

HTTP/1.1 200 OK (15097ms)
access-control-allow-origin: *
content-length: 212
content-type: text/xml;charset=UTF-8
date: Fri, 22 Sep 2023 07:09:12 GMT
server: NIA API Server

<OpenAPI_ServiceResponse>
	<cmmMsgHeader>
		<errMsg>SERVICE ERROR</errMsg>
		<returnAuthMsg>HTTP ROUTING ERROR</returnAuthMsg>
		<returnReasonCode>04</returnReasonCode>
	</cmmMsgHeader>
</OpenAPI_ServiceResponse>

원인 분석

1. 성공 여부 혹은 에러 종류에 따라 다른 응답의 content-type

API 문서에는 1개의 성공 응답 값과 16개의 실패 응답 값이 정의되어 있습니다.

성공적인 날씨 데이터를 응답받을 경우엔, application/json 형식의 데이터가 응답됩니다.

하지만, 실패한 응답의 경우엔 에러의 종류에 따라 application/json 혹은 text/xml 형식으로 에러가 응답됩니다.

(application/json 응답)

잘못된 파라미터로 요청을 한 에러 응답의 경우에는 아래와 같이 application/json으로 응답됩니다.

HTTP/1.1 200 OK (101ms)
access-control-allow-origin: *
content-language: ko-KR
content-length: 103
content-type: application/json;charset=UTF-8
date: Thu, 28 Sep 2023 06:35:01 GMT
server: NIA API Server
set-cookie: JSESSIONID=6VMY9cDkj4GfCTqR4d9X7eDma4qkwAOC6C8HdyNwl6inTEH7ncIzJdZzuWZDYzIa.amV1c19kb21haW4vbmV3c2t5Mw==; Path=/1360000/VilageFcstInfoService_2.0; HttpOnly; Domain=apis.data.go.kr

{
	"response":
		{
			"header":
			{
				"resultCode":"10",
				"resultMsg":"최근 3일 간의 자료만 제공합니다."
			}
		}
}

(text/xml 응답)

하지만, 등록되지 않은 키로 API 요청을 보내면 아래와 같이 text/xml로 응답됩니다.

HTTP/1.1 200 OK (15097ms)
access-control-allow-origin: *
content-length: 212
content-type: text/xml;charset=UTF-8
date: Fri, 22 Sep 2023 07:09:12 GMT
server: NIA API Server

<OpenAPI_ServiceResponse>
	<cmmMsgHeader>
		<errMsg>SERVICE ERROR</errMsg>
		<returnAuthMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</returnAuthMsg>
		<returnReasonCode>30</returnReasonCode>
	</cmmMsgHeader>
</OpenAPI_ServiceResponse>

2. 공공 데이터 포털

공공 데이터 포털은 공공 기관에서 제공하는 여러 데이터에 대한 요청을 중계하는 중계소입니다.

따라서, 공공 데이터 포털은 데이터 요청을 받으면 해당 데이터를 관리하는 공공 기관으로 요청을 ROUTING 해주는 방식입니다.

2-1. HTTP ROUTING ERROR

공공 데이터 포털 HTTP ROUTING ERROR 문의글

공공 데이터 포털에서 공공 기관으로 중계한 요청에 대하여 데이터를 제공하는 공공 기관에서 응답이 없거나 정상적이지 않은 응답을 보낼 경우에 포털에서 출력되는 메시지 입니다.

저의 경우에는 공공데이터 포털에서 기상청(데이터를 제공하는 공공 기관)으로 ROUTING이 되지 않아서 아래와 같은 응답을 받았습니다.

HTTP/1.1 200 OK (15097ms)
access-control-allow-origin: *
content-length: 212
content-type: text/xml;charset=UTF-8
date: Fri, 22 Sep 2023 07:09:12 GMT
server: NIA API Server

<OpenAPI_ServiceResponse>
	<cmmMsgHeader>
		<errMsg>SERVICE ERROR</errMsg>
		<returnAuthMsg>HTTP ROUTING ERROR</returnAuthMsg>
		<returnReasonCode>04</returnReasonCode>
	</cmmMsgHeader>
</OpenAPI_ServiceResponse>

위 2개의 문제로 인해 application/json으로 데이터를 요청했지만 text/xml을 응답받았고, Decoder가 decoder를 하지 못했던 것입니다.

해결방안

text/xml 처리할 수 있는 Decoder 등록하기

공식 문서에 나와있듯이 Feign에서는 사용자가 직접 Decoder를 등록하여 Default Decoder를 오버라이드 할 수 있습니다.

그렇다면 Override하는 Decoder는 아래 2개의 요구 사항을 만족해야합니다.

  1. 기상청에서의 application/json 응답에 대해서는 기존의 Feign의 Decoder와 동일하게 동작해야 한다.
  2. 공공 데이터 포털 혹은 기상청의 text/xml 에러 응답의 경우를 처리할 수 있어야한다.
    1. 기상청에서의 text/xml 에러 응답의 경우에는 에러 응답을 추출해서 직접 응답 객체를 만들어준다.
    2. 공공데이터 포털의 HTTP ROUTING ERROR의 text/xml의 경우에는, 일시적인 ROUTING ERROR이기 때문에 Batch 재시도 할 수 있는 WeatherRequestRetryableException을 발생시킨다.
    3. HTTP ROUTING ERROR가 아닌 text/xml의 경우에는, 새로운 문제 상황이 발생했으므로 Batch 재시도를 하지 않게 하는 WeatherRequestFatalException을 발생시킨다. 그럼, Batch를 실행쪽에서 slack 알람이 오게 한다.

2-c번 방식에 대해서, HTTP ROUTING ERROR가 아닌 상황이 일시적인 문제일 수도 있고 재시도를 통해 해결할 수도 있습니다. 만약 재시도 후에 성공하게 된다면, 개발자가 다른 에러 응답을 인지할 수 없기 때문에 위와 같은 로직을 결정했습니다.

또한, 기상청과 공공 데이터 포털의 응답 값이 다르기 때문에 하나의 객체로 관리하는 것이 좋지 않다고 생각했습니다. 따라서, 날씨 응답이라는 WeatherResponse 인터페이스를 정의했습니다. 그리고 해당 인터페이스를 구현하는 기상청 응답 객체와 공공 데이터 포털의 응답 객체를 정의하여

  1. 위의 표 사진에서 보았던 API 문서에 나열되어 있는 기상청에서의 응답의 경우에는 KmaWeatherResponse(기상청 응답)을 반환하고
  2. 공공 데이터 포털에서의 에러 응답의 경우에는 DataPortalErrorWeatherResponse을 반환하도록 했습니다.

0개의 댓글