Post

Spring) RestTemplate - Public API SERVICE KEY IS NOT REGISTERED ERROR

Spring) RestTemplate - Public API SERVICE KEY IS NOT REGISTERED ERROR

This post was migrated from Tistory. You can find the original here.

Problem

1
2
3
4
5
6
7
<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"]>

I was using the Korea Meteorological Administration’s short-term forecast API, and I got the response above.

KMA short-term forecast err_code

This is an error code provided by the API.

For public data APIs, both an encoding key and a decoding key are provided, and you’re told to use whichever one actually works.

Investigation

  1. Try both the encoding key and the decoding key. => Failed

  2. Is the key getting changed somewhere during the request?

1
2
3
String apiUrl = "~~~~";

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

Let’s step into exchange and debug the part where apiUrl seems likely to be changing.

1
2
3
4
5
    @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);
    }

Putting a breakpoint around here shows that the key value changes during the conversion from String uriTemplate to URI url.

1
2
3
4
5
6
7
    // this.uriTemplateHandler = initUriTemplateHandler();
    
    private static DefaultUriBuilderFactory initUriTemplateHandler() {
        DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory();
        uriFactory.setEncodingMode(EncodingMode.URI_COMPONENT);
        return uriFactory;
    }

It looks like there are several EncodingMode options.

So the problem is: when you pass the URL in as a String, there’s a conversion step to a URI object, and that conversion introduces an unintended change.

Solution

1
2
3
4
    @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);
    }

If you follow the other overload of exchange that takes a URI object, the URL goes straight into doExecute without any conversion.

Passing in a URI object should get the query parameters through as intended.

1
2
3
4
String apiUrl = "~~~~~";
URI apiUrl = new URI(apiUrl);

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

Once I passed in a URI and called the public API, I confirmed that the data came back correctly.

+) UriComponentsBuilder

1
2
3
4
5
6
7
8
    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

In Spring Boot you can build a URI using UriComponentsBuilder, but notice that the value ends up different from what you put in.

This looks similar to the String => URI conversion logic in RestTemplate.

When building a URI, be careful to check that the resulting URL is actually what you intended.

This post is licensed under CC BY-NC 4.0 by the author.