공공데이터포털 SERVICE_KEY_IS_NOT_REGISTERED_ERROR 원인 파헤치기

Lily·2023년 2월 14일
2

🚨 문제


포스트맨에서는 정상적으로 오던 응답이,
앱에서 호출하니 SERVICE_KEY_IS_NOT_REGISTERED_ERROR라는 응답을 받았다.

Response URL을 브라우저에 로드한 결과⬇️
(Parsing 실패 에러로 빠져서 이상한데서 삽질함...🤦‍♀️)


🕵🏻‍♀️ 원인 파헤치기


구글링을 해보니 APIkey 인코딩이 주된 원인이었다.

공공데이터포털에 올라와 있는 Q&A와 같이, 우선 APIKey는 URL 인코딩을 해야한다.


해당 Q&A 바로가기

URL 인코딩 그게 뭔데?

URL 인코딩...전송전에 URL 문자를 인코딩해아했다고 한 것 같은데...
잘 기억이 나지 않아 다시 찾아보았다.

퍼센트 인코딩(percent encoding)

URL 인코딩에서는 퍼센트 인코딩(percent encoding)이라는 방식을 사용한다.
퍼센트 인코딩은 RFC 3986에 정의되어있다.
퍼센트 인코딩 URL인코딩뿐만 아니라 URI, URN에도 사용할 수 있으므로 정확히는 '퍼센트 인코딩(Percent encoding)'이라는 용어가 더 적합하다고 한다.

퍼센트 인코딩을 하는 이유는 인터넷에서 주고 받을 수 있는 문자는 ASCII 문자뿐이기 때문이다.
따라서 ASCII가 아닌 문자는 전송 가능한 형태로 인코딩을 해야한다.

  • 퍼센트 인코딩은 URL에서 1) URL로 사용할 수 없는 문자나 2) URL로 사용할 순 있지만 의미가 왜곡될 수 있는 문자%XX (XX는 16진수)로 변환하는 방법이다.
    • 1) 예로 한글은 ASCII가 아니기 때문에 UTF-8과 같은 방식으로 인코딩해야한다.
      • 예시 : 한글 -> %ED%95%9C%EA%B8%80
    • 2) ASCII라도 예약된 의미를 가진 문자의 경우, 예약된 의미 말고 그 문자 자체를 전달하고 싶을 때는 이스케이프 처리가 필요하다
      • 예시 : / (URL의 각 레벨을 구분) , & (쿼리 파라미터를 구분) , = (쿼리 파라미터 값 지정)
      • A&B 라는 글자를 보내고 싶을 땐 → A%26B ’&’을 이스케이프 처리

[Web] URL 인코딩/디코딩 (URL Encoding/Decoding) 글을 참고했다.

URL Encoding해주는 사이트도 존재한다.
URL Encode Decode - URL Percent Encoding and Decoding.


URLComponent는 자동으로 인코딩해준다는데?

그런데 iOS에서 URL을 만들때 사용하는 URLComponent 타입은 자동으로 인코딩을 해준다.
https://developer.apple.com/documentation/foundation/urlcomponents

공공데이터 포털에서는 encodedKey, decodedKey 2타입을 제공한다.

따라서 decodedKey를 쿼리 파라미터에 넣으면 한번만 인코딩되니 정상적으로 될 것이라 생각했다.

근데 안됨...💩

그럼 왜 안되는 거야?

스택오버플로우에서 힌트를 얻을 수 있었다.

이유는 URLComponent+는 인코딩하지 않기 때문이다. 따라서 +는 수동으로 인코딩해야한다고한다.

공식문서에도 아래와 같은 내용이 Note로 제공되고 있다.

요약하자면

  • RFC 3986은 어떤 문자가 퍼센트 인코딩되어야하는지는 정의하지만, 문자의 해석 방법은 정의하고 있지 않음
  • 따라서 다른 컨벤션과 호환 문제를 겪을 수 있음
  • 그 중 중요한 예로 플러스 사인 (+)이 처리되는 방식인데, RFC 3986은 쿼리에서 유효한 문자로 퍼센트 인코딩 되지 않는다
  • 하지만 W3C recommendations for URI addressing에 따르면, 쿼리 스트링에서 플러스 사인은 공백의 축약형으로 해석된다.

즉, 공공데이터포털에서 제공하는 apiKey는 'W3C recommendations for URI addressing'에 따라 +%2B로 변환하는데, URLComponents에서는 그냥 + 그대로 뒀기 때문에 키가 틀리는 오류가 발생한 것이다.


인코딩과 디코딩키를 대조해보면 알 수 있다!


해결 방법

따라서 퍼센트 인코딩된 쿼리에 +%2B 로 직접 변환해 줌으로써 해결했다.

  • percentEncodedQuery : 퍼센트 인코딩된 쿼리
var url: URL? {
        var urlComponents = URLComponents(string: baseURLString + path)
        urlComponents?.queryItems = query.map {
            URLQueryItem(name: $0.key, value: "\($0.value)") }
         // replacingOccurrences로 문자 변환
        let encodedQuery = urlComponents?.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
        urlComponents?.percentEncodedQuery = encodedQuery
        return urlComponents?.url
    }

🚀 교훈


  • 네트워크 규약들은 정말 많다는 것을 다시 한번 느끼게 되었다.
    규약들의 호환성 때문에도 문제가 발생할 수 있다는 관점을 배우게 되었다!

  • api key error로 status 200을 받을 수 도 있다니...
    그래서 xml을 JSON encoding 방식으로 파싱하려 하다보니 Parsing Fail로 빠져서 디버깅하는데 오랜 시간이 걸렸다.
    서버사이드와 약속을 공유하는 건 매우 중요한 일이겠다..고 간접적으로 느낄 수 있던 사례였다.
    ✅ 개선: CountryCodeAPIService에서 data를 파싱하기전에 xml타입으로 데이터가 왔는지 체크하는 로직을 추가로 구현해야겠다.

profile
i🍎S 개발을 합니다

1개의 댓글

comment-user-thumbnail
2024년 2월 23일

좋은 정보 공유 감사합니다~^^
덕분에 해결한거 같아요!

답글 달기