catsridingCATSRIDING|OCEANWAVES
Dev

Spring WebClient text/html 응답 처리하기

jynn@catsriding.com
Dec 03, 2023
Published byJynn
999
Spring WebClient text/html 응답 처리하기

Resolve text/html Response with Spring WebClient

국가 기관에서 제공하는 공공 API를 확인하던 중 응답 페이로드는 JSON 형식인데 응답 헤더의 Content-Type이 text/html로 전송되어 Spring WebClient에서 UnsupportedMediaTypeException 예외가 발생하였습니다.

이렇게 설계된 것 자체가 특이한 상황이기 때문에 해당 서버의 API를 수정하는 것이 정상적인 처리 과정이겠지만 공공 API라서 어떻게 할 수 없는 상황입니다. 다행히 Stack Overflow에서 동일한 이슈를 발견하여 어렵지 않게 해결할 수 있었습니다.

Issue

해당 이슈는 WebClient에서 학교 기본 정보 NEIS Open API를 호출하고,

SchoolInfoApiPayload payload = createWebClient()  
        .get()  
        .uri(uriBuilder -> uriBuilder  
                .path("/hub/schoolInfo")  
                .queryParam("Type", "json")  
                .queryParam("SCHUL_NM", "서울과학고등학교")  
                .build())  
        .retrieve()  
        .bodyToMono(SchoolInfoApiPayload.class)  
        .block();

응답 페이로드를 그 스펙에 대응하는 객체 타입으로 바인딩 하려고 했을 때 발생하였습니다.

Content type 'text/html;charset=UTF-8' not supported for bodyType=dev.catsriding.openapi.WebClientTest$SchoolInfoApiPayload
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html;charset=UTF-8' not supported for bodyType=dev.catsriding.openapi.WebClientTest$SchoolInfoApiPayload

학교 기본 정보 NEIS Open API 스펙은 다음 HTTP 요청을 통해 확인할 수 있습니다.

GET /hub/schoolInfo?Type=json&SCHUL_NM=서울과학고등학교 HTTP/1.1
Content-Type: application/json
Cookie: WMONID=MqN60S8Elb0
Host: open.neis.go.kr
Connection: close
User-Agent: RapidAPI/4.2.0 (Macintosh; OS X/14.1.2) GCDHTTPRequest

Resolve

Stack Overflow에서 제안한 방법과 이런저런 시도 끝에 세 가지 방안이 마련되었습니다.

  • exchangeStrategies()를 활용하여 text/html 형식도 디코딩 할 수 있도록 Jackson Codec을 설정하는 방법
  • 응답 헤더 Content-Type 값을 application/json으로 바꿔치기하는 방법
  • String 문자열에 응답 페이로드를 담은 다음 JSON으로 역직렬화 하는 방법

ExchangeStrategies

WebClient.builder() 팩토리 메서드를 통해 WebClient 인스턴스를 생성할 때, exchangeStrategies()text/html 형식도 디코딩 할 수 있도록 Jackson Codec을 구성합니다.

private WebClient createWebClient() {  
    return WebClient.builder()  
            .baseUrl("https://open.neis.go.kr")  
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)  
            .exchangeStrategies(ExchangeStrategies.builder().codecs(this::acceptedCodecs).build())  
            .build();  
}

private void acceptedCodecs(ClientCodecConfigurer configurer) {  
    configurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonDecoder(new ObjectMapper(), TEXT_HTML));  
    configurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonEncoder(new ObjectMapper(), TEXT_HTML));  
}

Codec을 추가한 WebClient 인스턴스로 다시 API를 요청해 보면 예외 없이 정상적으로 응답 페이로드가 객체에 바인딩 되었습니다.

resolve-text-html-response-with-spring-webclient_0.png

Deserialize JSON

이번에는 응답 페이로드에 담긴 JSON 데이터를 String 문자열로 받은 다음 ObjectMapper를 통해 JSON으로 역직렬화하는 방법입니다.

String response = createWebClient()  
        ...
        .bodyToMono(String.class)  
        .block();

ObjectMapper objectMapper = new ObjectMapper();  
SchoolInfoApiPayload payload = objectMapper.readValue(response, SchoolInfoApiPayload.class);

요청 처리 결과 동일한 결과를 얻을 수 있었습니다.

resolve-text-html-response-with-spring-webclient_1.png

  • Spring
  • Reactive