java - Spring-Boot WebFlux getFormData 无法解析真实的 x-www-form-urlencoded 数据?

标签 java spring-boot spring-webflux form-data

我使用最新的 Spring-Boot V2.2.5 和 WebFlux starter 做了一个小测试项目。

    <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订阅并想要消费,所以你不应该。

您应该返回 Mono<Map<String, String>>来自您的@PostMapping带注释的函数。

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

然后重写以返回 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));
        });
}

关于java - Spring-Boot WebFlux getFormData 无法解析真实的 x-www-form-urlencoded 数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60470472/

相关文章:

spring-boot - 我应该明确验证 Keycloak token 还是由 Keycloak 适配器完成?

rx-java - Java Spring WebFlux 与 RxJava

project-reactor - 在计划任务中使用 Flux

java - 我应该用 run(){} 包围哪些代码?

Java 无法将reactor.core.publisher.MonoDefer 转换为EntityClass

JavaFX UI 元素未显示在 Stage 中

java - 使用 JUnit 和 Mockito 编写单元测试

Spring Transaction - 在特定方法下发生错误时不回滚

java - 在 drools 的列表中收集事实的任何特定属性值

java - spring WebClient 如何从使用 http/1.1 的 spring webflux 服务器接收流数据