RestClient Interceptor (ClientHttpRequestInterceptor) don't chain as expected #34169
Labels
in: web
Issues in web modules (web, webmvc, webflux, websocket)
status: waiting-for-triage
An issue we've not yet triaged or decided on
org.springframework.http.client.ClientHttpRequestInterceptor
javadoc describes the interceptor as a "chain", and I assumed that theClientHttpRequestExecution execution
argument was a reference to the next link/interceptor in the chain (or the terminal request execution.)The above assumption fails if the interceptor makes more than one call to
execution.execute(request,body)
. That is, all calls after the first will skip any following interceptors.Issue Demo Code
Root Cause?
I traced the issue down to the
org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution
class which is the type being passed into the interceptors as theexecution
argument. Here's an annotated snippet of that class showing the issue:The use of an iterator and
iterator.hasNext()
results in a visitor behavioral pattern instead of an execution chain.My expected behavior for a chain pattern is that any node in the chain has a reference to the next node in the chain and that there is no state outside of the chain arguments. It seems quite odd that calls to execute the next interceptor in the chain has the side-effect of changing the next reference.
Example Use Case
Using a request interceptor, detect a request that results in a
401 Unauthorized
response and replay the request with a refreshed token in the authorization header. For example:Workarounds
RestClient
into the repeating interceptor - If the interceptor had a reference to the otherwise fully configured rest client, it could use it to make follow on requests. Note that those requests would repeat the interceptor chain from the start instead of here forward. It also tightly couples the injected rest client to the interceptor; in fact, that injection would likely need to happen per request which would require the calling code to create an instance of the repeating interceptor (with a reference to the in flight rest client) and add that instance to the rest client (circular references!). This smells.InterceptingClientHttpRequestFactory
which is easy enough to replicate and have it use a fixed version ofInterceptingClientHttpRequest
, but ... I don't see a way to set my version intoDefaultRestClient.interceptingRequestFactory
; a lazily set field in a package-private final class. While I could use reflection to overcome the accessibility issues, its a brittle solution that will earn me the stink eye from peers.The text was updated successfully, but these errors were encountered: