example - ¿Cuál es la forma correcta de evitar las variables de URL con Spring''s RestTemplate cuando se llama a Spring RestController?
spring mvc example (5)
Al llamar a RestTemplate.exchange
para realizar una solicitud de obtención, como:
String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)
¿cuál es el adecuado para que las variables de URL se hayan escapado correctamente para la solicitud de obtención?
Específicamente, ¿cómo puedo obtener ventajas ( +
) escapadas correctamente porque Spring interpreta como espacios , por lo tanto, necesito codificarlos?
Intenté usar UriComponentsBuilder
esta manera:
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());
y que imprimió:
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
El espacio se ha escapado correctamente en algunos casos, pero la ventaja nunca se escapa.
También probé UriTemplate
esta manera:
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);
con el mismo resultado exacto:
http://example.com/?foo=fo+o&bar=ba%20r
Aparentemente, la forma correcta de hacerlo es definiendo una fábrica y cambiando el modo de codificación:
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);
Eso imprime:
http://example.com/?foo=fo%2Bo&bar=ba%20r
Esto se documenta aquí: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#web-uri-encoding
Creo que su problema aquí es que el RFC 3986 , en el que se UriComponents
y, por extensión, UriTemplate
, no obliga a escapar de +
en una cadena de consulta.
La opinión de la especificación sobre esto es simplemente:
sub-delims = "!" / "$" / "&" / "''" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
Si su marco web (Spring MVC, por ejemplo!) Está interpretando +
como un espacio, entonces esa es su decisión y no se requiere según las especificaciones de URI.
Con referencia a lo anterior, también verá que !$''()*+,;
UriTemplate
no escapa. =
y &
se escapan porque Spring ha tomado una vista "opinada" de cómo se ve una cadena de consulta: una secuencia de pares clave = valor.
Del mismo modo, #[]
y los espacios en blanco se escapan porque son ilegales en una cadena de consulta bajo la especificación.
Concedido, es probable que nada de esto le sirva de consuelo si simplemente desea que se escapen los parámetros de consulta.
Para codificar realmente los parámetros de consulta para que su marco web pueda tolerarlos, puede usar algo como org.springframework.web.util.UriUtils.encode(foo, charset)
.
Estoy empezando a creer que esto es un error y lo informé aquí: https://jira.spring.io/browse/SPR-16860
Actualmente, mi solución es la siguiente:
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);
}
que es un hack horrible que podría fallar en algunas situaciones.
Para este, prefiero que la codificación se resuelva utilizando un método adecuado en lugar de usar un Hack como lo hiciste tú. Solo usaría algo como abajo
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();
Y por supuesto la clase es como abajo.
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);
}
}
Aún no es la forma más limpia posible, pero es mejor que hacer un enfoque de reemplazo codificado
Podría usar UriComponentsBuilder en Spring (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);