Spring WebFlux:只允许一个连接接收订阅者

我正在用Spring 5 Webflux和Kotlin编写一个简单的应用程序。我正在尝试通过以下方式实现PUT端点:

PUT("/confs/{id}", {

val id = it.pathVariable("id")

ServerResponse.ok().body(service.save(it.bodyToMono(Item::class.java)), Item::class.java)

})

保存的诀窍是,我尝试从项目中读取城市名称,解析地理坐标,将其覆盖在原始项目中,然后使用Spring Data Mongo Reactive

repo保存到Mongo。

fun save(item: Mono<Item>): Mono<Item> {

val geo = item.flatMap {

val city = it.location?.city ?: "Somewhere"

geoService.resolveGeoFromCity(city)

}

val zipped = item.zipWith(geo)

.map {

it.t1.location?.geo = it.t2

it.t1

}

return repo.saveAll(zipped)

.toMono()

}

解析地理坐标的代码在这里:

@Service

class GeoService() {

val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/")

fun resolveGeoFromCity(city: String): Mono<Geo> {

return client.get()

.uri("json?address=$city&key=$API_KEY&language=en")

.exchange()

.flatMap { it.bodyToMono(String::class.java) }

.map { parse(it) }

}

private fun parse(response: String): Geo {

val locationMap = JsonPath.read<Map<String, Double>>(response, "$.results[0].geometry.location")

return Geo(locationMap["lat"] ?: 0.0, locationMap["lng"] ?: 0.0)

}

}

问题是,如果发出PUT请求,我将遵循stacktrace。我试图用val geo = Mono.just(Geo(0.0,

0.0))(不使用WebClient)对Mono进行存根处理,然后工作正常。

如何在不牺牲功能的情况下进行修复?)

    2018-01-01 01:41:00.595 ERROR 15120 --- [ctor-http-nio-4] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [PUT http://localhost:8097/confs/5a49675c910d123b1057207a]

java.lang.IllegalStateException: Only one connection receive subscriber allowed.

at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:276) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at reactor.ipc.netty.channel.FluxReceive.subscribe(FluxReceive.java:124) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.ipc.netty.ByteBufFlux.subscribe(ByteBufFlux.java:242) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at reactor.core.publisher.FluxPeek.subscribe(FluxPeek.java:83) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.ipc.netty.ByteBufFlux.subscribe(ByteBufFlux.java:242) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:97) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxDoFinallyFuseable.subscribe(FluxDoFinallyFuseable.java:48) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxMapFuseable.subscribe(FluxMapFuseable.java:63) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.FluxOnAssembly.subscribe(FluxOnAssembly.java:252) ~

[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.Mono.subscribe(Mono.java:3008) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:167) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:71) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.core.publisher.MonoOnAssembly.subscribe(MonoOnAssembly.java:76) ~[reactor-core-3.1.2.RELEASE.jar:3.1.2.RELEASE]

at reactor.ipc.netty.channel.ChannelOperations.applyHandler(ChannelOperations.java:383) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at reactor.ipc.netty.http.server.HttpServerOperations.onHandlerStart(HttpServerOperations.java:359) ~[reactor-netty-0.7.2.RELEASE.jar:0.7.2.RELEASE]

at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163) ~[netty-common-4.1.17.Final.jar:4.1.17.Final]

at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) ~[netty-common-4.1.17.Final.jar:4.1.17.Final]

at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403) ~[netty-common-4.1.17.Final.jar:4.1.17.Final]

at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463) ~[netty-transport-4.1.17.Final.jar:4.1.17.Final]

at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858) ~[netty-common-4.1.17.Final.jar:4.1.17.Final]

at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_112]

Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:

Assembly trace from producer [reactor.core.publisher.FluxMap] :

reactor.core.publisher.Flux.map(Flux.java:4855)

reactor.ipc.netty.ByteBufFlux.fromInbound(ByteBufFlux.java:68)

reactor.ipc.netty.NettyInbound.receive(NettyInbound.java:90)

org.springframework.http.server.reactive.ReactorServerHttpRequest.getBody(ReactorServerHttpRequest.java:148)

org.springframework.http.codec.DecoderHttpMessageReader.readMono(DecoderHttpMessageReader.java:93)

org.springframework.http.codec.DecoderHttpMessageReader.readMono(DecoderHttpMessageReader.java:123)

org.springframework.web.reactive.function.BodyExtractors.lambda$null$0(BodyExtractors.java:101)

java.util.Optional.map(Optional.java:215)

org.springframework.web.reactive.function.BodyExtractors.readWithMessageReaders(BodyExtractors.java:256)

org.springframework.web.reactive.function.BodyExtractors.lambda$toMono$2(BodyExtractors.java:96)

org.springframework.web.reactive.function.server.DefaultServerRequest.body(DefaultServerRequest.java:126)

org.springframework.web.reactive.function.server.DefaultServerRequest.body(DefaultServerRequest.java:120)

org.springframework.web.reactive.function.server.DefaultServerRequest.bodyToMono(DefaultServerRequest.java:145)

com.example.confs.web.ConferenceRouter$routes$1$2.invoke(ConferenceRouter.kt:31)

com.example.confs.web.ConferenceRouter$routes$1$2.invoke(ConferenceRouter.kt:16)

org.springframework.web.reactive.function.server.RouterFunctionDsl$PUT$1.handle(RouterFunctionDsl.kt:200)

