什么';这是使用Spring';转义URL变量的正确方法;调用Spring RestController时是否使用RestTemplate?

什么';这是使用Spring';转义URL变量的正确方法;调用Spring RestController时是否使用RestTemplate?,spring,resttemplate,Spring,Resttemplate,调用restemplate.exchange执行get请求时,例如: String foo = "fo+o"; String bar = "ba r"; restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar) 对于get请求,正确转义URL变量的正确方法是什么 具体来说,我如何正确地转义加号(+),因为我需要对它们进行编码 我试着像这样使用UriCo

调用
restemplate.exchange
执行get请求时,例如:

String foo = "fo+o";
String bar = "ba r";
restTemplate.exchange("http://example.com/?foo={foo}&bar={bar}", HttpMethod.GET, null, foo, bar)
对于get请求,正确转义URL变量的正确方法是什么

具体来说,我如何正确地转义加号(
+
),因为我需要对它们进行编码

我试着像这样使用
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());
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
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";
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());
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);
您可以在Spring中使用(org.springframework.web.util.UriComponentsBuilder)


我开始相信这是一个bug,我在这里报告:

目前,我的解决办法是:

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

这是一种可怕的黑客攻击,在某些情况下可能会失败。

我认为您这里的问题是,
UriComponents
和扩展的
UriTemplate
所基于的,并不要求在查询字符串中转义
+

规范对此的看法很简单:

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

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

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

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
如果您的web框架(例如SpringMVC!)将
+
解释为一个空间,那么这是它的决定,而不是URI规范所要求的

参考以上内容,您还将看到
!$”()*+,;不会被
UriTemplate
转义
=
&
被转义,因为Spring对查询字符串的外观采取了“自以为是”的观点——一系列键=值对

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

诚然,如果您只是合理地希望您的查询参数被转义,那么这些都不可能给您带来任何安慰


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

的方法,而不是像您这样使用黑客来解决编码问题。我会用下面的方法

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

}
类MyUriComponentsBuilder扩展了UriComponentsBuilder{
受保护的UriComponentsBuilder原始生成器;
公共MyUriComponentsBuilder(UriComponentsBuilder生成器){
//TODO自动生成的构造函数存根
原始生成器=生成器;
}
公共静态MyUriComponentsBuilderfromURI字符串(字符串uri){
返回新的MyUriComponentsBuilder(UriComponentsBuilder.fromUriString(uri));
}
@凌驾
公共组件buildAndExpand(对象…值){
//TODO自动生成的方法存根
对于(int i=0;i

仍然不是一种最干净的方法,但比采用硬编码替换方法更好。显然,正确的方法是定义工厂并更改编码模式:

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

这一点记录在这里:

使用UriTemplate并在其上调用expand。它将返回一个url正确转义的字符串。@ShayElkayam:你确定吗?你是说
foo
而不是
“{foo}”
?你可以像我在问题中那样使用变量
foo
bar
。另一个区别是,我在问题中使用了一个模板。不确定这是否相关,但使用带有
UricomontentsBuilder
的模板,似乎无法逃避
+
。请参阅更新的答案。UriComponentsBuilder将适当地转义我发布的答案,这使用了URIBuilder中的所有内容,并添加了一个解决方案来编码和发送查询组件。问题是Spring在这里不一致。如果我通过调用UriUtils.encode将+预编码为%2B,那么,%将通过UriTemplate破坏字符串得到进一步编码。我不认为这是不一致的。如果您已经对参数进行了编码,Spring就不会知道了。您可以告诉它您已经用例如
UriComponentsBuilder.fromUristering(“http://example.com/?foo={foo}”).queryParams(…).build(true).toUriString()
我认为这个解决方案可以应用于。