Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for null literal in Jackson2JsonDecoder [SPR-17510] #22042

Closed
spring-projects-issues opened this issue Nov 17, 2018 · 1 comment
Closed

Support for null literal in Jackson2JsonDecoder [SPR-17510] #22042

spring-projects-issues opened this issue Nov 17, 2018 · 1 comment
Assignees
Labels
in: web type: enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Nov 17, 2018

Boris Morris opened SPR-17510 and commented

When consuming the Nest Developer API using WebClient I encounter an error on keep-alive events.

I am unsure whether this is because of the Nest API not complying with the SSE spec or because of ServerSentEventHttpMessageReader not being tolerant enough.


The Nest Developer API in SSE send two types of messages:

  1. data frames
  2. keep alive frames

A data frame is structured thus:

event: put
data: <some JSON content>
<linebreak>

A keep alive frame is structured thus:

event: keep-alive
data: null
<linebreak>

The thing to note is the literal null in the data field.

When connecting to the endpoint with WebClient I see the following error:

2018-11-17 19:21:48.010 ERROR 75491 --- [ctor-http-nio-4] reactor.Flux.MonoFlatMapMany.1           : onError(java.lang.NullPointerException: The mapper returned a null value.)
2018-11-17 19:21:48.013 ERROR 75491 --- [ctor-http-nio-4] reactor.Flux.MonoFlatMapMany.1           : java.lang.NullPointerException: The mapper returned a null value.
	at java.base/java.util.Objects.requireNonNull(Objects.java:246) ~[na:na]
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:664) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:540) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap$FlatMapInner.onSubscribe(FluxFlatMap.java:924) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:139) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.Flux.subscribe(Flux.java:7734) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:389) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMapSignal$FluxMapSignalSubscriber.onNext(FluxMapSignal.java:147) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxJust$WeakScalarSubscription.request(FluxJust.java:99) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMapSignal$FluxMapSignalSubscriber.request(FluxMapSignal.java:225) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:335) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMapSignal$FluxMapSignalSubscriber.onSubscribe(FluxMapSignal.java:115) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxJust.subscribe(FluxJust.java:70) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMapSignal.subscribe(FluxMapSignal.java:69) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:97) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMap.subscribe(FluxMap.java:62) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.MonoSingle.subscribe(MonoSingle.java:58) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.Mono.subscribe(Mono.java:3590) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:442) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:244) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.emit(FluxBufferPredicate.java:295) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNextNewBuffer(FluxBufferPredicate.java:257) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.tryOnNext(FluxBufferPredicate.java:211) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNext(FluxBufferPredicate.java:184) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:287) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onNext(FluxContextStart.java:103) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onNext(FluxMap.java:213) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.emit(FluxBufferPredicate.java:295) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNextNewBuffer(FluxBufferPredicate.java:257) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.tryOnNext(FluxBufferPredicate.java:211) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNext(FluxBufferPredicate.java:184) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:395) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:638) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:242) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.2.2.RELEASE.jar:3.2.2.RELEASE]
	at reactor.netty.channel.FluxReceive.drainReceiver(FluxReceive.java:211) ~[reactor-netty-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	at reactor.netty.channel.FluxReceive.onInboundNext(FluxReceive.java:327) ~[reactor-netty-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	at reactor.netty.channel.ChannelOperations.onInboundNext(ChannelOperations.java:310) ~[reactor-netty-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:538) ~[reactor-netty-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:141) ~[reactor-netty-0.8.2.RELEASE.jar:0.8.2.RELEASE]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1429) ~[netty-handler-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1199) ~[netty-handler-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1243) ~[netty-handler-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) ~[netty-codec-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:628) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:528) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:482) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:442) ~[netty-transport-4.1.29.Final.jar:4.1.29.Final]
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) ~[netty-common-4.1.29.Final.jar:4.1.29.Final]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

This is caused by the Decoder returning null when trying to parse the "null" value for data.

I have attached a custom HttpMessageReader (in Kotlin) that handles this situation by filtering these events. Obviously that does not work for the case where the user wants "wrapped" events,.


Affects: 5.1.2

Attachments:

Issue Links:

  • #22043 ServerSentEventHttpMessageReader leaves a leading space on field decoding
@spring-projects-issues spring-projects-issues added type: bug status: waiting-for-triage in: web labels Jan 11, 2019
@spring-projects-issues spring-projects-issues removed the type: bug label Jan 11, 2019
@rstoyanchev rstoyanchev added this to the 5.1.5 milestone Jan 18, 2019
@rstoyanchev rstoyanchev added type: enhancement and removed status: waiting-for-triage labels Jan 18, 2019
@rstoyanchev rstoyanchev self-assigned this Jan 18, 2019
@rstoyanchev rstoyanchev changed the title ServerSentEventHttpMessageReader fails to process keep-alive events [SPR-17510] Support for null literal in Jackson2JsonDecoder [SPR-17510] Jan 18, 2019
@rstoyanchev
Copy link
Contributor

@rstoyanchev rstoyanchev commented Jan 18, 2019

The fix is in the Jackson decoder because null values cannot flow through Flux.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web type: enhancement
Projects
None yet
Development

No branches or pull requests

2 participants