【问题标题】:Why is Spring de-coding + (the plus character) on application/json get requests? and what should I do about it?为什么应用程序/json获取请求上的Spring解码+(加号)?我该怎么办?
【发布时间】:2018-10-20 13:48:25
【问题描述】:

我有一个 Spring 应用程序接收像 http://localhost/foo?email=foo+bar@example.com 这样的请求。这会触发一个大致如下所示的控制器:

@RestController
@RequestMapping("/foo")
public class FooController extends Controller {
    @GetMapping
    public void foo(@RequestParam("email") String email) {
       System.out.println(email)
    }
}

当我可以访问email 时,它已被转换为foo bar@example.com,而不是原来的foo+bar@example.com。根据When to encode space to plus (+) or %20?,这应该只发生在内容为application/x-www-form-urlencoded 的请求中。我的请求的内容类型为application/json。请求的完整 MIME 标头如下所示:

=== MimeHeaders ===
accept = application/json
content-type = application/json
user-agent = Dashman Configurator/0.0.0-dev
content-length = 0
host = localhost:8080
connection = keep-alive

为什么 Spring 将加号解码为空格?如果这是它应该工作的方式,why isn't it encoding pluses as %2B when making requests?

我发现了这个错误报告:https://jira.spring.io/browse/SPR-6291,这可能暗示这已在 3.0.5 版本上得到修复,并且我正在使用 Spring > 5.0.0。我可能会误解错误报告的某些内容。

我还发现了有关 RestTemplate 处理这些值的讨论:https://jira.spring.io/browse/SPR-5516(我的客户正在使用 RestTemplate)。

所以,我的问题是,Spring 为什么要这样做?我怎样才能禁用它?我应该禁用它还是应该在客户端上编码加号,即使请求是 json?

澄清一下,我在这里既没有使用 HTML 也没有使用 JavaScript。有一个 Spring Rest 控制器,客户端是 Spring 的 RestTemplateUriTemplateUriComponentsBuilder,它们都没有像 Spring 解码那样对加号进行编码。

【问题讨论】:

  • 我认为解码是正确的,如果您想发送+ 作为值的一部分,您不应该发送%2b+ 意味着成为 space,这就是你在这里得到的。您发布的问题是关于 url 解析而不是参数解析
  • @TarunLalwani:+ 表示application/x-www-form-urlencoded 中的空格。我正在发送application/json,其中 + 没有特定含义。
  • 您正在混合两件事,请求正文中的+ 意味着当标头有application/x-www-form-urlencoded 时有一个空格。到目前为止,我们讨论的是 url,而 url 根本不需要依赖 content-type
  • 另外,如果你想改变我相信你需要像stackoverflow.com/a/28214811/2830850一样配置过滤器
  • @TarunLalwani:据我所知,URI RFC 没有提到 + 需要编码为 %2b: tools.ietf.org/html/rfc3986。这是在 HTML4 中定义的:w3.org/TR/html4/interact/forms.html#h-17.13.4.1。此错误报告是相关的:jira.spring.io/browse/SPR-6296。同样,我在这里可能会感到困惑,我明白你为什么 URL 会依赖于内容类型。

标签: spring url encoding resttemplate


【解决方案1】:

原答案

您正在混合两件事,请求正文中的+ 意味着当标头具有application/x-www-form-urlencoded 时有一个空格。请求的正文或内容将取决于标头,但请求可以只有url,没有headers,也没有body

因此,URI 的编码不能由任何标头控制

请参阅https://en.wikipedia.org/wiki/Query_string 中的URL Encoding 部分

某些字符不能成为 URL 的一部分(例如空格),而某些其他字符在 URL 中具有特殊含义:例如,字符 # 可用于进一步指定一个小节(或片段)文档。在 HTML 表单中,字符 = 用于将名称与值分开。 URI 通用语法使用 URL 编码来处理这个问题,而 HTML 表单做了一些额外的替换,而不是对所有这些字符应用百分比编码。 SPACE 编码为“+”或“%20”。[10]

HTML 5 为通过“get”方法向 Web 服务器提交 HTML 表单指定了以下转换。1 以下是该算法的简要总结:

无法转换为正确字符集的字符被替换为 HTML 数字字符引用[11] SPACE 编码为“+”或“%20” 字母(A–Z 和 a–z)、数字 (0–9) 和字符 '*'、'-'、'.'和 '_' 保持原样 所有其他字符都编码为 %HH 十六进制表示,任何非 ASCII 字符首先编码为 UTF-8(或其他指定编码) RFC3986 允许在查询字符串中使用与波浪号(“~”)对应的八位字节,但要求在 HTML 表单中以百分比编码为“%7E”。

SPACE 编码为“+”和“原样”字符的选择将此编码与 RFC 3986 区分开来。

您也可以从下面的屏幕截图中看到 google.com 上的相同行为

您也可以在其他框架中看到相同的行为。下面是一个 Python Flask 的例子

