Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/317.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java SpringBoot URL以不一致的方式对查询参数中的加号进行编码_Java_Spring Boot - Fatal编程技术网

Java SpringBoot URL以不一致的方式对查询参数中的加号进行编码

Java SpringBoot URL以不一致的方式对查询参数中的加号进行编码,java,spring-boot,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 = "

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

@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;
    }
}
其中一种方法与所给出的参数值相呼应;第二种方法使用通过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();
}
这两个测试都只是通过查询参数发送一个字符串,读取响应并断言内容得到了正确响应

第一次测试失败:

java.lang.AssertionError: 
Expected: is "1+1"
     but: was "1 1"
第二次测试通过

看起来在第二个测试中,
WebTestClient
url对值进行编码(实际上,它只是url对百分比字符进行编码),然后web服务器url对其进行解码,一切正常。但是在第一个测试中,客户机不对加号字符进行url编码,而服务器对加号字符进行url解码,因此得到一个空格字符

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

只是比较一下:当在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();
}
问题是

  • 这是Spring Boot(或它使用的组件之一)中的错误吗
  • 如果不是,我做错什么了吗
  • 还有一个实际问题:在针对
    WebTestClient
    编写的测试中,如何传递包含加号字符的查询参数值?如果我不手动对其进行url编码,它将由web服务器对url进行解码;如果我这样做,它会第二次得到url编码,所以服务器(在url解码后)会得到一个url解码版本
  • Spring Boot版本是
    2.1.4.发行版
    (在撰写本文时的最新版本)

    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>
    
    
    4.0.0
    org.springframework.boot
    

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

    请参见此

    理解这一点的关键是不同程度的编码是不同的 应用于URI模板vs URI变量。换言之:

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

    URI模板是除URI变量之外的所有内容 占位符。但是,您显示的代码片段只构建了一个URI literal没有任何变量,因此级别编码是相同的,只是 非法字符,无论使用哪种方法

    所以它也必须是这样的:

    builder
          .path("/param")
          .queryParam("p", "{value}")
          .build(value)
    
    .queryParam(“foo”,“{foo}”).buildAndExpand(foo)

    因此,我们的想法是使用如下内容:

    builder
          .path("/param")
          .queryParam("p", "{value}")
          .build(value)
    
    以获取已编码的查询参数。

    添加一些详细信息

    @Eugen Covaci建议的代码片段实际上是有效的:

                .uri(builder -> {
                    return builder
                            .path("/param")
                            .queryParam("p", "{value}")
                            .build(value);
                })
    
    有一件事仍然很奇怪:为什么以更详细的方式编写(似乎)相同的代码会产生不同的结果?两种形式(纯文本和使用URI变量的形式)实际上对查询参数使用了不同的编码级别,这似乎违反了我最不惊讶的原则

    看起来这是由遗留问题造成的:之前,有一个bug:
    +
    字符总是被编码的,尽管它不应该被编码。现在,作者修复了这个bug,但它产生了一个不一致性:
    .queryParam(“p”),“{value}”).build(value)
    .queryParam(“p”),value.build()不同

    看来你必须知道/记住这一点

    完整的工作方法代码如下所示

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

    可能重复@net它看起来不像一个复制品。在那里,OP想要禁用解码。在这里,我想理解为什么编码+解码是不一致的,并知道如何修复它:通过修复Tomcat中的错误,通过修复web客户端中的错误,或者做其他事情。这种行为是标准的。你试过“1%2B1”吗?@Bohemian是的,我试过了。问题是:)
    %
    被编码,所以我们在请求中得到双编码加字符;web服务器解码一次,控制器得到
    1%2B1
    而不是
    1+1
    。谢谢你,Eugen!我还在另一个答案中添加了一些细节。当使用
    build
    时,它将进行编码,当不使用
    build
    时,它假定值已经编码。在这种情况下,
    +
    是空白的编码值。@M.Deinum对不起,我不明白:你怎么能不使用
    build()
    (2个中的任何一个)?没有它,您就有了
    UriBuilder
    ,但必须返回
    URI
    。此外,如果执行
    .queryParam(“p”,“1+1”).build()
    ,它不会对加号进行编码。这就是我要说明的。当使用path/variable SUBSTION时,它假定需要进行编码;当不进行path/variable SUBSTION时,它假定值已经编码。因此,对于
    build(value)
    您使用的是path/variable substation,而对于
    build()
    您不是。@M.Deinum我明白您的意思。是的,当您熟悉此工具的逻辑时,此行为似乎是合乎逻辑的。但当你不这样做时,情况似乎并非如此:)当然,这可能只是我个人的问题。文档规则。主要区别在于是否使用变量substation。