我使用最新的 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/