0

Percent Endcoding với Swift

Vấn đề Endcode phát sinh trong dự án của team mình đó là việc phải Endcode URL khi gửi lên WebAPI của khách hàng. Ban đầu team sử dụng hàm endcode mặc định trong IOS và Android, tuy nhiên output của Endcode không thỏa mãn yêu cầu khách hàng. Với yêu cầu của khách hàng thì URL sau khi được encode thì không được chứa các ký tự đặc biệt nữa. Sau họ gửi cho một trang web về cách endcode theo chuẩn RFC3986 thì team mới tìm hiểu và thấy rằng Percent Endcoding thích hợp nhất để endcode theo đúng yêu cầu khách hàng (hehe)

VD về yêu cầu endcode của KH như sau:

"q=" + uriEncode("a")   // => "q=a"
"q=" + uriEncode("a b") // => "q=a%20b"
"q=" + uriEncode("あ")  // => "q=%E3%81%82"

stringByAddingPercentEscapesUsingEncoding

Đây là hàm đầu tiên team sử dụng thử để xem kq có ổn không

func uriEncode(str: String) -> String {
  return str.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
}

Những tưởng với cách trên thì encode ngon lành cành đào. Nhưng kết quả khi endcode ra thì vẫn không chuẩn. Vì theo chuẩn RFC3986 thì kể cả các ký tự đặc biệt cũng phải được endcode. Mà kết quả của hàm trên xuất ra thì:

"q=" + uriEncode("=") // => "q=="
"q=" + uriEncode("&") // => "q=&"

stringByAddingPercentEncodingWithAllowedCharacters

Tuy nhiên thì từ IOS7 trở lên chúng ta có thể sử dụng API này (honho)

func uriEncode(str: String) -> String {
  return str.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}

Function trên sẽ encode tất cả các ký tự đầu vào theo kiểu Percent Encoding trừ các ký tự được cho phép ở trong ngoặc. Do đó đây có vẻ là phương pháp chuẩn nhất để endcode theo yêu cầu của khách hàng:

"q=" + uriEncode("=") // => "q=%3D"
"q=" + uriEncode("&") // => "q=%26"

"q=" + uriEncode("-") // => "q=%2D"
"q=" + uriEncode("_") // => "q=%5F"

Tuy nhiên Mặc dù phương pháp này thoả mãn yêu cầu của Khách hàng. Nhưng theo chuẩn RFC3986 thì encode vẫn chưa chuẩn. Bởi vì theo chuẩn đó , ngoài các ký tự anphalbet ra có 4 ký tự # nữa cũng không cần phải endcode đó là "-._~"

URLQueryAllowedCharacterSet

Function NSCharacterSet.URLQueryAllowedCharacterSet được sử dụng để thử giải quyết bài toán này, tuy nhiên kết quả trả ra thì hoàn toàn không phù hợp:

// これは駄目なコード
func uriEncode(str: String) -> String {
  return str.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
}
uriEncode("!*'();:@&=+$,/?%#[]-._~ ") // => "!*'();:@&=+$,/?%25%23%5B%5D-._~%20"

Mặc dù 4 ký tự trên đã ko encode nhưng kèm theo đó là một loạt ký tự # nữa đó là !*'();:@&=+$,/?(khoc)

Cuối cùng để đạt được kết quả endcoding theo chuẩn RFC3986 thì ngoài việc sử dụng hàm stringByAddingPercentEncodingWithAllowedCharacters để endcode, chúng ta cần tách biến AllowedCharacter trong ngoặc ra trước và add thêm 4 ký tự trên vào -> rất là đơn giản mà mãi mới nghĩ ra 😄

func uriEncode(str: String) -> String {
  var allowedCharacterSet = NSMutableCharacterSet.alphanumericCharacterSet()
  allowedCharacterSet.addCharactersInString("-._~")
  return str.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)!
}
uriEncode("!*'();:@&=+$,/?%#[]-._~ ") // => "%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23%5B%5D-._~%20"

Tổng kết

Sử dụng API cho Swift (IOS7.0 trở lên) chúng ta dùng hàm sau để endcode percent

func uriEncode(str: String) -> String {
  return str.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}

Và sử dụng hàm dưới để endcode theo chuẩn RFC3986

func uriEncode(str: String) -> String {
  var allowedCharacterSet = NSMutableCharacterSet.alphanumericCharacterSet()
  allowedCharacterSet.addCharactersInString("-._~") // add 4 signal to allow
  return str.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)!
}

Nội dung bài viết khá đơn giản, nhưng rất hữu ích cho các bạn khi lần đầu gặp phải vấn đề này ^^. Hi vọng sẽ giúp được ít nhiều trong việc tiết kiệm thời gian tìm giải pháp trong các vấn đề endcoding. (honho)


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.