When calling RestTemplate.exchange
to do a get request, such as:
String foo = "fo+o"; String bar = "ba r"; restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)
what's the proper to have the URL variables correctly escaped for the get request?
Specifically, how do I get pluses (+
) correctly escaped because Spring is interpreting as spaces, so, I need to encode them.
I tried using UriComponentsBuilder
like this:
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());
and that printed:
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
The space is correctly escaped in some instances, but the plus is never escaped.
I also tried UriTemplate
like this:
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);
with the exact same result:
http://example.com/?foo=fo+o&bar=ba%20r
5 Answers
Answers 1
Apparently, the correct way of doing this is by defining a factory and changing the encoding mode:
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);
That prints out:
http://example.com/?foo=fo%2Bo&bar=ba%20r
This is documented here: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#web-uri-encoding
Answers 2
I think your problem here is that RFC 3986, on which UriComponents
and by extension UriTemplate
are based, does not mandate the escaping of +
in a query string.
The spec's view on this is simply:
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" pchar = unreserved / pct-encoded / sub-delims / ":" / "@" query = *( pchar / "/" / "?" ) URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
If your web framework (Spring MVC, for example!) is interpreting +
as a space, then that is its decision and not required under the URI spec.
With reference to the above, you will also see that !$'()*+,;
are not escaped by UriTemplate
. =
and &
are escaped because Spring has taken an "opinionated" view of what a query string looks like -- a sequence of key=value pairs.
Likewise, #[]
and whitespace are escaped because they are illegal in a query string under the spec.
Granted, none of this is likely to be any consolation to you if you just quite reasonably want your query parameters escaped!
To actually encode the query params so your web framework can tolerate them, you could use something like org.springframework.web.util.UriUtils.encode(foo, charset)
.
Answers 3
I'm starting to believe this is a bug and I reported here: https://jira.spring.io/browse/SPR-16860
Currently, my workaround is this:
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); }
which is a horrible hack that might fail in some situations.
Answers 4
For this one I would still prefer the encoding to be resolved using a proper method instead of using a Hack like you did. I would just use something like below
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();
And of course the class is like below
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); } }
Still not a cleanest possible way but better then doing a hardcoded replace approach
Answers 5
You could use UriComponentsBuilder in 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);
0 comments:
Post a Comment