문제

<200 OK OK,<OpenAPI_ServiceResponse>
	<cmmMsgHeader>
		<errMsg>SERVICE ERROR</errMsg>
		<returnAuthMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</returnAuthMsg>
		<returnReasonCode>30</returnReasonCode>
	</cmmMsgHeader>
</OpenAPI_ServiceResponse>,[Access-Control-Allow-Origin:"*", Content-Type:"text/xml;charset=UTF-8", Content-Length:"229", Date:"Thu, 24 Oct 2024 15:24:30 GMT", Server:"NIA API Server"]>

기상청 단기예보 api를 사용하는데 위와 같은 응답이 왔다.

 

기상청 단기예보 err_code

api에서 제공하는 에러 코드이다.

 

공공데이터 API는 위와 같이 인코딩 키와 디코딩 키를 둘 다 주고 잘 동작하는 키를 사용하라고 한다.

 

접근

1. 인코딩키와 디코딩키를 둘 다 사용해 본다. => 실패

 

2. 키가 전송되면서 바뀌나?

String apiUrl = "~~~~";

ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, String.class);

exchange를 따라 들어가 apiUrl이 바뀔 거 같은 부분을 디버깅해 보자.

 

    @Nullable
    public <T> T execute(String uriTemplate, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
        URI url = this.getUriTemplateHandler().expand(uriTemplate, uriVariables);
        return this.doExecute(url, uriTemplate, method, requestCallback, responseExtractor);
    }

저 구간에서 디버깅을 찍어보면 String uriTemplate => URI url로 변화는 과정에 키 값이 변한다.

 

    // this.uriTemplateHandler = initUriTemplateHandler();
    
    private static DefaultUriBuilderFactory initUriTemplateHandler() {
        DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory();
        uriFactory.setEncodingMode(EncodingMode.URI_COMPONENT);
        return uriFactory;
    }

몇 가지 EncodingMode가 있는 듯하다.

일단 문제는, String으로 url을 넣으면 URI 객체로 변환되는 과정이 있고 그 과정에서 의도치 않은 변화가 일어나는 것이다.

 

해결

    @Nullable
    public <T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
        return this.doExecute(url, (String)null, method, requestCallback, responseExtractor);
    }

exchange에 URI 객체를 받는 다른 오버로딩 함수를 따라가면, url이 변환 없이 바로 doExecute로 들어간다.

URI 객체를 인자로 주면 쿼리 파라미터가 의도대로 들어갈 거 같다.

 

String apiUrl = "~~~~~";
URI apiUrl = new URI(apiUrl);

ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.GET, entity, String.class);

URI를 넣고 공공 api를 요청하면 정상적으로 데이터가 넘어오는 걸 확인할 수 있다.

+) UriComponentsBuilder

    String baseUrl = "http://www.example.com/search";

    URI uri = UriComponentsBuilder.fromHttpUrl(baseUrl)
            .queryParam("key", "%2FY")
            .queryParam("key2", "===2D")
            .build()
            .toUri();
    System.out.println(uri); // http://www.example.com/search?key=%252FY&key2====2D

스프링 부트에서 UriComponentsBuilder를 사용해서 URI를 만들 수 있는데 value가 달라지는 걸 볼 수 있다.

restTemplate의 String => URI 변환되는 로직과 유사해 보인다.

URI를 만들 때도, 의도한 url이 생성됐는지 주의하자.