【问题标题】:What's the proper way to escape URL variables with Spring's RestTemplate when calling a Spring RestController?调用 Spring RestController 时使用 Spring 的 RestTemplate 转义 URL 变量的正确方法是什么?
【发布时间】:2018-10-30 03:20:22
【问题描述】:

调用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 变量的正确方法是什么?

具体来说,由于Spring is interpreting as spaces,我如何正确转义加号(+),所以,我需要对它们进行编码。

我尝试像这样使用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 并在其上调用 expand。它将返回一个正确转义 url 的字符串。
  • @ShayElkayam:你确定吗?

标签: spring resttemplate


【解决方案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 3986UriComponents 和扩展 UriTemplate 所基于的,要求在查询字符串中转义 +

规范对此的看法很简单:

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

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

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

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

如果您的 Web 框架(例如 Spring MVC!)将 + 解释为空格,则这是它的决定,而不是 URI 规范所要求的。

参考以上内容,您还会看到!$'()*+,; 没有被UriTemplate 转义。 =&amp; 转义了,因为 Spring 对查询字符串的外观采取了“自以为是”的看法——一系列 key=value 对。

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

当然,如果您只是非常合理地希望您的查询参数被转义,那么这些都不会对您有任何安慰!

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

【讨论】:

  • 问题是 Spring 在这里不一致。如果我通过调用 UriUtils.encode 将 + 预编码为 %2B,那么 % 会通过 UriTemplate 破坏字符串进行进一步编码。
  • 我不认为这不一致。如果您已经对参数进行了编码,那么 Spring 就无法知道这一点。你可以告诉它你已经用例如编码它们。 UriComponentsBuilder.fromUriString("http://example.com/?foo={foo}").queryParams(...).build(true).toUriString()
【解决方案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);
}

这是一种可怕的 hack,在某些情况下可能会失败。

【讨论】:

  • 查看我发布的答案,它使用了 URIBuilder 中的所有内容,并添加了一种解决方法来编码和发送查询组件。
【解决方案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】:

    您可以在 Spring 中使用 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);
    

    【讨论】:

    • 你的意思是foo而不是"{foo}"
    • 您可以像我在问题中那样使用变量foobar。另一个区别是我在我的问题中使用了一个模板。不确定这是否相关,但使用带有UriCompontentsBuilder 的模板,似乎无法逃脱+
    • 查看更新的答案。 UriComponentsBuilder 将适当地转义
    猜你喜欢
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    • 2020-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-08
    相关资源
    最近更新 更多