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
Try both the encoding key and the decoding key. => Failed
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.
