Каков правильный способ избежать URL-переменных с Spring RestTemplate при вызове Spring RestController?
При вызове RestTemplate.exchange
для запроса запроса, например:
String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)
что правильно, чтобы переменные URL правильно экранировались для запроса на получение?
В частности, как мне получить плюсы (+
) правильно экранированы, потому что Spring интерпретирует как пробелы, поэтому мне нужно их закодировать.
Я попытался использовать UriComponentsBuilder
следующим образом:
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());
и что напечатано:
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
В некоторых случаях пробел корректно экранируется, но плюс никогда не ускользает.
Я также пробовал UriTemplate
следующим образом:
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);
с тем же результатом:
http://example.com/?foo=fo+o&bar=ba%20r
Ответы
Ответ 1
По-видимому, правильный способ сделать это - определить фабрику и изменить режим кодирования:
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);
Это печатает:
http://example.com/?foo=fo%2Bo&bar=ba%20r
Это описано здесь: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#web-uri-encoding
Ответ 2
Я думаю, что ваша проблема здесь в том, что RFC 3986, на котором UriComponents
и на основе UriTemplate
, не дает возможности экранировать +
в строке запроса.
Спектральный вид на этом просто:
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
Если ваша веб-среда (Spring MVC, например!) Интерпретирует +
как пробел, то это его решение и не требуется по спецификации URI.
Ссылаясь на вышесказанное, вы также увидите, что !$'()*+,;
не экранируются UriTemplate
. =
и &
экранированы, потому что Spring приняла "упрямое" представление о том, как выглядит строка запроса, - последовательность пар ключ = значение.
Аналогично, #[]
и пробелы экранируются, поскольку они являются незаконными в строке запроса в спецификации.
Конечно, ничто из этого, скорее всего, не будет для вас утешением, если вы просто достаточно хотите, чтобы ваши параметры запроса сбежали!
Чтобы действительно кодировать параметры запроса, чтобы ваша веб-среда могла их терпеть, вы можете использовать что-то вроде org.springframework.web.util.UriUtils.encode(foo, charset)
.
Ответ 3
Я начинаю верить, что это ошибка, и я сообщил здесь: https://jira.spring.io/browse/SPR-16860
В настоящее время мой подход к решению этой проблемы:
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);
}
который является ужасным взломом, который может потерпеть неудачу в некоторых ситуациях.
Ответ 4
Для этого я все же предпочел бы, чтобы кодирование было разрешено с использованием правильного метода вместо использования Hack, как вы. Я бы просто использовал что-то вроде ниже
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();
И, конечно, класс подобен ниже
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);
}
}
Тем не менее, это не самый чистый способ, но лучше, чем делать жесткую замену
Ответ 5
Вы можете использовать UriComponentsBuilder весной (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);