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

Reactive AbstractErrorWebExceptionHandler#htmlEscape() may be blocking #26712

Closed
sguillope opened this issue Mar 22, 2021 · 4 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@sguillope
Copy link

Spring Boot 2.4.4 using WebFlux
Spring Framework 5.3.5
Spring Cloud Gateway 3.0.2


Blocking call detected by Blockhound when returning an error response to a browser. In particular, saw this happening when the browser sends a GET /favicon.ico request, resulting in a 404 whitelabel error page being rendered.

As a workaround for now, I've disabled the whitelabel error page by setting server.error.whitelabel.enabled to false.

The call to AbstractErrorWebExceptionHandler#htmlEscape() can trigger the initialisation of Spring Framework's static class HtmlUtils. One of its static field - characterEntityReferences - is of type HtmlCharacterEntityReferences and its constructor loads the file HtmlCharacterEntityReferences.properties in a blocking way.

Full stacktrace:

reactor.blockhound.BlockingOperationError: Blocking call! java.io.RandomAccessFile#readBytes
	at java.base/java.io.RandomAccessFile.readBytes(RandomAccessFile.java)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Assembly trace from producer [reactor.core.publisher.MonoFlatMap] :
	reactor.core.publisher.Mono.flatMap
	org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.handle(AbstractErrorWebExceptionHandler.java:326)
Error has been observed at the following site(s):
	|_       Mono.flatMap ⇢ at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.handle(AbstractErrorWebExceptionHandler.java:326)
	|_      Mono.doOnNext ⇢ at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.handle(AbstractErrorWebExceptionHandler.java:327)
	|_       Mono.flatMap ⇢ at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.handle(AbstractErrorWebExceptionHandler.java:328)
	|_                    ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler.lambda$handle$0(ExceptionHandlingWebHandler.java:77)
	|_ Mono.onErrorResume ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler.handle(ExceptionHandlingWebHandler.java:77)
	|_         Mono.error ⇢ at org.springframework.web.server.handler.ResponseStatusExceptionHandler.handle(ResponseStatusExceptionHandler.java:67)
	|_                    ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler.lambda$handle$0(ExceptionHandlingWebHandler.java:77)
	|_ Mono.onErrorResume ⇢ at org.springframework.web.server.handler.ExceptionHandlingWebHandler.handle(ExceptionHandlingWebHandler.java:77)
	|_   Mono.doOnSuccess ⇢ at org.springframework.web.server.adapter.HttpWebHandlerAdapter.handle(HttpWebHandlerAdapter.java:249)
Stack trace:
		at java.base/java.io.RandomAccessFile.readBytes(RandomAccessFile.java)
		at java.base/java.io.RandomAccessFile.read(RandomAccessFile.java:406)
		at java.base/java.io.RandomAccessFile.readFully(RandomAccessFile.java:470)
		at java.base/java.util.zip.ZipFile$Source.readFullyAt(ZipFile.java:1318)
		at java.base/java.util.zip.ZipFile$ZipFileInputStream.initDataOffset(ZipFile.java:1003)
		at java.base/java.util.zip.ZipFile$ZipFileInputStream.read(ZipFile.java:1018)
		at java.base/java.util.zip.ZipFile$ZipFileInflaterInputStream.fill(ZipFile.java:468)
		at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:159)
		at java.base/java.io.FilterInputStream.read(FilterInputStream.java:133)
		at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
		at java.base/java.util.Properties$LineReader.readLine(Properties.java:502)
		at java.base/java.util.Properties.load0(Properties.java:418)
		at java.base/java.util.Properties.load(Properties.java:407)
		at org.springframework.web.util.HtmlCharacterEntityReferences.<init>(HtmlCharacterEntityReferences.java:75)
		at org.springframework.web.util.HtmlUtils.<clinit>(HtmlUtils.java:46)
		at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.htmlEscape(AbstractErrorWebExceptionHandler.java:295)
		at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.renderDefaultErrorView(AbstractErrorWebExceptionHandler.java:282)
		at org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler.renderErrorView(DefaultErrorWebExceptionHandler.java:141)
		at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.lambda$handle$0(AbstractErrorWebExceptionHandler.java:326)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
		at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:73)
		at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
		at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onNext(FluxConcatArray.java:177)
		at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2193)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2067)
		at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4099)
		at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onComplete(FluxConcatArray.java:208)
		at reactor.core.publisher.FluxConcatArray.subscribe(FluxConcatArray.java:80)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4099)
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106)
		at reactor.core.publisher.Operators.error(Operators.java:197)
		at reactor.core.publisher.MonoError.subscribe(MonoError.java:52)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4099)
		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
		at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onError(MonoPeekTerminal.java:258)
		at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onError(MonoPeekTerminal.java:258)
		at org.springframework.cloud.sleuth.instrument.web.TraceWebFilter$MonoWebFilterTrace$WebFilterTraceSubscriber.onError(TraceWebFilter.java:255)
		at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onError(FluxDoFinally.java:136)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:172)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:172)
		at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onError(Operators.java:2062)
		at reactor.core.publisher.Operators.error(Operators.java:197)
		at reactor.core.publisher.MonoError.subscribe(MonoError.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4099)
		at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:81)
		at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102)
		at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:366)
		at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:218)
		at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:164)
		at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at org.springframework.cloud.sleuth.instrument.web.TraceWebFilter$MonoWebFilterTrace.subscribe(TraceWebFilter.java:170)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
		at reactor.core.publisher.Mono.subscribe(Mono.java:4099)
		at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:173)
		at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
		at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
		at reactor.netty.http.server.HttpServer$HttpServerHandle.onStateChange(HttpServer.java:915)
		at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:654)
		at reactor.netty.transport.ServerTransport$ChildObserver.onStateChange(ServerTransport.java:478)
		at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:526)
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:209)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
		at java.base/java.lang.Thread.run(Thread.java:834)
@wilkinsona
Copy link
Member

wilkinsona commented Mar 22, 2021

Thanks for the report. This is somewhat similar to #26631. Given that this problem also only happens once, it may also be appropriate for Spring Framework to provide a Blockhound rule to suppress the warning. Does that sound reasonable to you, @rstoyanchev?

@rstoyanchev
Copy link
Contributor

Yes, that seems reasonable.

@snicoll snicoll transferred this issue from spring-projects/spring-boot Mar 22, 2021
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 22, 2021
@rstoyanchev rstoyanchev self-assigned this Mar 22, 2021
@rstoyanchev rstoyanchev added in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 22, 2021
@rstoyanchev rstoyanchev added this to the 5.3.6 milestone Mar 22, 2021
@rstoyanchev
Copy link
Contributor

@sguillope this should be now fixed. If you can please give Spring Framework 5.3.6 snapshot a try.

@sguillope
Copy link
Author

Thanks @rstoyanchev, confirmed that the Blockhound rule is working as expected. NoBlockingOperationError.
Cheers

Zoran0104 pushed a commit to Zoran0104/spring-framework that referenced this issue Aug 20, 2021
lxbzmy pushed a commit to lxbzmy/spring-framework that referenced this issue Mar 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants