Спринкоды SpringBoot и знак входа в параметрах запроса противоречивым образом - PullRequest
2 голосов
/ 04 мая 2019

У меня простой эхо-контроллер

@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-кодирование символа процента), затем веб-сервер 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();
}

Вопросы :

  1. Это ошибка в Spring Boot (или одном из компонентов, которые он использует)?
  2. Если нет, я что-то не так сделал?
  3. И практический вопрос: как передать значение параметра запроса, содержащее символ плюс, в тестах, написанных для WebTestClient? Если я не кодирую его вручную, он будет декодирован 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>

Проект доступен на GitHub: https://github.com/rpuch/springboot-plus-in-query-string

Просто запустите тесты (mvn clean test).

Ответы [ 2 ]

3 голосов
/ 04 мая 2019

См. выпуск

Ключ к пониманию этого заключается в том, что различные степени кодирования применяется к шаблону 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)

чтобы получить закодированный параметр запроса.

1 голос
/ 09 мая 2019

Добавление некоторых деталей.

Фрагмент, предложенный @Eugen Covaci, на самом деле работает:

            .uri(builder -> {
                return builder
                        .path("/param")
                        .queryParam("p", "{value}")
                        .build(value);
            })

Одна вещь, которая все еще остается странной, заключается в следующем: почему так получается, что написание (казалось бы)один и тот же код более многословно дает другой результат?Две формы (чисто литеральная и та, которая использует переменные URI) фактически используют разные уровни кодирования для параметров запроса, и это выглядит как нарушение Принципа наименьшего удивления для меня.

Похоже, что это вызвано наследиемпроблемы: ранее была ошибка: + символ всегда был закодирован, хотя это не должно быть.Теперь авторы исправили ошибку, но это создало несоответствие: .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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...