Spring-启动 WebFlux getFormData 无法解析真正的 x-www-form-urlencoded 数据?

Spring-Boot WebFlux getFormData fails to parse real x-www-form-urlencoded data?

我用最新最好的 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查询没有from-data解析出来?

在 webflux 中,你有一个订阅者(消费者)和一个发布者。

一个发布,一个消费。您的应用程序向消费者发布数据。因此,您有一个想要使用数据的客户端 (curl)。要使用它,curl 会订阅一个 webflux 流,而 webflux 会传送一个或多个对象。如果它是一个对象,它会传递一个 Mono<T> 如果它是多个对象,它会传递一个 Flux<T>.

我为什么要解释这个?

好吧:

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

您正在应用程序内部订阅。一旦你订阅,你就离开了反应世界,你失去了拥有 webflux 应用程序的所有好处,你消耗了数据。

所以最重要的是,您(几乎)不应该在自己的应用程序中订阅。如果您的应用程序是发起调用和消费的应用程序,那么请确保它可以订阅。但是这里是curl订阅了想要消费,所以你不应该。

您应该 return 来自 @PostMapping 注释函数的 Mono<Map<String, String>>

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

然后重写为return一个Mono:

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));
        });
}