我正在运行一个Spring-boot v2.0.3 tomcat嵌入式Web服务器8.5.31,以提供Spring Webflux REST服务。 这些REST服务之一调用了另一个外部REST Web服务。
public Mono<ServerResponse> select(ServerRequest request) {
return request.principal().cast(Authentication.class)
.flatMap(principal ->
client.get().uri(f -> buildUri(request, principal, request.queryParams(), f))
.exchange())
.flatMap((ClientResponse mapper) ->
ServerResponse.status(mapper.statusCode())
.headers(c -> mapper.headers().asHttpHeaders().forEach(c::put))
.body(mapper.bodyToFlux(DataBuffer.class)
.delayElements(Duration.ofSeconds(10))
.doOnCancel(() -> log.error("Cancelled client"))
.doOnTerminate(() -> log.error("Terminated client")), DataBuffer.class))
.doOnTerminate(() -> log.error("Termination called"));
}
如果浏览器调用了我的REST服务,并且不久后取消了连接,我可以看到外部的“ Termination named”事件,并且客户端也被终止了。但是客户端终止似乎会在tomcat中触发错误:
2018-07-25 12:50:42.860 DEBUG 12084 --- [ elastic-3] org.example.search.security.UserManager : Authorizing org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@809aec11: Principal: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Credentials: [PROTECTED]; Authenticated: false; Details: null; Not granted any authorities
2018-07-25 12:50:42.864 DEBUG 12084 --- [ elastic-3] org.example.search.security.UserManager : Successfully authorized: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@c03925ec: Principal: org.springframework.security.core.userdetails.User@809aec0e: Username: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN
2018-07-25 12:50:45.470 ERROR 12084 --- [ctor-http-nio-4] c.d.s.s.h.SolrSelectRequestHandler : Termination called
2018-07-25 12:51:15.562 ERROR 12084 --- [ parallel-3] c.d.s.s.h.SolrSelectRequestHandler : Terminated client
2018-07-25 12:51:15.625 ERROR 12084 --- [nio-8443-exec-2] o.s.w.s.adapter.HttpWebHandlerAdapter : Unhandled failure: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen, response already set (status=200)
2018-07-25 12:51:15.628 WARN 12084 --- [nio-8443-exec-2] o.s.h.s.r.ServletHttpHandlerAdapter : Handling completed with error: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen
2018-07-25 12:51:15.652 ERROR 12084 --- [nio-8443-exec-2] o.a.catalina.connector.CoyoteAdapter : Exception while processing an asynchronous request
java.lang.IllegalStateException: Calling [asyncError()] is not valid for a request with Async state [DISPATCHING]
at org.apache.coyote.AsyncStateMachine.asyncError(AsyncStateMachine.java:424)
at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:470)
at org.apache.coyote.Request.action(Request.java:431)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:388)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:176)
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:232)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
对不起,德语错误消息表示“客户端堕胎连接”。
我对这个错误消息本身并没有真正的问题,只是,似乎没有清除我在spring的Webclient中的缓冲区(我没有在本地复制日志,所以它有不同的时间戳):
2018-07-23 08:44:36.892 ERROR 22707 — [reactor-http-nio-5] io.netty.util.ResourceLeakDetector : LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331) io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
这里的问题是:当取消对我的REST服务的请求时,如何干净地终止WebClient连接?
答案 0 :(得分:0)
我不能确切地说出该异常消息,但是我知道Tomcat在8.5.x世代中对此进行了改进。您正在使用哪个版本?如果您可以使用最小的应用程序提供一致的方式来重现此问题,则可以在Spring框架上的jira.spring.io或Tomcat本身中创建一个新问题,如果您设法在没有Spring的情况下重现它(尽管这应该很困难复制)。
现在要发布output.write(outBuffer, 0, numWritten);
实例-可以合并DataBuffer
实例,具体取决于实现。这里DataBuffer
使用的是Netty,它是缓冲池。因此,当它们不再使用时,需要释放它们。
看看您的实现,我认为那些未释放的缓冲区来自此:
WebClient
正在从远程端点获取数据并创建WebClient
实例当前,在这些错误情况下,Reactor没有提供连接这些对象的挂钩点。但这是Reactor核心3.2.0中新增的一项全新功能。 Spring Framework将在内部使用SPR-17025来利用这一点。请关注此问题-在测试修补程序时,您的用例可能会很方便。