This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 9140b90954 [Fix][Connector-V2] Fix HTTP cursor pagination no-progress 
loop (#11098)
9140b90954 is described below

commit 9140b90954d43f9d4debe2b50dcf7a4595be23c2
Author: Jast <[email protected]>
AuthorDate: Thu Jun 18 08:24:46 2026 +0800

    [Fix][Connector-V2] Fix HTTP cursor pagination no-progress loop (#11098)
---
 .../seatunnel/http/source/HttpSourceReader.java    |  8 +++-
 .../http/HttpSourceReaderInternalPollNextTest.java | 48 ++++++++++++++++++++++
 2 files changed, 54 insertions(+), 2 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java
 
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java
index 2bc66a3796..3ed214ce67 100644
--- 
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java
+++ 
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java
@@ -464,6 +464,7 @@ public class HttpSourceReader extends 
AbstractSingleSplitReader<SeaTunnelRow> {
 
             // cursor pagination
             if 
(HttpPaginationType.CURSOR.getCode().equals(pageInfo.getPageType())) {
+                String currentCursor = pageInfo.getCursor();
                 // get cursor value from response JSON with fileName
                 String cursorResponseField = 
pageInfo.getPageCursorResponseField();
                 ReadContext context = 
JsonPath.using(jsonConfiguration).parse(data);
@@ -473,8 +474,11 @@ public class HttpSourceReader extends 
AbstractSingleSplitReader<SeaTunnelRow> {
                     newCursor = cursorList.get(0);
                 }
                 pageInfo.setCursor(newCursor);
-                // if not present cursor, then no more data
-                noMoreElementFlag = Strings.isNullOrEmpty(newCursor);
+                // If the response cursor is empty or unchanged, the next 
request cannot make
+                // progress.
+                noMoreElementFlag =
+                        Strings.isNullOrEmpty(newCursor)
+                                || Objects.equals(currentCursor, newCursor);
             } else {
                 // if not set page pagination is default
                 // Determine whether the task is completed by specifying the 
presence of the 'total
diff --git 
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpSourceReaderInternalPollNextTest.java
 
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpSourceReaderInternalPollNextTest.java
index 057eeb851b..13ac4dc8ac 100644
--- 
a/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpSourceReaderInternalPollNextTest.java
+++ 
b/seatunnel-connectors-v2/connector-http/connector-http-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/http/HttpSourceReaderInternalPollNextTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.seatunnel.connectors.seatunnel.http;
 
+import org.apache.seatunnel.api.source.Boundedness;
 import org.apache.seatunnel.api.source.Collector;
 import org.apache.seatunnel.api.table.type.BasicType;
 import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -42,10 +43,13 @@ import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class HttpSourceReaderInternalPollNextTest {
@@ -135,6 +139,50 @@ public class HttpSourceReaderInternalPollNextTest {
         httpSourceReader.close();
     }
 
+    @Test
+    public void testCursorPaginationStopsWhenCursorDoesNotAdvance() throws 
Exception {
+        
httpParameter.setBody("{\"scrollId\":\"${scrollId}\",\"capacity\":100}");
+
+        PageInfo pageInfo = new PageInfo();
+        pageInfo.setPageType(HttpPaginationType.CURSOR.getCode());
+        pageInfo.setPageCursorFieldName("scrollId");
+        pageInfo.setPageCursorResponseField("$.scrollId");
+        pageInfo.setUsePlaceholderReplacement(true);
+
+        when(context.getBoundedness()).thenReturn(Boundedness.BOUNDED);
+        AtomicInteger requestCount = new AtomicInteger();
+        when(httpClientProvider.execute(
+                        anyString(), anyString(), any(), any(), any(), 
anyBoolean()))
+                .thenAnswer(
+                        invocation -> {
+                            int currentRequest = 
requestCount.incrementAndGet();
+                            if (currentRequest == 1) {
+                                return new HttpResponse(
+                                        200,
+                                        
"{\"scrollId\":\"cursor-1\",\"data\":[{\"key1\":\"v1\",\"key2\":\"v2\"}]}");
+                            }
+                            if (currentRequest == 2) {
+                                return new HttpResponse(
+                                        200, 
"{\"scrollId\":\"cursor-1\",\"data\":[]}");
+                            }
+                            throw new AssertionError(
+                                    "Cursor pagination should stop when the 
cursor does not advance");
+                        });
+
+        httpSourceReader =
+                new HttpSourceReader(
+                        httpParameter, context, deserializationSchema, null, 
"$.data", pageInfo);
+        httpSourceReader.open();
+        httpSourceReader.setHttpClient(httpClientProvider);
+
+        httpSourceReader.internalPollNext(collector);
+
+        verify(httpClientProvider, times(2))
+                .execute(anyString(), anyString(), any(), any(), any(), 
anyBoolean());
+        verify(context, times(1)).signalNoMoreElement();
+        httpSourceReader.close();
+    }
+
     @AfterEach
     public void tearDown() throws Exception {
         mock.close();

Reply via email to