在调用Spring RestController时,使用Spring的RestTemplate转义URL变量的正确方法是什么?

pup*_*eno 21 spring resttemplate

在调用RestTemplate.exchangeget请求时,例如:

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

为get请求正确转义URL变量的正确方法是什么?

具体来说,我如何+正确地转义pluses()因为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());
Run Code Online (Sandbox Code Playgroud)

并打印:

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
Run Code Online (Sandbox Code Playgroud)

在某些情况下,空间被正确转义,但加号永远不会被转义.

我也尝试过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);
Run Code Online (Sandbox Code Playgroud)

结果完全相同:

http://example.com/?foo=fo+o&bar=ba%20r
Run Code Online (Sandbox Code Playgroud)

pup*_*eno 14

显然,正确的方法是定义工厂并更改编码模式:

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);
Run Code Online (Sandbox Code Playgroud)

打印出:

http://example.com/?foo=fo%2Bo&bar=ba%20r
Run Code Online (Sandbox Code Playgroud)

这在此处记录:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#web-uri-encoding


rya*_*anp 9

我认为你的问题在于RFC 3986,UriComponents扩展UriTemplate所依据的RFC 3986 并不强制要求+在查询字符串中进行转义.

该规范对此的看法很简单:

sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="

pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"

query       = *( pchar / "/" / "?" )

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
Run Code Online (Sandbox Code Playgroud)

如果您的Web框架(例如Spring MVC!)正在解释+为空间,那么这就是它的决定,而不是URI规范下的要求.

参考上面的内容,您还将看到!$'()*+,;未被转义的内容UriTemplate.=并且因为Spring对查询字符串的外观 - 一系列键=值对 - 采取了"自以为是"的观点而& 转义.

同样,#[]并且空格转义,因为它们在规范下的查询字符串中是非法的.

当然,如果您非常合理地希望查询参数被转义,这一切都不会给您带来任何安慰!

要实际编码查询参数,以便您的Web框架可以容忍它们,您可以使用类似的东西org.springframework.web.util.UriUtils.encode(foo, charset).


pup*_*eno 6

我开始相信这是一个错误,我在这里报道: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);
}
Run Code Online (Sandbox Code Playgroud)

这是一个可怕的黑客,在某些情况下可能会失败.


Tar*_*ani 5

对于这个,我仍然希望使用适当的方法解决编码,而不是像你一样使用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();
Run Code Online (Sandbox Code Playgroud)

当然课程如下

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

}
Run Code Online (Sandbox Code Playgroud)

仍然不是最干净的方式,但更好的是做一个硬编码的替换方法