简体   繁体   English

如何使反应式@WebFilter 与@RequestBody 一起正常工作?

[英]How to make reactive @WebFilter work properly with @RequestBody?

I am trying to make a reactive @WebFilter that executes stuff before and after the actual server exchange (ie the controller code handling the request):我正在尝试制作一个反应式@WebFilter ,它在实际服务器交换之前之后执行东西(即处理请求的 controller 代码):

public static class Foobar implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.empty()
                .doOnEach(s -> /* BEFORE */))
                .then(chain.filter(exchange) /* CONTROLLER */)
                .and(Mono.empty().doOnEach(s -> /* AFTER */))
                .and(Mono.empty().doFinally(s -> /* FINALLY */));
    }
}

Everything works as expected for simple GET requests that return a Mono :对于返回Mono的简单GET请求,一切都按预期工作:

@RestController
@RequestMapping
public static class Foo {

    @GetMapping
    @PostMapping(value = "foo")
    public Mono<String> foo(ServerWebExchange exchange) {
        return Mono.just("FOOBAR").map(e -> "OK");
    }
}

But something really unexpected happens when the controller receives a parameter annotated as @RequestBody .但是,当 controller 收到一个注释为@RequestBody的参数时,会发生一些真正意想不到的事情。 Say, for example a POST request that takes a Mono<String> from the client:比如说,一个从客户端获取Mono<String>POST请求:

@RestController
@RequestMapping
public static class Bar {

    @PostMapping(value = "bar")
    public Mono<String> bar(ServerWebExchange exchange, @RequestBody Mono<String> text) {
        return text.map(s -> "OK");
    }
}

In this case, all steps in my filter are executed before the controller gets to complete the request .在这种情况下,我的过滤器中的所有步骤都在 controller 完成请求之前执行。 This means that the web exchange is committed independently of the filter and therefore I cannot do anything right after the response is sent back to the client.这意味着 web 交换独立于过滤器提交,因此在将响应发送回客户端后我无法立即执行任何操作。

So I'm wondering:所以我想知道:

  • Is this some kind of Spring bug?这是某种 Spring 错误吗?
  • Am I doing something wrong?难道我做错了什么?
  • Or is this simply the expected behavior?或者这只是预期的行为?

I've created a small Gist containing a test case that reproduces the problem:我创建了一个包含重现问题的测试用例的小要点:


Edit after Brian's comment:在布赖恩的评论后编辑:

I still think this might be a bug because Mono.then doesn't seem to have any effect at all:我仍然认为这可能是一个错误,因为Mono.then似乎根本没有任何效果:

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            return chain.filter(exchange)
                .doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
                        exchange.getResponse().isCommitted()))
                .then().doOnEach(s -> logger.info("then doOnEach response committed:" +
                        exchange.getResponse().isCommitted()))
                .doFinally(s -> logger.info("doFinally response committed:" +
                        exchange.getResponse().isCommitted()));
        }

Additionally, if I put stuff in doOnEach is not executed either:另外,如果我把东西放在doOnEach中也不会执行:

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            return chain.filter(exchange)
                    .doOnSubscribe(s -> logger.info("FILTER-BEFORE-CHAIN/commited=" +
                        response.isCommitted()))
                    .doOnEach(s -> logger.info("FILTER-AFTER-CHAIN/commited=" +
                        response.isCommitted()))
                    .doFinally(s -> logger.info("FILTER-FINALLY/commited=" +
                        response.isCommitted()));
        }

I don't think this is a bug in Spring (nor in Reactor in this case), but rather a wrong choice of operators to achieve what you're trying to do.我认为这不是 Spring 中的错误(在这种情况下也不是 Reactor 中的错误),而是错误地选择了运算符来实现您想要做的事情。

  • Mono.doOnEach is executed for each signal (next element, completion, error, cancel); Mono.doOnEach对每个信号执行(下一个元素,完成,错误,取消); this will be executed several times per request in our case.在我们的例子中,每个请求都会执行几次。
  • Mono.and joins the termination signals - so it waits for both Mono to be done and then completes. Mono.and加入终止信号 - 因此它等待Mono完成然后完成。 But both Monos are not executed sequentially, they're subscribed at the same time.但是两个 Mono 都不是按顺序执行的,它们是同时订阅的。 Mono.just completes right away, independently of what happens with the filter chain. Mono.just完成,与过滤器链发生的情况无关。

In your case, you don't need to have something more complex than adding one operator when the processing starts ( doOnSubscribe happens when the mapping has been done and we're starting to execute the handler) and another one when we're done ( doFinally happens when it's done, with completion, error or cancellation).在您的情况下,您不需要比在处理开始时添加一个运算符( doOnSubscribe在映射完成并且我们开始执行处理程序时发生)和我们完成时添加另一个更复杂的操作( doFinally在完成时发生,包括完成、错误或取消)。

    @Component
    public class MyFilter implements WebFilter {

        Logger logger = LoggerFactory.getLogger(getClass());

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            return chain.filter(exchange)
                .doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
                        exchange.getResponse().isCommitted()))
                .doFinally(s -> logger.info("doFinally response committed:" + 
                        exchange.getResponse().isCommitted()));
        }
    }

Note that this approach works as long as you're not performing blocking operations in the DoOnXYZ methods, as they're side effects methods.请注意,只要您不在DoOnXYZ方法中执行阻塞操作,这种方法就可以工作,因为它们是副作用方法。 Doing so will create significant performance problems in your application (and reactor might even reject that operation with an exception).这样做会在您的应用程序中产生严重的性能问题(并且反应器甚至可能会拒绝该操作并出现异常)。 So logging, adding an element to a map are fine - but publishing something to an event queue, for example, is not.因此,记录、向 map 添加元素是可以的——但例如,将某些内容发布到事件队列就不行。 In this case, you should use instead chain operations with Mono.then() .在这种情况下,您应该使用Mono.then()代替链操作。

Edit编辑

I don't think there is a bug with Mono.then() - doOnEach only works with signals sent downstream (next, error, complete).我认为Mono.then()没有错误 - doOnEach仅适用于向下游发送的信号(下一个,错误,完成)。 In this case, you should only get the complete signal.在这种情况下,您应该只获得完整的信号。 If you'd like to get the context in all cases, you can take a look at Mono.deferWithContext .如果您想在所有情况下获取上下文,可以查看Mono.deferWithContext

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM