SpringBoot urlencodes plus sign in 查询参数不一致

Rom*_*kiy 3 java spring-boot

我有一个简单的回声控制器

@RestController
public class EchoController {
    @GetMapping(path = "/param", produces = MediaType.TEXT_PLAIN_VALUE)
    String echoParam(@RequestParam("p") String paramValue) {
        return paramValue;
    }

    @GetMapping(path = "/path-variable/{val}", produces = MediaType.TEXT_PLAIN_VALUE)
    String echoPathVariable(@PathVariable("val") String val) {
        return val;
    }
}
Run Code Online (Sandbox Code Playgroud)

它的一种方法与它所呈现的参数的值相呼应;第二个对通过 URI 提供的值执行相同的操作。

我有以下测试:

@Autowired
private WebTestClient webTestClient;

@Test
public void rawPlus_inQueryParam() {
    String value = "1+1";

    String response = getValueEchoedThroughQueryParam(value);

    assertThat(response, is(equalTo(value)));
}

@Test
public void urlencodedPlus_inQueryParam() {
    String value = "1%2B1";

    String response = getValueEchoedThroughQueryParam(value);

    assertThat(response, is(equalTo(value)));
}

private String getValueEchoedThroughQueryParam(String value) {
    return webTestClient.get()
            .uri(builder -> {
                return builder
                        .path("/param")
                        .queryParam("p", value)
                        .build();
            })
            .exchange()
            .expectStatus().is2xxSuccessful()
            .expectBody(String.class)
            .returnResult()
            .getResponseBody();
}
Run Code Online (Sandbox Code Playgroud)

这两个测试都只是通过查询参数发送一个字符串,读取响应并断言内容被正确回显。

第一次测试失败:

java.lang.AssertionError: 
Expected: is "1+1"
     but: was "1 1"
Run Code Online (Sandbox Code Playgroud)

第二次测试通过。

看起来在第二个测试中,WebTestClienturl 对值进行了编码(实际上,它只是对百分比字符进行了 url 编码),然后网络服务器对其进行了 url 解码,一切正常。但是,在第一次测试中,客户端确实没有URL编码,加上性格,但服务器URL解码它,因此它得到一个空格字符。

这看起来是不一致的。我怀疑我可以做一些愚蠢的事情来导致它,因为一切都在默认模式下工作;实际上,我在这里展示的控制器几乎是应用程序唯一的代码(Application 类本身是默认的,生成后我没有接触它)。

只是为了比较:在 URI 中传递相同的数据时,一切正常。以下测试通过:

@Test
public void rawPlus_inPathVariable() {
    String value = "1+1";

    String response = getValueEchoedThroughPathVariable(value);

    assertThat(response, is(equalTo(value)));
}

@Test
public void urlencodedPlus_inPathVariable() {
    String value = "1%2B1";

    String response = getValueEchoedThroughPathVariable(value);

    assertThat(response, is(equalTo(value)));
}

private String getValueEchoedThroughPathVariable(String value) {
    return webTestClient.get()
            .uri("/path-variable/" + value)
            .exchange()
            .expectStatus().is2xxSuccessful()
            .expectBody(String.class)
            .returnResult()
            .getResponseBody();
}
Run Code Online (Sandbox Code Playgroud)

问题是

  1. 这是 Spring Boot(或它使用的组件之一)中的错误吗?
  2. 如果没有,我做错了什么吗?
  3. 以及实际问题:如何在针对 编写的测试中传递包含加号字符的查询参数值WebTestClient?如果我不手动对其进行 url 编码,它会由网络服务器进行 url 解码;如果我这样做,它会第二次进行 url 编码,因此服务器会(在 url 解码后)获得 url 解码的版本。

Spring Boot 版本是2.1.4.RELEASE(撰写本文时的最新版本)。

POM如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-plus-in-query-string</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-plus-in-query-string</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Run Code Online (Sandbox Code Playgroud)

该项目在 GitHub 上可用:https : //github.com/rpuch/springboot-plus-in-query-string

只需运行测试 ( mvn clean test)。

小智 6

看到这个问题

理解这一点的关键是,不同程度的编码应用于 URI 模板与 URI 变量。换句话说:

http://example.com/a/{b}/c?q={q}&p={p}

除了 URI 变量占位符之外,URI 模板就是一切。然而,您展示的代码片段只构建了一个没有任何变量的 URI 文字,因此无论使用哪种方法,级别编码都是相同的,只有非法字符。

所以它也必须是这样的:

.queryParam("foo", "{foo}").buildAndExpand(foo)

因此,我们的想法是使用类似的东西:

builder
      .path("/param")
      .queryParam("p", "{value}")
      .build(value)
Run Code Online (Sandbox Code Playgroud)

获取编码的查询参数。