所以你看到的是正确的,你只是将它与一个引用请求正文而不是 URL 的文档进行比较

Edit-1:5 月 22 日

在调试之后,似乎在 Spring 中甚至都没有进行解码。我发生在package org.apache.tomcat.util.buf;UDecoder 类中

/**
 * URLDecode, will modify the source.
 * @param mb The URL encoded bytes
 * @param query <code>true</code> if this is a query string
 * @throws IOException Invalid %xx URL encoding
 */
public void convert( ByteChunk mb, boolean query )
    throws IOException
{
    int start=mb.getOffset();

下面是实际发生转换的地方

    if( buff[ j ] == '+' && query) {
        buff[idx]= (byte)' ' ;
    } else if( buff[ j ] != '%' ) {

这意味着它是一个嵌入式 tomcat 服务器,它执行此翻译,而 spring 甚至不参与其中。如类代码中所示,没有配置可以更改此行为。所以你必须忍受它

【讨论】:

  • 将其与引用请求正文的文档进行比较是什么意思?
  • @pupeno,我的意思是回答问题中的According to When to encode space to plus (+) or %20? this should only happen in requests where the content is application/x-www-form-urlencoded
  • 我不认为维基百科是这件事的权威答案,我更愿意参考 RFC。但是,在您从维基百科复制的那个 sn-p 中,它说 HTML 5 进行了这些转换。我不会在任何地方接触 HTML。
  • 例如,UriTemplateUriComponentsBuilder 都不能转义加号:stackoverflow.com/questions/50432395/…
  • 让我看看我是否可以挖掘一些与same相关的RFC
【解决方案2】:

如果您有此要求:

http://localhost/foo?email=foo+bar@example.com

那么原始foo bar@example.com。如果你说原来的应该是foo+bar@example.com那么请求应该是

http://localhost/foo?email=foo%2Bbar@example.com

所以 Spring 正在按预期工作。也许在客户端上,您应该检查 URI 是否正确编码。客户端 URL 编码负责构建正确的 HTTP 请求。

如果您在 JavaScript 中生成请求,请参阅 encodeURI();如果您在 Spring 中生成请求,请参阅 uriToString()

构建您的请求字符串(? 之后的部分),不进行任何编码,使用未编码的值,例如 foo+bar@email.com,并且仅在最后,在实际在 GET 中使用它之前,使用任何编码对其进行编码可在客户端平台上使用。如果您想使用POST,那么您应该根据您选择的 MIME 类型对其进行编码。

【讨论】:

  • Spring 似乎将加号解析为空格,但不将加号编码为空格:stackoverflow.com/questions/50432395/…
  • @pupeno 该问题的OP与您有相同的问题。他使用.fromUriString()+ 正确解码为空格。但是,他没有使用.toUriString() 来获取正确编码的请求。
  • 顺便说一句,我没有在任何地方使用 JavaScript。
【解决方案3】:

SPR-6291v3.0.5 中修复了此问题,但在其他一些情况下仍未解决,例如SPR-11047 仍未解决。 SPR-6291 的优先级为Major,而SPR-11047 的优先级为Minor

去年我在旧 Spring 开发 REST API 时遇到了这个问题。我们可以通过多种方式在 Spring controller 中获取数据。所以其中两个是通过@RequestParam@PathVariable 注释

正如其他人提到的,我认为它的 spring 的内部问题并不具体属于 URL 编码,因为我通过 POST 请求发送数据,但它有点编码问题。但我也同意其他人的观点,因为现在它只在URL 中仍然存在问题。

所以有两种解决方案我知道:

  1. 您可以使用@PathVariable 而不是@RequestParam,因为从SPR-6291 开始,这个加号问题在@PathVariable已修复,并且对于@RequestParam 仍保持开放状态为@ 987654338@

  2. 我的 spring 版本甚至不接受通过 @PathVariable 注释的加号,所以这就是我克服问题的方法(我不记得它是一步一步的,但它会给你提示)。

在您的情况下,您可以在发送请求之前通过JSescape 加号获取字段。像这样的:

var email = document.getElementById("emailField").value;
email = email.replace('+', '%2B');

【讨论】:

  • 哇...所以,Spring 的 UriTemplateUriComponentsBuilder 与 Spring 的 URI 解析不一致,甚至 Spring 对 URI 的解析与使用不同的路径和请求规则的自身不一致参数。
  • @usama 对于 JS,OP 应该使用 encodeURI()
  • @pupeno 我也在.Net 中发现了这个uri 问题,不知道它是否还有问题。由于 uri 中的加号解码,这似乎是一个常见问题
  • 顺便说一句,最后一点不适用于我。我没有在任何地方使用 JavaScript。
  • 由于您使用的是 spring >5.0 @PathVariable 应该适合您。
猜你喜欢
  • 2020-03-29
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-25
  • 2012-09-27
  • 1970-01-01
相关资源
最近更新 更多