简体   繁体   English

在 Flux.error 的情况下返回相关的 ServerResponse

[英]Return relevant ServerResponse in case of Flux.error

I have a WebFlux functional REST endpoint and I am having trouble returning custom http errors as a result of exceptions thrown in my code, for example a BadRequest on an invalid path variable.我有一个 WebFlux 功能 REST 端点,但由于我的代码中抛出异常(例如无效路径变量上的 BadRequest),我无法返回自定义 http 错误。

Consider my handler:考虑我的处理程序:

public Mono<ServerResponse> getStarships(ServerRequest request) {
    String starshipType = request.pathVariable("type");
    return ServerResponse
            .ok()
            .contentType(APPLICATION_JSON)
            .body(starshipService.getFromSpacedock(starshipType), Starship.class)
            .onErrorResume(InvalidStarshipTypeException.class, 
                           e -> ServerResponse
                                  .badRequest()
                                  .bodyValue(e.getMessage()));
}

When starshipService.getFromSpacedock(starshipType) returns Flux.just(new Starship()) , all is as expected.starshipService.getFromSpacedock(starshipType)返回Flux.just(new Starship())时,一切都如预期的那样。

When it returns Flux.error(new InvalidStarshipTypeException("invalid starship type")) , I expect the onErrorResume to kick in and return my custom BadRequest ServerResponse with my message.当它返回Flux.error(new InvalidStarshipTypeException("invalid starship type"))时,我希望 onErrorResume 启动并返回我的自定义 BadRequest ServerResponse 和我的消息。

Instead, my endpoint responds with 500 (with my custom exception wrapped in it).相反,我的端点以 500 响应(其中包含我的自定义异常)。 The onErrorResume is ignored. onErrorResume 被忽略。

How do I solve this?我该如何解决这个问题?

What I have tried:我试过的:

  • wrap the exception in a ResponseStatusException : I get my 400 but not through the custom ServerResponse route.将异常包装在ResponseStatusException中:我得到了 400 但不是通过自定义 ServerResponse 路由。 The problem with this approach is that I would have to configure Spring to show messages when handling exceptions this way, which I do not want.这种方法的问题是我必须配置 Spring 以在以这种方式处理异常时显示消息,这是我不想要的。
  • use flatMap on the Flux, but this results in a Flux<ServerResponse> instead of a Mono<ServerResponse> :在 Flux 上使用 flatMap,但这会导致Flux<ServerResponse>而不是Mono<ServerResponse>
return starshipService.getFromSpacedock(starshipType) // remember, this is a Flux<Starship>
    .flatMap(ships -> ServerResponse.ok()
                          .contentType(MediaType.APPLICATION_JSON)
                          .body(ships, StarShip.class))
    .onErrorResume(e -> ServerResponse.badRequest().bodyValue(e.getMessage()));

I had a similar issue.我有一个类似的问题。 To solve this issue, you need to first convert your cold flux into a hot flux.要解决此问题,您需要首先将冷焊剂转换为热焊剂。 Then on the hot flux call .next() , to return a Mono<Starship> .然后在热通量调用.next()上,返回Mono<Starship> On this mono, call .flatMap().switchIfEmpty().onErrorResume() .在这个单声道上,调用.flatMap().switchIfEmpty().onErrorResume() In the flatMap() concat the returned startship object with the hot flux.flatMap()中,将返回的 startship 对象与热通量连接起来。

Here's the code snippet modified to achieve what you want:这是修改后的代码片段以实现您想要的效果:

public Mono<ServerResponse> getStarships(ServerRequest request) 
{
String starshipType = request.pathVariable("type");

Flux<Starship> coldStarshipFlux = starshipService.getFromSpacedock(starshipType);

//The following step is a very important step. It converts your cold flux into a hot flux.
Flux<Startship> hotStarshipFlux = coldStarshipFlux
                                           .publish()
                                           .refCount(1, Duration.ofSeconds(2));

return hotStarshipFlux.next()
                    .flatMap( starShipObj ->
                       {
                            Flux<Starship> flux = Mono.just(starShipObj)
                                                     .concatWith(hotStarshipFlux);
                            
                            return ServerResponse.ok()
                                          .contentType(MediaType.APPLICATION_JSON)
                                          .body(flux, Starship.class);
                        }
                    )
                    .switchIfEmpty(
                        ServerResponse.notFound().build()
                    )
                    .onErrorResume( t ->
                        {
                            if(t instanceof InvalidStarshipTypeException)
                            {
                                return ServerResponse.badRequest()
                                                    .contentType(MediaType.TEXT_PLAIN)
                                                    .bodyValue(t.getMessage());
                            }
                            else
                            {
                                return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                                    .contentType(MediaType.TEXT_PLAIN)
                                                    .bodyValue(t.getMessage());
                            }
                        });
}

The code .publish().refCount(1, Duration.ofSeconds(2));代码.publish().refCount(1, Duration.ofSeconds(2)); is what makes your cold flux into a hot flux.是什么让你的冷焊剂变成热焊剂。 It is important that you do this.这样做很重要。

