关于外部onTerminate事件上的Spring WebClient

时间:2018-07-25 11:15:07

标签: spring-boot spring-webflux project-reactor

我正在运行一个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连接?

1 个答案:

答案 0 :(得分:0)

我不能确切地说出该异常消息,但是我知道Tomcat在8.5.x世代中对此进行了改进。您正在使用哪个版本?如果您可以使用最小的应用程序提供一致的方式来重现此问题,则可以在Spring框架上的jira.spring.io或Tomcat本身中创建一个新问题,如果您设法在没有Spring的情况下重现它(尽管这应该很困难复制)。

现在要发布output.write(outBuffer, 0, numWritten); 实例-可以合并DataBuffer实例,具体取决于实现。这里DataBuffer使用的是Netty,它是缓冲池。因此,当它们不再使用时,需要释放它们。

看看您的实现,我认为那些未释放的缓冲区来自此:

  • WebClient正在从远程端点获取数据并创建WebClient实例
  • 一路走来,各种Reactor运算符正在缓冲使用内部队列的那些运算符(取决于预取和使用的运算符,排队缓冲区的数量可能会有所不同)
  • 当订户失败或取消时,位于内部队列中的那些缓冲区不会按应有的方式释放。

当前,在这些错误情况下,Reactor没有提供连接这些对象的挂钩点。但这是Reactor核心3.2.0中新增的一项全新功能。 Spring Framework将在内部使用SPR-17025来利用这一点。请关注此问题-在测试修补程序时,您的用例可能会很方便。