ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift 에서 한글, 특수문자 엔드포인트를 적절히 인코딩하기
    iOS 2019. 2. 12. 00:50

    Swift에서의 한글, 특수문자 엔드포인트를 적절히 URL Encoding하기

     

    https url에 엔드포인트로 한국어를 사용했을 때, 습관적으로 이렇게 코딩하곤 합니다.

    import Foundation
    
    var url = URL(string: "https://sesang06.tistory.com/")!
    /// https://sesang06.tistory.com/
    
    let koreanEndPoint = "헬로 월드!"
    
    url.appendPathComponent(koreanEndPoint)
    /// https://sesang06.tistory.com/%ED%97%AC%EB%A1%9C%20%EC%9B%94%EB%93%9C!
    
    

     

     

    얼핏 보면 한글이 제대로 퍼센트 이스케이핑 되어있는 것처럼 보이지만, ''헬로 월드!'' 의 느낌표에 주목해 봅시다.

    느낌표가 이스케이핑되지 않고 !로 그래도 나온 것을 확인해볼 수 있습니다.

    Foundation 에서의 appendPathComponent 함수는 다음과 같이 정의되어 있지만, 별다른 알고리즘에 대한 도움을 얻기는 힘들었습니다.

    /// Appends a path component to the URL.
        ///
        /// - parameter pathComponent: The path component to add.
        /// - parameter isDirectory: Use `true` if the resulting path is a directory.
        public mutating func appendPathComponent(_ pathComponent: String, isDirectory: Bool) {
            self = appendingPathComponent(pathComponent, isDirectory: isDirectory)
        }
        
        /// Appends a path component to the URL.
        ///
        /// - note: This function performs a file system operation to determine if the path component is a directory. If so, it will append a trailing `/`. If you know in advance that the path component is a directory or not, then use `func appendingPathComponent(_:isDirectory:)`.
        /// - parameter pathComponent: The path component to add.
        public mutating func appendPathComponent(_ pathComponent: String) {
            self = appendingPathComponent(pathComponent)
    
    

     

    플레이그라운드를 열어서 써본 결과 다음과 같은 결과를 얻을 수 있었죠.

    import Foundation
    
    var url = URL(string: "https://sesang06.tistory.com/")!
    
    let notReservedString = "-_.!~*'()@&=+$,:/"
    
    
    url.appendPathComponent(notReservedString)
    ///https://sesang06.tistory.com/-_.!~*'()@&=+$,:/
    
    var anotherUrl = URL(string: "https://sesang06.tistory.com/")!
    let reservedString = ";?"
    anotherUrl.appendPathComponent(reservedString)
    ///https://sesang06.tistory.com/%3B%3F
    
    
    
    

    ;와 ? 같은 일부 문자만 이스케이핑이 정상적으로 처리되고, 나머지 아스키 특문들은 제대로 이스케이핑이 되지 않습니다.

    하지만 퍼센트 이스케이핑에서는 느낌표를 포함한 몇 개의 문자를 반드시 이스케이핑할 것을 요구하고 있죠.

    규약[편집]

    퍼센트 인코딩 규약은 RFC 3986에 정의되어 있다. 이 RFC에 따르면 URL에서 중요하게 사용되는 예약(reserved) 문자가 있고, 또한 인코딩이 필요하지 않은 비예약(unreserved) 문자가 존재한다.

    예약 문자는 다음과 같다. 이들 중 일부는 URI에서 중요한 문법적 의미를 가지고 있기 때문에, 그 의미로 사용할 것이 아니라면 반드시 인코딩을 해야 한다.

    !*'();:@&=+$,/?#[]
                      

    비예약 문자는 다음과 같다. 이들 문자는 퍼센트 인코딩을 할 필요가 없고, 인코딩을 안 하는 것을 권장한다.

    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    abcdefghijklmnopqrstuvwxyz
    0123456789-_.~            

     

    즉, endPoint 가 일부 특문을 포함한 경우, appendPathComponent 를 사용하면 의도치 않은 결과를 얻을 수 있습니다.

    그렇다면... addingPercentEncoding(withAllowedCharacters: CharacterSet.urlPathAllowed) 으로 인코딩하는 것은 어떨까 하고 시도해보았습니다.

    url의 정의에서 경로를 이스케이핑하는거니, 정의에도 퍽 맞아 보였고요.

    import Foundation
    
    let baseString = "https//sesang06.tistory.com/"
    let koreanEndPoint = "헬로 월드!"
    
    let escapedKoreanEndPoint = koreanEndPoint.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlPathAllowed)
    ///"%ED%97%AC%EB%A1%9C%20%EC%9B%94%EB%93%9C!"
    
    

    하지만 이 함수도 !를 적절히 이스케이핑하지 못하는 것을 확인할 수 있었습니다.

    이스케이핑하지 않아도 되는 건 오직 알파벳 대, 소문자, 숫자와 "-_.~" 특수문자 뿐이었는데요.

     

    결국 불가피하게 이런 코드를 작성할 수밖에 없었습니다.

     

    import Foundation
    
    let baseString = "https//sesang06.tistory.com/"
    let koreanEndPoint = "헬로 월드!"
    
    let escapingCharacterSet: CharacterSet = {
      var cs = CharacterSet.alphanumerics
      cs.insert(charactersIn: "-_.~")
      return cs
    }()
    
    let escapedKoreanEndPoint = koreanEndPoint.addingPercentEncoding(withAllowedCharacters: escapingCharacterSet)!
    ///"%ED%97%AC%EB%A1%9C%20%EC%9B%94%EB%93%9C%21"
    let url = URL(string: "\(baseString)\(escapedKoreanEndPoint)")
    ///https//sesang06.tistory.com/%ED%97%AC%EB%A1%9C%20%EC%9B%94%EB%93%9C%21
    
    

     

    저의 경우 이 이슈로 인해 https://github.com/devxoul/MoyaSugar 라이브러리를 활용한 코드를 뜯어고칠 수밖에 없었습니다.

    위 라이브러리는 Route와 url을 아래와 같은 식으로 정의합니다.

    var route: Route {
      return .get("/me")
    }
     // override default url building behavior
      var url: URL {
        switch self {
        case .url(let urlString):
          return URL(string: urlString)!
        default:
          return self.defaultURL
        }
      }
    
    

    그런데 라이브러리 내부에서 route 를 baseURL과 합칠 때 다음과 같은 수를 씁니다.

    public extension SugarTargetType {
      public var url: URL {
        return self.defaultURL
      }
    
      var defaultURL: URL {
        return self.path.isEmpty ? self.baseURL : self.baseURL.appendingPathComponent(self.path)
      } ///문제가 되는 부분
    
      public var path: String {
        return self.route.path
      }
    
      public var method: Moya.Method {
        return self.route.method
      }
    
      public var task: Task {
        guard let parameters = self.parameters else { return .requestPlain }
        return .requestParameters(parameters: parameters.values, encoding: parameters.encoding)
      }
    }
    

     

    appendingPathComponent 함수를 썼으므로, Route 에 아스키 코드 외의 , 혹은 @, ! 등의 특문을 포함한 엔드포인트를 정의할 경우 오류가 납니다.

    그러자고 퍼센트 인코딩을 거기다가 해버릴 경우에는, 한글이 이중으로 이스케이핑 되버립니다.

    따라서 다음과 같이 하드코딩하는 것이 최선이었죠.

    var route: Route {
      return .get("") ///여기선 엔드포인트를 정의하지 못하고..
    }
    var url: URL {
        switch self {
        case .url(let urlString, let koreanEndPoint):
          let escapingString = properlyEscapingFunc(koreanEndPoint)
          return URL(string: "\(urlString)\(escapingString)")!
            ///여기서 구태여 정의한다.
        default:
          return self.defaultURL
        }
      }
    

     

    따라서 위 라이브러리를 사용할 땐, 엔드포인트의 값이 적절한지 반드시 확인이 필요해 보입니다.


    댓글 0

Designed by Tistory.