Spring-Boot WebFlux getFormData не может проанализировать реальные данные x- www-form-urlencoded? - PullRequest
0 голосов
/ 01 марта 2020

Я сделал небольшой тестовый проект с самой последней версией Spring-Boot V2.2.5 и стартером WebFlux.

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- ... -->

    <properties>
        <java.version>11</java.version>
    </properties>

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

    <!-- ... -->

В этом проекте я создал действительно простой @RestController для разбора x- www-form-urlencoded, подобный этому :

@RestController
public class MyController {

    /*
     This worked in Tomcat stack, but it's unfortunately too old-school for WebFlux...

     java.lang.IllegalStateException: In a WebFlux application, form data is accessed via ServerWebExchange.getFormData().
         at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:158) ~[spring-webflux-5.2.4.RELEASE.jar:5.2.4.RELEASE]
         Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
     Error has been observed at the following site(s):
         |_ checkpoint ⇢ HTTP POST "/" [ExceptionHandlingWebHandler]
     Stack trace:
         ...

    @PostMapping
    public Map<String, String> test(@RequestBody MultiValueMap<String, String> formData) {
        String formDataTest = formData.getFirst("test");
        String result = Objects.requireNonNullElse(formDataTest, "you failed!");
        return Map.of("result", result);
    }
    */

    @PostMapping
    public Map<String, String> test(ServerWebExchange serverWebExchange) {
        MultiValueMap<String, String> formData = getFormData(serverWebExchange);
        String formDataTest = formData.getFirst("test");
        String result = Objects.requireNonNullElse(formDataTest, "you failed!");
        return Map.of("result", result);
    }

    private static MultiValueMap<String, String> getFormData(ServerWebExchange serverWebExchange) {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        serverWebExchange.getFormData().subscribe(formData::addAll);
        return formData;
    }
}

Чтобы проверить это, я написал следующее ...

@WebFluxTest(controllers = MyController.class)
class MyControllerTest {

    @Autowired
    private WebTestClient webClient;

    @Test
    void test() {
        webClient.post()
                .uri("/")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData("test", "testing-test"))
                .exchange()
                .expectStatus().is2xxSuccessful()
                .expectBody()
                .jsonPath("$.result").isEqualTo("testing-test");
    }
}

... и этот тест станет зеленым. Отличный материал!

К сожалению, для реальных данных это не получается. Протестировано в Postman и curl - этот запрос должен работать, насколько я понимаю, но ...

➜  ~ curl --location --request POST 'localhost:8080' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'test=testing-test'
{"result":"you failed!"}%

Я что-то здесь не так сделал? Почему исходные данные не анализируются при запросе curl?

1 Ответ

1 голос
/ 01 марта 2020

в webflux у вас есть то, что называется подписчиком (потребителем) и издателем.

Один публикует, другой потребляет. Ваше приложение публикует данные для потребителя. Итак, у вас есть клиент (curl), который хочет использовать данные. Чтобы использовать его, curl подписывается на поток webflux, а webflux доставляет один или несколько объектов. Если один объект доставляет Mono<T>, если его много, Flux<T>.

Почему я объясняю это?

Ну вот:

serverWebExchange.getFormData().subscribe(formData::addAll)

вы подписываетесь внутри вашей заявки. Как только вы подписываетесь, вы покидаете реактивный мир, и вы теряете все преимущества, связанные с наличием приложения webflux и потреблением данных.

Таким образом, суть в том, что вы (почти) никогда не должны подписываться самостоятельно применение. Если ваше приложение инициирует вызов и потребляет его, то обязательно оно может подписаться. Но здесь есть curl, который подписывается и хочет потреблять, поэтому вы не должны.

Вместо этого вы должны вернуть Mono<Map<String, String>> из вашей @PostMapping аннотированной функции.

public Mono<Map<String, String>> test(ServerWebExchange serverWebExchange)

и затем перепишите, чтобы вернуть моно:

private static Mono<MultiValueMap<String, String> >getFormData(ServerWebExchange serverWebExchange) {
    return serverWebExchange.getFormData()
        .flatMap(formData -> {
            MultiValueMap<String, String> formDataResponse = new LinkedMultiValueMap<>();
            return Mono.just(formDataResponse.addAll(formData));
        });
}
...