org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:61)

org.springframework.web.reactive.DispatcherHandler.invokeHandler(DispatcherHandler.java:168)

org.springframework.web.reactive.DispatcherHandler.lambda$handle$1(DispatcherHandler.java:160)

reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)

reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)

reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:271)

reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:803)

reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:115)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)

reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:1649)

reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1463)

reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1337)

reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)

reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)

reactor.core.publisher.Mono.subscribe(Mono.java:3008)

reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)

reactor.core.publisher.Operators.complete(Operators.java:125)

reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:45)

reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)

reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)

reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)

reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)

reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:59)

reactor.core.publisher.Mono.subscribe(Mono.java:3008)

reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:418)

reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:210)

reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:128)

reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:61)

reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:121)

reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40)

reactor.core.publisher.MonoSwitchIfEmpty.subscribe(MonoSwitchIfEmpty.java:44)

reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)

reactor.core.publisher.MonoFlatMap.subscribe(MonoFlatMap.java:60)

reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)

reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61)

reactor.core.publisher.MonoPeekFuseable.subscribe(MonoPeekFuseable.java:74)

reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)

reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)

reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)

reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)

reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)

reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)

reactor.core.publisher.Mono.subscribe(Mono.java:3008)

reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:167)

reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)

reactor.core.publisher.MonoOnErrorResume.subscribe(MonoOnErrorResume.java:44)

reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61)

reactor.ipc.netty.channel.ChannelOperations.applyHandler(ChannelOperations.java:383)

reactor.ipc.netty.http.server.HttpServerOperations.onHandlerStart(HttpServerOperations.java:359)

io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163)

io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)

io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)

io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)

Error has been observed by the following operator(s):

|_ Flux.map(ByteBufFlux.java:68)

|_ Flux.doOnNext(ByteBufFlux.java:230)

|_ Flux.map(ReactorServerHttpRequest.java:148)

|_ Flux.flatMap(AbstractJackson2Decoder.java:95)

|_ Flux.doFinally(AbstractJackson2Decoder.java:95)

|_ Flux.map(AbstractJackson2Decoder.java:117)

|_ Flux.singleOrEmpty(AbstractJackson2Decoder.java:87)

|_ Operators.error(FluxReceive.java:276)

|_ Mono.onErrorMap(DefaultServerRequest.java:146)

|_ Mono.map(ConferenceService.kt:27)

|_ Mono.map(ConferenceService.kt:32)

|_ Mono.zipWith(ConferenceService.kt:47)

|_ Mono.map(ConferenceService.kt:48)

|_ Flux.flatMap(SimpleReactiveMongoRepository.java:318)

|_ MonoExtensionsKt.toMono(ConferenceService.kt:55)

|_ Mono.map(ConferenceService.kt:56)

|_ Flux.map(AbstractJackson2Encoder.java:99)

|_ Mono.flatMap(DispatcherHandler.java:177)

|_ Mono.onErrorResume(DispatcherHandler.java:177)

|_ Mono.flatMap(DispatcherHandler.java:161)

|_ Mono.defer(DefaultWebFilterChain.java:71)

|_ Mono.doOnSuccess(MetricsWebFilter.java:59)

|_ Mono.doOnError(MetricsWebFilter.java:60)

|_ Mono.compose(MetricsWebFilter.java:54)

|_ Mono.defer(DefaultWebFilterChain.java:71)

|_ Mono.defer(DefaultWebFilterChain.java:71)

回答:

WebClient.exchange() 结果流是单播的

实际上,这里的问题是WebClient每个连接仅允许一个订户。如果您尝试两次订阅相同的 连接-

您将获得java.lang.IllegalStateException: Only one connection receive subscriber

allowed.

尽管我看不到您尝试在哪里重用同一连接两次的事实,但我相信您可以通过使用下一个运算符组合来解决该问题:

class GeoService() {

val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/")

fun resolveGeoFromCity(city: String): Mono<Geo> {

return client.get()

.uri("json?address=$city&key=$API_KEY&language=en")

.exchange()

.flatMap { it.bodyToMono(String::class.java) }

.map { parse(it) }

.share();

}

...

}

在该示例中,流被配置为多播(共享)原始源,只要有至少一个Subscriber订阅源即可。如果您需要所有订阅者都收到相同的日期,可以.share.cache运营商代替。

此外,还有一种替代上述技术的方法。您可以将上述运算符替换为处理器,并获得相同的共享可能性:

class GeoService() {

val client = WebClient.create("https://maps.googleapis.com/maps/api/geocode/")

fun resolveGeoFromCity(city: String): Mono<Geo> {

return client.get()

.uri("json?address=$city&key=$API_KEY&language=en")

.exchange()

.flatMap { it.bodyToMono(String::class.java) }

.map { parse(it) }

.subscribeWith(DirectProcessor.create());

}

...

}

在这种情况下,您正好在调用之后立即订阅和运行对源数据的使用subscribeWith,因此,在这种情况下,可能会丢失部分数据,依此类推。

为什么Mono.just(..)一切正常?

首先.just是冷操作员,它允许尽可能多的用户在任何时间点接收相同的数据。这就是为什么当您尝试两次从连接中使用相同的数据块时,没有得到任何异常的原因。

以上是 Spring WebFlux:只允许一个连接接收订阅者 的全部内容, 来源链接: utcz.com/qa/431018.html

回到顶部