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 ! 🎉

Publié par Jean-Christophe Pastant

Jean-Christophe est consultant iOS et partage régulièrement ses bonnes pratiques avec la communauté iOS.

Commentaire

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.