对 URL 查询参数进行编码,使其可以有一个“+”

Encoding a URL Query Parameter so it can have a '+'

显然,在从 Spring Boot 1 到 Spring Boot 2 (Spring 5) 的移动中,RestTemplates 的 URL 参数的编码行为发生了变化。在传递的剩余模板上获取通用查询参数似乎异常困难,以便正确转义具有特殊含义(例如“+”)的字符。看起来,因为“+”是一个有效字符,所以它不会被转义,即使它的含义被改变了(见here)。这看起来很奇怪,违反直觉,并且违反所有其他平台上的所有其他惯例。更重要的是,我不知道如何轻松绕过它。如果我先对字符串进行编码,它会被双重编码,因为“%”会被重新编码。无论如何,这看起来应该是框架所做的非常简单的事情,但我没有弄明白。

这是我在 Spring Boot 1 中运行的代码:

  String url = "https://base/url/here";
  UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  for (Map.Entry<String, String> entry : query.entrySet()) {
    builder.queryParam(entry.getKey(), entry.getValue());
  }
  HttpEntity<TheResponse> resp = myRestTemplate.exchange(builder.toUriString(), ...);

但是,现在它不会对“+”字符进行编码,所以另一端将其解释为space。在 Java Spring Boot 2 中构建此 URL 的正确方法是什么?

注意 - 我也试过了,但它实际上对所有内容进行了双重编码:

try {
  for (Map.Entry<String, String> entry : query.entrySet()) {
    builder.queryParam(entry.getKey(), URLEncoder.encode(entry.getValue(),"UTF-8" ));
  }
} catch(Exception e) {
  System.out.println("Encoding error");
}

在第一个中,如果我输入“q”=>“abc+1@efx.com”,那么,正好在 URL 中,我得到“abc+1@efx.com"(即根本不编码)。但是,在第二个中,如果我输入“abc+1@efx.com”,那么我会得到“abc%252B1%2540efx.com”,这是双重编码的。

可以手写一个编码方法,但这似乎 (a) 有点矫枉过正,并且 (b) 自己编码是安全问题和奇怪的错误容易蔓延的地方在。但对我来说,您不能只在 Spring Boot 2 中添加查询参数,这似乎很疯狂。这似乎是一项基本任务。我错过了什么?

找到了我认为不错的解决方案。事实证明,问题的很大一部分实际上是“交换”函数,它接受一个 URL 的字符串,然后出于我无法理解的原因重新编码 URL。但是,交换函数可以发送 java.net.URI 代替。在这种情况下,它不会尝试插入任何内容,因为它已经是一个 URI。然后我使用 java.net.URLEncoder.encode() 对片段进行编码。我仍然不知道为什么这不是 Spring 中的标准,但这应该有效。

    private String mapToQueryString(Map<String, String> query) {
        List<String> entries = new LinkedList<String>();
        for (Map.Entry<String, String> entry : query.entrySet()) {
            try {
                entries.add(URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"));
            } catch(Exception e) {
                log.error("Unable to encode string for URL: " + entry.getKey() + " / " + entry.getValue(), e);
            }
        }
        return String.join("&", entries);
    }

    /* Later in the code */
    String endpoint = "https://baseurl.example.com/blah";
    String finalUrl = query.isEmpty() ? endpoint : endpoint + "?" + mapToQueryString(query);
    URI uri;
    try {
        uri = new URI(finalUrl);
    } catch(URISyntaxException e) {
        log.error("Bad URL // " + finalUrl, e);
            return null;
        }
    }
    /* ... */
    HttpEntity<TheResponse> resp = myRestTemplate.exchange(uri, ...)