Spring Boot PostMapping:如果不存在 Content-Type,如何强制执行 JSON 解码 [英] Spring Boot PostMapping: How to enforce JSON decoding if no Content-Type present

查看:73
本文介绍了Spring Boot PostMapping:如果不存在 Content-Type,如何强制执行 JSON 解码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Boot (2.3.2) 实现一个简单的休息控制器,以通过 POST 从外部源接收数据.该源不随其发送 Content-Type,而是提供包含 JSON 的正文.

I'm implementing a simple rest controller with Spring Boot (2.3.2) to receive data from an external source via POST. That source doesn't send a Content-Type with it, but provides a body containing 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 字符串,它将不会触发该请求映射.

Now, if I send it a json String, it will not trigger that request mapping.

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

除非我取消对header"的注释,否则此测试将失败.上面的线;那么就可以了.

This test fails unless I uncomment the "header" line above; then it is fine.

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

Since I have no influence over the source submitting the POST request, I cannot add application/json as the request header.

我在 Spring 中搜索了很多关于内容协商的内容,但我尝试过的都没有奏效.

I've been searching a lot about content negotiation in Spring, but nothing I've tried worked.

  • consumes="application/json" 添加到 @PostMapping 注释
  • consumes=MediaType.ALL_VALUE 添加到 @PostMapping 注释
  • 添加 MappingJackson2HttpMessageConverter bean 不起作用
  • 添加 ContentNegotiationStrategy bean 未编译无法访问 javax.servlet.ServletException";- 这是一条值得追求的路线吗?
  • adding a consumes="application/json" to the @PostMapping annotation
  • adding a consumes=MediaType.ALL_VALUE to the @PostMapping annotation
  • adding a MappingJackson2HttpMessageConverter bean didn't work
  • adding a ContentNegotiationStrategy bean didn't compile "cannot access javax.servlet.ServletException" - is it a worthwhile route to pursue this?

如何强制映射接受非内容类型"?请求并将其解码为 JSON?

What can I do to enforce the mapping to accept a non-"Content-Type" request and decode it as JSON?

推荐答案

Uggg.所以 Lebecca 的评论是关于如何使用 Spring 调整头字段的疯狂运行(我使用的是 webflux,所以链接的解决方案不适用),我找不到合适的教程,文档是位分布在您搜索时遇到的几个页面中.

Uggg. So Lebecca's comment sent on a wild run through how to adjust header field with Spring (I'm using webflux, so the linked solution was not applicable as such), and I couldn't find a proper tutorial on it and the documentation is a bit distributed amongst several pages you come across when searching.

有一些障碍,所以这是一个精简版.

There are a few roadblocks, so here's a condensed version.

干净的方式:调整header以注入Content-Type`

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

So what you can do is create a WebFilter that gets evaluated before the request actually reaches the mapping. Just define a @Component that implements it.

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

To add to the ServerWebExchange object that gets passed, you wrap it in a ServerWebExchangeDecorator.

@Component
class MyWebFilter implements WebFilter {

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

那个装饰器可以操作请求和响应;只需覆盖适当的方法.当然,请求也需要修饰 - 无法处理原始对象(如果您尝试设置其属性,它实际上会抛出异常,因为它是只读的).

That decorator can manipulate both the request and the response; just override the appropriate method. Of course, the request needs to be decorated as well - can't go around working on the original object (it actually throws an exception if you try to set its properties because it's read only).

class MyServerExchangeDecorator extends ServerWebExchangeDecorator {

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

现在您只需实现 ContentRequestDecorator 就可以了.

Now you just implement the ContentRequestDecorator and you're good to go.

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,因为它也是只读的;因此 HttpHeaders.writableHttpHeaders() 调用.

Trying to just call setContentType on the object being passed in results in an OperationNotSupportedException because, again, it is read only; hence the HttpHeaders.writableHttpHeaders() call.

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

Of course, this will adjust all headers for all incoming requests, so if you plan on doing this and don't want the nuclear option, inspect the request beforehand.

简单的方法

因此上述方式触发 Springs 将传入请求映射到 consumes="application/json" 端点.

So the way above triggers Springs mapping of the incoming request to a consumes="application/json" endpoint.

但是,如果您没有阅读我之前的描述,因为其中包含略带讽刺的意味,我应该不那么微妙.这是一个非常简单的问题的非常复杂的解决方案.

However, if you didn't read my previous description as containing a slightly sarcastic undertone, I should have been less subtle. It's a very involved solution for a very simple problem.

我最终的做法是

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

这篇关于Spring Boot PostMapping:如果不存在 Content-Type,如何强制执行 JSON 解码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