Skip to content

Instantly share code, notes, and snippets.

@jonenst
Last active February 1, 2021 15:19
Show Gist options
  • Save jonenst/5afd4de92d716c37400dc1ed6883fa5d to your computer and use it in GitHub Desktop.
Save jonenst/5afd4de92d716c37400dc1ed6883fa5d to your computer and use it in GitHub Desktop.
UriComponentsBuilder
UriComponentsBuilder:
RESUME: REGLES D'OR
* toujours appeler encode(), même quand on encode pas (!!!!) (encode pas = build(true))
* toujours utiliser toUri() plutôt que toUriString()
* (presque) toujours utiliser .encode().build(false).expand(vars).toUri() ou le raccourci build(vars)
Interaction avec restTemplateBuilder:
restTemplateBuilder utiliser le mode recommandé si on fait new DefaultUriBuilderFactory(baseUri),
mais dans ce cas oblige obligatoirememnt à utiliser un template...
Si on veut utiliser UriComponentBuilder explicitement, il faut absolument passer des URIs au lieu de strings à RestTemplate,
mais dans ce cas on perd la fonctionnalité de baseUri du templateHandler.. il faut récuperer le baseUri soit même.
Pour utiliser, il y a 3 modes:
modes recommandés:
.build(true) : safe, tout doit avoir été parfaitement encodé si besoin
(sinon exception), et si quelqu'un appelle encode() fait pas de double
encoding. Pas d'expand.
.encode().build(false).expand(vars) : safe, rien ne doit avoir été encodé (sinon double
encoding), mettre la structure de l'url dans la template et les données dans les variables.
encoding "minimal pour la template, encoding "maximal" pour les variables;
mode OK ~~:
build(false).encode() ou
build(false).encode().expand(vars)
safe, rien ne doit avoir été encodé (sinon double encoding), encoding "minimal" pour tout;
pas très utile, sauf si on a vraiment besoin (presque jamais) de mettre des charactères nonencodés à un endroit.
mode interdit sous peine de sanctions immédiate:
.build() (ou synonyme .build(false)) tout seul: ne pas utiliser (mode qui
ne fait aucun check, permet le double encoding... code à la php quoi). C'est le
mode par défaut lol.
Pour renseigner les données:
soit donner les données de base (.queryParam, .pathSegment, .host).
les autres methodes (fromUriString, path, query) parsent et appellent les setters de base, mais ils font un parsing simpliste qui ne décode pas,
donc la nature (encodé ou pas) de leur input doit être prise en compte (pour la contrainte du dessus de soit tout encodé, soit rien encodé)
(alors qu'il aurait été possible de faire une API ou ces trucs acceptent des trucs encodés et les décodent pour que ça fonctionne avec le reste non encodé par ex.. ¯\_(ツ)_/¯ )
//Equivalents
jshell> UriComponentsBuilder.newInstance().pathSegment("a", "b", "{name}").queryParam("filter", "{filter}").encode().build().expand("nom", "filtre")
$21 ==> /a/b/nom?filter=filtre
jshell> UriComponentsBuilder.fromUriString("/a/b/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
$22 ==> /a/b/nom?filter=filtre
//si b doit être un charactère bizarre (ex "|"), pareil
jshell> UriComponentsBuilder.newInstance().pathSegment("a", "|", "{name}").queryParam("filter", "{filter}").encode().build().expand("nom", "filtre")
$24 ==> /a/%7C/nom?filter=filtre
jshell> UriComponentsBuilder.fromUriString("/a/|/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
$25 ==> /a/%7C/nom?filter=filtre
// par contre, si on a besoin d'escaper un parametre pour faire que le parsing marche, alors on doit se mettre à tout escaper
// Ex: maintenant on veut plus mettre a, mais '?' dans le path.
// premier essai, pas bon, car le premier ? est interprété par le parsing simple comme le début de la query string
// deuxième essai, pas bon, double escaping
Base UriComponentsBuilder.fromUriString("/a/|/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
?? UriComponentsBuilder.fromUriString("/?/|/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
?? UriComponentsBuilder.fromUriString("/%3F/|/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
OK UriComponentsBuilder.fromUriString("/%3F/%7C/{name}?filter={filtre}").encode().build().expand("nom", "filtre")
mais il a fallu changer 2 trucs pour s'adapter à un changement à cause du parsing ambigü. Donc en utilisant pas le parsing c'est ok direct:
jshell> UriComponentsBuilder.newInstance().pathSegment("?", "|", "{name}").queryParam("filter", "{filter}").encode().build().expand("nom", "filtre")
$29 ==> /%3F/%7C/nom?filter=filtre
2eme aspect: encoding "minimal" vs "maximal"
Encoding minimal (pas très utile...):
// le & est encodé
jshell> UriComponentsBuilder.fromUriString("/").queryParam("a", "&").encode().build()
$10 ==> /?a=%26
// pas besoin d'encoder un '?' dans la query string (car elle commence au premier '?' et après plus besoin)
jshell> UriComponentsBuilder.fromUriString("/").queryParam("a", "?").encode().build()
$9 ==> /?a=?
// besoin d'encoder ? dans un pathsegment
jshell> UriComponentsBuilder.fromUriString("/").pathSegment("?","foo").encode().build()
$34 ==> /%3F/foo
encoding "maximal": mode préconisé
jshell> UriComponentsBuilder.fromUriString("/").queryParam("a", "{b}").encode().build().expand("?")
$30 ==> /?a=%3F
//note, tjs possible de faire un encoding "minimal" si on veut avec les templates, mais dans ce cas à quoi servent les templates?
//(sauf ptet à découper un peu le code ?)
jshell> UriComponentsBuilder.fromUriString("/").queryParam("a", "{b}").build().expand("?").encode()
$93 ==> /?a=?
toUri() vs toUriString() sont sensé être équivalents.. Uri un peu plus safe car il crée un objet java.net.URI intermediare qui valid (exception si pas bon)
mais bon ça veut dire que l'url construite est serialisée par UriComponents puis reparsée par Uri puis reserialisée..
Raccouris à connaitre (pour les utiliser ou les éviter..)
* `buildAndExpand(XXX)`: comme `.build().expand(XXX)`: OK
* `build(XXX)` comme `.encode().build().expand(XXX).toUri()` : PARFAIT ! mais un peu moins explicite
* `toUriString()`: (!! dangereux n'utiliser que si on n'utilise pas de variables.) .build().encode().toUriString()
* exemple de danger:
jshell> UriComponentsBuilder.fromUriString("/{a}/").uriVariables(Map.of("a","X")).toUriString()
$17 ==> "/X/"
//OK tout va bien
jshell> UriComponentsBuilder.fromUriString("/{a}/").toUriString()
$19 ==> "/%7Ba%7D/"
//Probablement pas ce qui était voulu si on voulait faire un template ?
//Mais si c'était pas un template et vraiment la requete qu'on voulait faire,
//alors le résultat a correctement était encodé.. Rare
jshell> UriComponentsBuilder.fromUriString("/{b}/").uriVariables(Map.of("a","X")).toUriString()
$18 ==> "/{b}/"
//uri invalid, '{' et '}' interdits pas escapés !!
Bonus1: variables positionnelles ou nommées
expand(a,b,c) -> positionnelles
expand(Map.of("x", "a", ...) -> nommée
Bonus2: variable "pas tout d'un coup":
on peut faire `.uriVariables(XXX).encode().build()`:
UriComponentsBuilder.fromUriString("/").queryParam("{c}", "{b}").uriVariables(Map.of("c","d")).encode().build().expand("e")
Bonus3: uri opaque: (comme URI), plus d'encoding..
UriComponentsBuilder.fromUriString("gridsuitelink:123-aaa-FF").build()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment