web-dev-qa-db-fra.com

Quelle est la bonne façon d'échapper les variables d'URL avec Spring's RestTemplate lors de l'appel d'un Spring RestController?

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
22
pupeno

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

14
pupeno

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).

9
ryanp

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.

6
pupeno

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

5
Tarun Lalwani

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);
2
Prakash Ayappan