Spring Boot PostMapping:如果没有内容类型,如何强制执行JSON解码



我正在使用Spring Boot(2.3.2(实现一个简单的rest控制器,以通过POST从外部源接收数据。该源不随它一起发送ContentType,而是提供了一个包含JSON的主体。

我的控制器真的很简单

@RestController
@RequiredArgsConstructor
public class WaitTimeImportController {
private final StorageService storageService;
@PostMapping(value = "/endpoint")
public void setWaitTimes(@RequestBody DataObject toStore) {
storageService.store(toStore);
}
}
@Data
@NoArgsConstructor
class DataObject {
private String data;
}
@Service
class StorageService {
void store(DataObject do) {
System.out.println("received");
}
}

现在,如果我向它发送一个json字符串,它将不会触发该请求映射。

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class WaittimesApplicationTests {
@Test
void happyPath() throws IOException {
WebClient client = client();
String content = "{ "data": "test"} ";
AtomicBoolean ab = new AtomicBoolean();
await().atMost(Duration.FIVE_SECONDS).ignoreExceptions()
.until(() -> {
ClientResponse response = client.post()
// .header("Content-Type", "application/json")
.bodyValue(content)
.exchange()
.timeout(java.time.Duration.ofMillis(200))
.block();
ab.set(response.statusCode() == HttpStatus.ACCEPTED);
return ab.get();
});
}
}

除非我取消注释";标题";上面的行;那就好了。

由于我对提交POST请求的源没有影响,所以我不能添加application/json作为请求头。

我在春季搜索了很多关于内容协商的内容,但我尝试过的都没有成功。

  • @PostMapping注释添加consumes="application/json"
  • @PostMapping注释添加consumes=MediaType.ALL_VALUE
  • 添加MappingJackson2HttpMessageConverterbean不起作用
  • 添加CCD_ 7 bean没有编译";无法访问javax.servlet.ServletException"-这是一条值得走的路吗

我能做些什么来强制映射接受非";内容类型";请求并将其解码为JSON?

Ugg。因此,Lebecca的评论在如何使用Spring调整头字段的过程中发出(我使用的是webflux,所以链接的解决方案本身不适用(,我找不到合适的教程,文档在搜索时遇到的几个页面中有点分散。

这里有一些障碍,所以这里有一个浓缩版本。

干净的方式:调整标题以注入内容类型`

因此,您可以创建一个WebFilter,在请求实际到达映射之前对其进行评估。只需定义一个实现它的@Component

要添加到传递的ServerWebExchange对象,请将其包装在ServerWebExchangeDecorator中。

@Component
class MyWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(new MyServerExchangeDecorator(exchange));
}
}

该装饰器可以同时操作请求和响应;只需覆盖适当的方法。当然,请求也需要修饰——不能继续处理原始对象(如果你试图设置它的属性,它实际上会抛出一个异常,因为它是只读的(。

class MyServerExchangeDecorator extends ServerWebExchangeDecorator {
protected MyServerExchangeDecorator(ServerWebExchange delegate) {
super(delegate);
}
@Override
public ServerHttpRequest getRequest() {
return new ContentRequestDecorator(super.getRequest());
}
}

现在您只需实现ContentRequestDecorator,就可以开始了。

class ContentRequestDecorator extends ServerHttpRequestDecorator {
public ContentRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = HttpHeaders.writableHttpHeaders(super.getHeaders());
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
}

尝试仅对传入的对象调用setContentType会导致OperationNotSupportedException,因为它也是只读的;从而产生CCD_ 15呼叫。

当然,这将调整所有传入请求的所有标头,因此,如果您计划这样做,并且不想要核选项,请事先检查请求。

简单的方法

因此,上面的方式触发了传入请求到consumes="application/json"端点的Springs映射。

然而,如果你没有读到我之前的描述中有一点讽刺的意味,我应该不会那么含蓄。对于一个非常简单的问题,这是一个非常复杂的解决方案。

我最终的做法是

@Autowired
private ObjectMapper mapper;
@PostMapping(value = "/endpoint")
public void setMyData(HttpEntity<String> json) {
try {
MyData data = mapper.readValue(json.getBody(), MyData.class);
storageService.setData(data);
} catch (JsonProcessingException e) {
return ResponseEntity.unprocessableEntity().build();
}
}