Какой правильный способ избежать переменных 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

Используйте UriTemplate и вызовите его. Он вернет строку с правильно экранированным URL-адресом.

Shay Elkayam 20.05.2018 10:08

@ShayElkayam: ты в этом уверен?

pupeno 22.05.2018 10:20
22
2
5 700
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы можете использовать UriComponentsBuilder в 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);

Вы имеете в виду foo вместо "{foo}"?

pupeno 21.05.2018 12:17

Вы можете просто использовать переменные foo и bar, как я сделал в своем вопросе. Другое отличие состоит в том, что я использую в своем вопросе шаблон. Не уверен, что это актуально, но использование шаблона с UriCompontentsBuilder, похоже, не позволяет избежать +.

pupeno 22.05.2018 10:37

См. Обновленный ответ. UriComponentsBuilder соответствующим образом ускользнет

Prakash Ayappan 22.05.2018 18:39

Я начинаю верить, что это ошибка, и сообщил здесь: 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);
}

это ужасный взлом, который может потерпеть неудачу в некоторых ситуациях.

См. Ответ, который я опубликовал, он использует все из URIBuilder и добавляет обходной путь для кодирования и отправки компонентов запроса.

Tarun Lalwani 22.05.2018 15:36

Я думаю, ваша проблема здесь в том, что 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).

Проблема в том, что Spring здесь непоследовательна. И если я предварительно закодирую + как% 2B, вызвав UriUtils.encode, тогда% будет дополнительно закодирован с помощью UriTemplate, нарушающего строку.

pupeno 22.05.2018 16:24

Не думаю, что это непоследовательно. Если вы уже закодировали свои параметры, Spring не может этого знать. Вы можете сказать, что уже закодировали их, например UriComponentsBuilder.fromUriString("http://example.com/?foo=‌​{foo}").queryParams(‌​...).build(true).toU‌​riString()

ryanp 22.05.2018 16:47

Для этого я все же предпочел бы, чтобы кодировка решалась с помощью правильного метода, а не с помощью взлома, как вы. Я бы просто использовал что-то вроде ниже

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);
    }

}

Все еще не самый чистый из возможных способов, но лучше, чем жестко запрограммированный подход к замене.

Ответ принят как подходящий

По-видимому, правильный способ сделать это - определить фабрику и изменить режим кодирования:

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

Думаю, это решение можно применить и для этот вопрос.

Steffen Harbich 29.05.2018 08:41

Другие вопросы по теме