Environment
- Spring Boot 3.3.4
- Spring Web 6.1.1
Old surroundings where it operated
- Spring Boot 2.7.18
- Spring Web 5.3.31
Context
We have migrated from a Spring Boot 2.7.18 environment to Spring Boot 3.3.4 and we have detected that when using a WebClient with a JettyClientHttpConnector with its respective HttpClient, in case the call comes with a www-authenticate header, it gets stuck making calls infinitely and we never get it to respond correctly.
All this worked perfectly in the Spring Boot 2 version, where upon receiving the call, it responded without any problem.
We have tried to define the connector in the most basic way possible, but it always breaks, in case of receiving that header.
Examples and Code
We have done the test, simply by telling WebClient
that the connector we are going to use is JettyClientHttpConnector
.
WebClient client =
WebClient.builder()
.baseUrl("your-url-with-www-authenticate-header-in-response")
.clientConnector(new JettyClientHttpConnector())
.build();
If you try to make a call to any endpoint, which returns the www-authenticate header, you can see that it gets stuck and after passing the timeout, it throws this error:
onFillableFail SslConnection@10ac4a81::SocketChannelEndPoint@4d09e077[{l=null,r=null,CLOSED,fill=-,flush=-,to=7/30000}{io=1/1,kio=-1,kro=-1}]->[SslConnection@10ac4a81{NOT_HANDSHAKING,eio=-1/-1,di=-1,fill=INTERESTED,flush=IDLE}~>{l=null,r=null,CLOSED,fill=FI,flush=-,to=30027/30000}=>HttpConnectionOverHTTP@46f46e11(l:null <-> r:null,closed=true)=>HttpChannelOverHTTP@11d2c7ca(exchange=null)[send=HttpSenderOverHTTP@4f015fae(req=QUEUED,failure=null)[HttpGenerator@3d8bec8d{s=START}],recv=HttpReceiverOverHTTP@69f1010a(ex=null,rsp=IDLE,failure=null)[HttpParser{s=START,0 of -1}]]]
java.nio.channels.ClosedChannelException: null
at org.eclipse.jetty.io.FillInterest.onClose(FillInterest.java:147)
at org.eclipse.jetty.io.AbstractEndPoint.onClose(AbstractEndPoint.java:336)
at org.eclipse.jetty.io.SelectableChannelEndPoint.onClose(SelectableChannelEndPoint.java:165)
at org.eclipse.jetty.io.AbstractEndPoint.onClose(AbstractEndPoint.java:326)
at org.eclipse.jetty.io.AbstractEndPoint.doOnClose(AbstractEndPoint.java:253)
at org.eclipse.jetty.io.AbstractEndPoint.close(AbstractEndPoint.java:217)
at org.eclipse.jetty.io.AbstractEndPoint.close(AbstractEndPoint.java:200)
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.disconnect(SslConnection.java:1399)
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.doShutdownOutput(SslConnection.java:1381)
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.doClose(SslConnection.java:1449)
at org.eclipse.jetty.io.AbstractEndPoint.doOnClose(AbstractEndPoint.java:248)
at org.eclipse.jetty.io.AbstractEndPoint.close(AbstractEndPoint.java:217)
at org.eclipse.jetty.io.AbstractEndPoint.close(AbstractEndPoint.java:200)
at org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP.close(HttpConnectionOverHTTP.java:298)
at org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP.onIdleExpired(HttpConnectionOverHTTP.java:237)
at org.eclipse.jetty.io.ssl.SslConnection.onIdleExpired(SslConnection.java:371)
at org.eclipse.jetty.io.AbstractEndPoint.onIdleExpired(AbstractEndPoint.java:389)
at org.eclipse.jetty.io.IdleTimeout.checkIdleTimeout(IdleTimeout.java:172)
at org.eclipse.jetty.io.IdleTimeout.idleCheck(IdleTimeout.java:113)
Also, it seems to retry the call until finally the error appears, but the call gets hung up and never resolves.
[DEBUG] [ o.e.j.u.t.ReservedThreadExecutor: 348 ] waiting for task
[DEBUG] [ com.zaxxer.hikari.pool.HikariPool: 405 ] HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)
[DEBUG] [ com.zaxxer.hikari.pool.HikariPool: 510 ] HikariPool-1 - Fill pool skipped, pool has sufficient level or currently being filled.
[DEBUG] [ o.e.j.util.thread.QueuedThreadPool: 1015 ] Evict check, period=60000ms QueuedThreadPool[HttpClient@7218bb00]@42681628{STARTED,8<=12<=200,i=3,r=-1,t=-67ms,q=0}[ReservedThreadExecutor@7457d199{capacity=16,threads=ThreadIdPool@397ce9d8{capacity=16}}]
[DEBUG] [ o.e.j.util.thread.QueuedThreadPool: 1033 ] Evict skipped, threshold=59932ms in the future QueuedThreadPool[HttpClient@7218bb00]@42681628{STARTED,8<=12<=200,i=3,r=-1,t=-67ms,q=0}[ReservedThreadExecutor@7457d199{capacity=16,threads=ThreadIdPool@397ce9d8{capacity=16}}]
[DEBUG] [ o.e.j.u.t.ReservedThreadExecutor: 348 ] waiting for task
[DEBUG] [ o.e.j.util.thread.QueuedThreadPool: 1015 ] Evict check, period=60000ms QueuedThreadPool[HttpClient@7218bb00]@42681628{STARTED,8<=12<=200,i=3,r=-1,t=-74ms,q=0}[ReservedThreadExecutor@7457d199{capacity=16,threads=ThreadIdPool@397ce9d8{capacity=16}}]
[DEBUG] [ o.e.j.util.thread.QueuedThreadPool: 1033 ] Evict skipped, threshold=59925ms in the future QueuedThreadPool[HttpClient@7218bb00]@42681628{STARTED,8<=12<=200,i=3,r=-1,t=-75ms,q=0}[ReservedThreadExecutor@7457d199{capacity=16,threads=ThreadIdPool@397ce9d8{capacity=16}}]
[DEBUG] [ com.zaxxer.hikari.pool.PoolBase: 133 ] HikariPool-1 - Closing connection oracle.jdbc.driver.T4CConnection@24c9581b: (connection has passed maxLifetime)
[DEBUG] [ com.zaxxer.hikari.pool.HikariPool: 728 ] HikariPool-1 - Added connection oracle.jdbc.driver.T4CConnection@c485e55
[DEBUG] [ com.zaxxer.hikari.pool.HikariPool: 405 ] HikariPool-1 - Connection not added, stats (total=10, active=0, idle=10, waiting=0)
However, with exactly the same code in Spring Boot 2, it resolves the call without a problem.
Thanks for the support! :)
Comment From: bclozel
I can reproduce this behavior without Spring being involved. The "www-authenticate" header is not required, a 401 HTTP response is enough to trigger this behavior.
I've opened https://github.com/jetty/jetty.project/issues/12652 and will close this issue. I'll reopen it if it turns out this is an integration issue on our side. Thanks for the report!
Comment From: Canocles
Thank you for your promptness! @bclozel
Comment From: bclozel
Thanks to Simone's comment on the Jetty issue, you can use this workaround in the meantime:
HttpClient httpClient = new HttpClient();
httpClient.start();
httpClient.getProtocolHandlers().remove(WWWAuthenticationProtocolHandler.NAME);
WebClient webClient = builder
.clientConnector(new JettyClientHttpConnector(httpClient))
.baseUrl("https://httpbin.org").build();
Comment From: Canocles
Thank you very much to both for your time.
Indeed, I have tried using the header removal in the same way you have done and everything seems to work as it should. For the moment, it is a good temporary solution.
Again, thanks for your time.