Как правильно экранировать URL-переменные с помощью RestTemplate Spring при вызове Spring RestController? - PullRequest
0 голосов
/ 20 мая 2018

При вызове RestTemplate.exchange для выполнения запроса get, например:

String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)

, как правильно корректно экранировать переменные URL для запроса get?

В частности, какправильно ли я получаю плюсы (+), потому что 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

Ответы [ 5 ]

0 голосов
/ 22 мая 2018

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

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

0 голосов
/ 22 мая 2018

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

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

}

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

0 голосов
/ 22 мая 2018

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

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

0 голосов
/ 22 мая 2018

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

0 голосов
/ 20 мая 2018

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