diff --git a/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java index a73aa2225..d58496136 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java +++ b/arex-instrumentation/httpclient/arex-httpclient-feign/src/main/java/io/arex/inst/httpclient/feign/FeignClientInstrumentation.java @@ -52,10 +52,13 @@ public static boolean onEnter(@Argument(0)Request request, if (IgnoreUtils.excludeOperation(uri.getPath())) { return false; } + // check outermost before enter() so nested http clients (e.g. Feign -> Apache) + // skip replay and avoid request body double consumption + boolean isOutermost = RepeatedCollectManager.validate(); RepeatedCollectManager.enter(); adapter = new FeignClientAdapter(request, uri); extractor = new HttpClientExtractor(adapter); - if (ContextManager.needReplay()) { + if (ContextManager.needReplay() && isOutermost) { mockResult = extractor.replay(); return mockResult != null && mockResult.notIgnoreMockResult(); } @@ -73,6 +76,9 @@ public static void onExit(@Local("adapter") FeignClientAdapter adapter, return; } + // pair enter() unconditionally to keep CallDepth balanced across replay/record mixed flows + boolean isOutermost = RepeatedCollectManager.exitAndValidate(); + if (mockResult != null && mockResult.notIgnoreMockResult()) { if (mockResult.getThrowable() != null) { throwable = mockResult.getThrowable(); @@ -82,7 +88,7 @@ public static void onExit(@Local("adapter") FeignClientAdapter adapter, return; } - if (ContextManager.needRecord() && RepeatedCollectManager.exitAndValidate()) { + if (ContextManager.needRecord() && isOutermost) { response = adapter.copyResponse(response); if (throwable != null) { extractor.record(throwable); diff --git a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java index 75726d5c4..401dca276 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java +++ b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java @@ -66,7 +66,12 @@ void onEnter() { // need replay and not exclude operation Mockito.when(ContextManager.needReplay()).thenReturn(true); + Mockito.when(RepeatedCollectManager.validate()).thenReturn(true); assertTrue(FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); + + // nested call: outer already entered, inner replay should be skipped + Mockito.when(RepeatedCollectManager.validate()).thenReturn(false); + assertFalse(FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); } @Test