When using a hot flux, each new subscription shares the elements emitted in the flux stream, which are emitted after the subscription began.当使用热通量时,每个新订阅共享通量流中发出的元素,这些元素在订阅开始后发出。 So, the initial call to .next() causes the hot flux to emit the first element.因此,对.next()的初始调用导致热通量发射第一个元素。 Now, when the .concatWith() method is called again on the same hot flux, the hot flux will not re-emit the first element again, but will continue emitting the subsequent elements in its stream.现在,当在同一个热通量上再次调用.concatWith()方法时,热通量不会再次重新发射第一个元素,但会继续发射其流中的后续元素。 So all remaining elements in the stream, starting with the second will get concatenated.因此,流中所有剩余的元素,从第二个开始将被连接起来。

If you did not convert your flux into a hot flux, but ran the above code on your cold flux, then the calls to .next() and .concatWith() would each cause your cold flux to re-generate the same data anew twice, once for .next() , and once for .concatWith() .如果您没有将助焊剂转换为热助焊剂,而是在冷助焊剂上运行上述代码,那么对.next().concatWith()的调用都会导致冷助焊剂重新生成相同的数据两次,一次用于.next() ,一次用于.concatWith() Thus, you'd have a duplicate first element.因此,您将有一个重复的第一个元素。

Now, you may ask, why bother with all this hot and cold flux?现在,您可能会问,为什么要为所有这些冷热流量烦恼? Why not just do something like the following code snippet using just a cold flux?为什么不只使用焊剂来执行类似以下代码片段的操作? After all a cold will will regenerate the data anew so there won't be a need to call the concatWith() method at all.毕竟感冒会重新生成数据,所以根本不需要调用concatWith()方法。

Flux<Starship> coldStarshipFlux = starshipService.getFromSpacedock(starshipType);

return coldStarldshipFlux.next()
                         .flatMap( starShipObj -> // ignore the startShipObj
                            {
                                return ServerResponse.ok()
                                        .contentType(MediaType.APPLICATION_JSON)
                                        .body(coldStarldshipFlux, Starship.class);
                            }
                        )
                        .switchIfEmpty(
                             ... //same code as above hot flux
                        )
                        .onErrorResume( t ->
                            {
                                ... //same code as as above hot flux
                            });

The problem with the above code snippet is that all subscriptions on a cold flux re-generate data anew, per subscription.上述代码片段的问题在于,冷通量上的所有订阅都会根据订阅重新生成数据。 So, effectively the call to .next() and .concatWith() would cause the cold flux to re-generate the same data stream anew, and depending on how your startshipService is coded, may result in a second HTTP request being made.因此,有效地调用.next().concatWith()会导致冷通量重新生成相同的数据流,并且根据startshipService的编码方式,可能会导致发出第二个 HTTP 请求。 Thus, effectively, you'd be making two HTTP requests instead of one.因此,实际上,您将发出两个 HTTP 请求而不是一个。

When using a hot flux, the need to re-generate data anew (thereby potentially making a second HTTP request) is avoided.使用热通量时,可以避免重新生成数据(从而可能会发出第二个 HTTP 请求)的需要。 That's the biggest advantage of converting your cold flux into a hot flux.这是将冷焊剂转化为热焊剂的最大优势。

For details on hot and cold flux see the following web site.有关冷热焊剂的详细信息,请参见以下网站。 Towards the end, it very clearly explains the difference between a hot and cold flux, and how they behave differently:最后,它非常清楚地解释了热通量和冷通量之间的区别,以及它们的行为方式有何不同:
https://spring.io/blog/2019/03/06/flight-of-the-flux-1-assembly-vs-subscription https://spring.io/blog/2019/03/06/flight-of-the-flux-1-assembly-vs-subscription

I hope this answer helps you.我希望这个答案对你有所帮助。

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

相关问题 即使抛出异常,Flux.error也会返回200个http代码 - Flux.error return 200 http code even if exception is thrown 我可以将 Flux 作为 ServerResponse 正文的一个字段吗? - Can I have Flux as a field of ServerResponse body? 为什么要通过 Mono<ServerResponse> 来自 webflux 路由器中的处理程序函数而不是 Flux<ServerResponse> - Why pass Mono<ServerResponse> from handler function in webflux router and not Flux<ServerResponse> 合并 Flux 结果并返回 Flux - Merge Flux result and return Flux 助焊剂和 Mono 的用例 - Use case of Flux and Mono 如何返回 Mono<ServerResponse> (作为副作用?) Mono.subscribe()? - How to return a Mono<ServerResponse> (as a side effect?) of Mono.subscribe()? 为什么是通量<string>当通过 ServerResponse 返回时被折叠成单个字符串,除非每个字符串元素都以“\n”结尾?</string> - Why is a Flux<String> being collapsed into a single String when returned via ServerResponse, unless each String element is terminated with “\n”? Micronaut 不返回 Flux 返回类型的默认 HTTP 错误表示 - Micronaut doesn't return default HTTP error representation for Flux return types 在单个 object 中返回多个通量 - Return multiple Flux in a single object 如何对 Flux 中的项目进行计数,如果计数大于 X,则返回错误,否则继续流水线 - How to Count Items in a Flux, return error if count is greater than X, else continue with Pipeline
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM