Il y a 3 ans -
Temps de lecture 2 minutes
Pépite #25 – Les URL dans tous leurs états
Depuis iOS8, iOS propose une nouvelle classe pour construire ses URL à partir de plusieurs paramètres : URLComponents
. Seulement celle-ci possède… quelques subtilités.
You know nothing, urlQuery
!
Imaginez : vous avez une API pour requêter des informations concernant un numéro de téléphone. Celui-ci est au format international. (+33xxxxxxxxx pour la France). Avec URLComponents
, vous pourriez construire votre URL comme suit :
[cpp]var builder = URLComponents(string: "https://smsservice.com")!
builder.path = "/status"
builder.queryItems = [URLQueryItem(name: "number", value: "+33601020304")]
let requestURL = builder.url[/cpp]
Rien de compliqué. Vous envoyez alors votre requête au serveur qui vous répond… erreur 400 ! Notre requête est malformée. Mais pourquoi ?
Parlez-vous RFC ?
Le problème est simple, mais subtil. D’après la documentation de URLComponents
, queryItems
est encodé selon la norme RFC 3986, dans laquelle “+” est un caractère valide (et donc non encodé) pour une query
.
hello+world
correspond donc à… hello+world
👍.
A l’inverse, la W3C recommendations for URI addressing définit le “+” comme un équivalent à espace, hello+world
correspond donc à… hello world
😲. Et devinez quoi ? Les serveurs suivent la W3C recommendations !
[cpp]// requestURL vaut "https://smsservice.com/status?number=+33601020304"
// alors que le serveur s’attend à https://smsservice.com/status?number=%2B33601020304
print(requestURL!)[/cpp]
Il va donc être donc nécessaire, pour envoyer un « + » à notre serveur, de l’encoder afin qu’il ne soit pas interprété comme un espace 😒.
À tout problème sa solution
Pour ce faire, le plus simple est de définir une méthode qui va encoder les paramètres selon la norme W3C :
[cpp]extension URLQueryItem {
func percentEncoded() -> URLQueryItem {
/// addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) encode les paramètres selon la norme RFC 3986
/// Par dessus, nous allons venir encoder le + par son équivalent HTTP
var newQueryItem = self
newQueryItem.value = value?
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?
.replacingOccurrences(of: "+", with: "%2B")
return newQueryItem
}
}
extension Array where Element == URLQueryItem {
func percentEncoded() -> Array<Element> {
return map { $0.percentEncoded() }
}
}[/cpp]
Il ne nous reste plus qu’à l’appeler au moment de définir nos paramètres :
[cpp]let encodedQueryItems = [URLQueryItem(name: "number", value: "+33601020304")].percentEncoded()
builder.queryItems = encodedQueryItems
print(builder.url)[/cpp]
Le résultat sera quelque peu surprenant : « https://smsservice.com/status?number=%252B33601020304 » au lieu de « https://smsservice.com/status?number=%2B33601020304 » !
La réponse vient de queryItems
: les paramètres y sont encodés automatiquement, mais nous les avions nous-même déjà encodés auparavant. Les caractères spéciaux se retrouvent donc encodés deux fois…
Heureusement, il existe une solution à cela ! Caché au fin fond de la documentation Apple se trouve percentEncodedQueryItems
, la version raw (non encodée) de queryItems
.
[cpp]builder.percentEncodedQueryItems = encodedQueryItems
print(builder.url)[/cpp]
Et voilà ! Après de durs labeurs, votre URL vaut enfin « https://smsservice.com/status?number=%2B33601020304« . Hourra ! 🎉
Commentaire