Lors de l'appel RestTemplate.exchange
pour faire une requête get, telle que:
String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)
à quoi bon échapper correctement les variables URL pour la requête get?
Plus précisément, comment obtenir des avantages (+
) s'est correctement échappé parce que Spring interprète comme des espaces , donc je dois les encoder.
J'ai essayé d'utiliser UriComponentsBuilder
comme ceci:
String foo = "fo+o";
String bar = "ba r";
UriComponentsBuilder ucb = UriComponentsBuilder.fromUriString("http://example.com/?foo={foo}&bar={bar}");
System.out.println(ucb.build().expand(foo, bar).toUri());
System.out.println(ucb.build().expand(foo, bar).toString());
System.out.println(ucb.build().expand(foo, bar).toUriString());
System.out.println(ucb.build().expand(foo, bar).encode().toUri());
System.out.println(ucb.build().expand(foo, bar).encode().toString());
System.out.println(ucb.build().expand(foo, bar).encode().toUriString());
System.out.println(ucb.buildAndExpand(foo, bar).toUri());
System.out.println(ucb.buildAndExpand(foo, bar).toString());
System.out.println(ucb.buildAndExpand(foo, bar).toUriString());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toUri());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toString());
System.out.println(ucb.buildAndExpand(foo, bar).encode().toUriString());
et qui a imprimé:
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
http://example.com/?foo=fo+o&bar=ba%20r
L'espace est correctement échappé dans certains cas, mais le plus n'est jamais échappé.
J'ai également essayé UriTemplate
comme ceci:
String foo = "fo+o";
String bar = "ba r";
UriTemplate uriTemplate = new UriTemplate("http://example.com/?foo={foo}&bar={bar}");
Map<String, String> vars = new HashMap<>();
vars.put("foo", foo);
vars.put("bar", bar);
URI uri = uriTemplate.expand(vars);
System.out.println(uri);
avec exactement le même résultat:
http://example.com/?foo=fo+o&bar=ba%20r
Apparemment, la bonne façon de procéder consiste à définir une usine et à changer le mode d'encodage:
String foo = "fo+o";
String bar = "ba r";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
URI uri = factory.uriString("http://example.com/?foo={foo}&bar={bar}").build(foo, bar);
System.out.println(uri);
Cela imprime:
http://example.com/?foo=fo%2Bo&bar=ba%20r
Ceci est documenté ici: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#web-uri-encoding
Je pense que votre problème ici est que RFC 3986 , sur lequel UriComponents
et par extension UriTemplate
sont basés, ne fonctionne pas rend obligatoire l'échappement de +
dans une chaîne de requête.
Le point de vue de la spécification à ce sujet est simplement:
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
Si votre infrastructure Web (Spring MVC, par exemple!) Interprète +
Comme un espace, alors c'est sa décision et ce n'est pas requis par la spécification URI.
En référence à ce qui précède, vous verrez également que !$'()*+,;
ne sont pas échappés par UriTemplate
. =
Et &
sont échappés car Spring a pris une vue "d'opinion" de ce à quoi ressemble une chaîne de requête - - une séquence de paires clé = valeur.
De même, #[]
Et les espaces sont échappés car ils sont illégaux dans une chaîne de requête sous la spécification.
Certes, rien de tout cela ne sera probablement une consolation pour vous si vous voulez tout à fait raisonnablement que vos paramètres de requête s'échappent!
Pour réellement encoder les paramètres de requête afin que votre framework web puisse les tolérer, vous pouvez utiliser quelque chose comme org.springframework.web.util.UriUtils.encode(foo, charset)
.
Je commence à croire que c'est un bug et j'ai signalé ici: https://jira.spring.io/browse/SPR-1686
Actuellement, ma solution est la suivante:
String foo = "fo+o";
String bar = "ba r";
String uri = UriComponentsBuilder.
fromUriString("http://example.com/?foo={foo}&bar={bar}").
buildAndExpand(vars).toUriString();
uri = uri.replace("+", "%2B"); // This is the horrible hack.
try {
return new URI(uriString);
} catch (URISyntaxException e) {
throw new RuntimeException("UriComponentsBuilder generated an invalid URI.", e);
}
qui est un horrible hack qui pourrait échouer dans certaines situations.
Pour celui-ci, je préférerais toujours que l'encodage soit résolu en utilisant une méthode appropriée au lieu d'utiliser un hack comme vous l'avez fait. Je voudrais simplement utiliser quelque chose comme ci-dessous
String foo = "fo+o";
String bar = "ba r";
MyUriComponentsBuilder ucb = MyUriComponentsBuilder.fromUriString("http://example.com/?foo={foo}&bar={bar}");
UriComponents uriString = ucb.buildAndExpand(foo, bar);
// http://example.com/?foo=fo%252Bo&bar=ba+r
URI x = uriString.toUri();
// http://example.com/?foo=fo%2Bo&bar=ba+r
String y = uriString.toUriString();
// http://example.com/?foo=fo%2Bo&bar=ba+r
String z = uriString.toString();
Et bien sûr, la classe est comme ci-dessous
class MyUriComponentsBuilder extends UriComponentsBuilder {
protected UriComponentsBuilder originalBuilder;
public MyUriComponentsBuilder(UriComponentsBuilder builder) {
// TODO Auto-generated constructor stub
originalBuilder = builder;
}
public static MyUriComponentsBuilder fromUriString(String uri) {
return new MyUriComponentsBuilder(UriComponentsBuilder.fromUriString(uri));
}
@Override
public UriComponents buildAndExpand(Object... values) {
// TODO Auto-generated method stub
for (int i = 0; i< values.length; i ++) {
try {
values[i] = URLEncoder.encode((String) values[i], StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return originalBuilder.buildAndExpand(values);
}
}
Ce n'est toujours pas le moyen le plus propre mais mieux que de faire une approche de remplacement codée en dur
Vous pouvez utiliser riComponentsBuilder au printemps (org.springframework.web.util.UriComponentsBuilder)
String url = UriComponentsBuilder
.fromUriString("http://example.com/")
.queryParam("foo", "fo+o")
.queryParam("bar", "ba r")
.build().toUriString();
restTemplate.exchange(url , HttpMethod.GET, httpEntity);