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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new cfe3cd9f28 Add configurable HTTP retry mechanism to REST transform 
(#6440)
cfe3cd9f28 is described below

commit cfe3cd9f28ac2594b0581e8094293a68336513a2
Author: lance <[email protected]>
AuthorDate: Mon Feb 2 20:41:46 2026 +0800

    Add configurable HTTP retry mechanism to REST transform (#6440)
    
    Signed-off-by: lance <[email protected]>
---
 .../apache/hop/pipeline/transforms/rest/Rest.java  | 211 ++++++++++++++++-----
 .../hop/pipeline/transforms/rest/RestDialog.java   | 194 +++++++++++++++++++
 .../hop/pipeline/transforms/rest/RestMeta.java     |  39 ++++
 .../pipeline/transforms/rest/common/RestConst.java |  44 +++++
 .../rest/messages/messages_en_US.properties        |   7 +
 .../rest/messages/messages_zh_CN.properties        |   7 +
 6 files changed, 459 insertions(+), 43 deletions(-)

diff --git 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
index f96d450613..24dfd0ae05 100644
--- 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
+++ 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/Rest.java
@@ -40,6 +40,8 @@ import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
 import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Supplier;
 import javax.net.ssl.SSLContext;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.Const;
@@ -247,48 +249,24 @@ public class Rest extends BaseTransform<RestMeta, 
RestData> {
           logDebug(BaseMessages.getString(PKG, "Rest.Log.BodyValue", 
entityString));
         }
       }
-      try {
-        switch (data.method) {
-          case RestMeta.HTTP_METHOD_GET -> response = 
invocationBuilder.get(Response.class);
-          case RestMeta.HTTP_METHOD_POST -> {
-            if (null != contentType) {
-              response = invocationBuilder.post(Entity.entity(entityString, 
contentType));
-            } else {
-              response = invocationBuilder.post(Entity.entity(entityString, 
data.mediaType));
-            }
-          }
-          case RestMeta.HTTP_METHOD_PUT -> {
-            if (null != contentType) {
-              response = invocationBuilder.put(Entity.entity(entityString, 
contentType));
-            } else {
-              response = invocationBuilder.put(Entity.entity(entityString, 
data.mediaType));
-            }
-          }
-          case RestMeta.HTTP_METHOD_DELETE -> {
-            Invocation invocation =
-                invocationBuilder.build("DELETE", Entity.entity(entityString, 
data.mediaType));
-            response = invocation.invoke();
-          }
-          case RestMeta.HTTP_METHOD_HEAD -> response = 
invocationBuilder.head();
-          case RestMeta.HTTP_METHOD_OPTIONS -> response = 
invocationBuilder.options();
-          case RestMeta.HTTP_METHOD_PATCH -> {
-            if (null != contentType) {
-              response =
-                  invocationBuilder.method(
-                      RestMeta.HTTP_METHOD_PATCH, Entity.entity(entityString, 
contentType));
-            } else {
-              response =
-                  invocationBuilder.method(
-                      RestMeta.HTTP_METHOD_PATCH, Entity.entity(entityString, 
data.mediaType));
-            }
-          }
-          default ->
-              throw new HopException(
-                  BaseMessages.getString(PKG, "Rest.Error.UnknownMethod", 
data.method));
-        }
-      } catch (Exception e) {
-        throw new HopException("Request could not be processed", e);
-      }
+
+      // execute first call
+      final Invocation.Builder finalInvocationBuilder = invocationBuilder;
+      final String finalEntityString = entityString;
+      final String finalContentType = contentType;
+
+      // add retry
+      response =
+          executeWithRetry(
+              () -> {
+                try {
+                  return executeRequest(
+                      finalInvocationBuilder, finalEntityString, 
finalContentType);
+                } catch (HopException e) {
+                  throw new RuntimeException(e);
+                }
+              });
+
       if (response != null) {
         response.bufferEntity();
       }
@@ -418,6 +396,154 @@ public class Rest extends BaseTransform<RestMeta, 
RestData> {
     return newRow;
   }
 
+  private Response executeWithRetry(Supplier<Response> requestSupplier) throws 
HopException {
+    int maxRetries = meta.getRetryTimes();
+    long baseDelay = meta.getRetryDelayMs();
+    List<String> retryMethods = meta.getRetryMethods();
+    Exception lastException = null;
+
+    if (retryMethods == null || retryMethods.isEmpty() || 
!retryMethods.contains(data.method)) {
+      return requestSupplier.get();
+    }
+
+    for (int attempt = 0; attempt <= maxRetries; attempt++) {
+      Response response = null;
+      try {
+        response = requestSupplier.get();
+        int status = response.getStatus();
+
+        if (!shouldRetry(String.valueOf(status))) {
+          return response;
+        }
+
+        logRetry(attempt, maxRetries, status);
+        if (attempt >= maxRetries) {
+          throw new HopException("Request failed after retries, status: " + 
status);
+        }
+      } catch (Exception e) {
+        lastException = e;
+        if (attempt == maxRetries) {
+          throw new HopException("Request failed after retries", e);
+        }
+      }
+
+      if (response != null) {
+        response.close();
+      }
+      sleepBeforeRetry(attempt, baseDelay);
+    }
+
+    throw new HopException("Request failed after retries", lastException);
+  }
+
+  /**
+   * Sleeps for a computed backoff duration before performing the next retry.
+   *
+   * @param attempt the current retry attempt, starting from 0
+   * @param delay the base delay in milliseconds
+   */
+  private void sleepBeforeRetry(int attempt, long delay) {
+    try {
+      Thread.sleep(computeRetryDelay(attempt, delay));
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  /**
+   * Determines whether the current request should be retried.
+   *
+   * @param status the HTTP response status code as a string
+   * @return {@code true} if the request should be retried, {@code false} 
otherwise
+   */
+  private boolean shouldRetry(String status) {
+    return meta.getRetryStatusCodes().contains(status);
+  }
+
+  /** Logs retry attempt information at detailed log level. */
+  private void logRetry(int attempt, int maxRetries, int status) {
+    if (isDetailed()) {
+      logDetailed(
+          "Retry rest {0}/{1}, status: {2}, method: {3}",
+          attempt + 1, maxRetries, status, meta.getMethod());
+    }
+  }
+
+  /**
+   * Computes the retry delay using an exponential backoff strategy with equal 
jitter.
+   *
+   * <pre>
+   *   delay = 100
+   *   attempt = 0 → delay     -> 50 ~ 149 ms
+   *   attempt = 1 → delay * 2 -> 100 ~ 199 ms
+   *   attempt = 2 → delay * 4 -> 200 ~ 299 ms
+   * </pre>
+   *
+   * @param attempt the current retry attempt, starting from 0
+   * @param delay the base delay in milliseconds
+   * @return the computed retry delay in milliseconds
+   */
+  private long computeRetryDelay(int attempt, long delay) {
+    long maxDelay = 30_000;
+
+    long expDelay = delay * (1L << attempt);
+    long capped = Math.min(expDelay, maxDelay);
+
+    long jitter = ThreadLocalRandom.current().nextLong(delay);
+    return capped / 2 + jitter;
+  }
+
+  private Response executeRequest(
+      Invocation.Builder invocationBuilder, String entityString, String 
contentType)
+      throws HopException {
+    try {
+      switch (data.method) {
+        case RestMeta.HTTP_METHOD_GET -> {
+          return invocationBuilder.get(Response.class);
+        }
+        case RestMeta.HTTP_METHOD_POST -> {
+          if (null != contentType) {
+            return invocationBuilder.post(Entity.entity(entityString, 
contentType));
+          } else {
+            return invocationBuilder.post(Entity.entity(entityString, 
data.mediaType));
+          }
+        }
+        case RestMeta.HTTP_METHOD_PUT -> {
+          if (null != contentType) {
+            return invocationBuilder.put(Entity.entity(entityString, 
contentType));
+          } else {
+            return invocationBuilder.put(Entity.entity(entityString, 
data.mediaType));
+          }
+        }
+        case RestMeta.HTTP_METHOD_DELETE -> {
+          Invocation invocation =
+              invocationBuilder.build("DELETE", Entity.entity(entityString, 
data.mediaType));
+          return invocation.invoke();
+        }
+        case RestMeta.HTTP_METHOD_HEAD -> {
+          return invocationBuilder.head();
+        }
+        case RestMeta.HTTP_METHOD_OPTIONS -> {
+          return invocationBuilder.options();
+        }
+        case RestMeta.HTTP_METHOD_PATCH -> {
+          if (null != contentType) {
+            return invocationBuilder.method(
+                RestMeta.HTTP_METHOD_PATCH, Entity.entity(entityString, 
contentType));
+          } else {
+            return invocationBuilder.method(
+                RestMeta.HTTP_METHOD_PATCH, Entity.entity(entityString, 
data.mediaType));
+          }
+        }
+        default ->
+            throw new HopException(
+                BaseMessages.getString(PKG, "Rest.Error.UnknownMethod", 
data.method));
+      }
+    } catch (Exception e) {
+      throw new HopException("Request could not be processed", e);
+    }
+  }
+
   private void setConfig() throws HopException {
     if (data.config == null) {
       // Use ApacheHttpClient for supporting proxy authentication.
@@ -731,7 +857,6 @@ public class Rest extends BaseTransform<RestMeta, RestData> 
{
 
   @Override
   public void dispose() {
-
     data.config = null;
     data.headerNames = null;
     data.indexOfHeaderFields = null;
diff --git 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestDialog.java
 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestDialog.java
index b60dc3cd53..a21f8eb37d 100644
--- 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestDialog.java
+++ 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestDialog.java
@@ -29,9 +29,11 @@ import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.rest.RestConnection;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.rest.common.RestConst;
 import org.apache.hop.pipeline.transforms.rest.fields.HeaderField;
 import org.apache.hop.pipeline.transforms.rest.fields.MatrixParameterField;
 import org.apache.hop.pipeline.transforms.rest.fields.ParameterField;
+import org.apache.hop.ui.core.FormDataBuilder;
 import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
@@ -55,6 +57,7 @@ import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Group;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
@@ -150,6 +153,13 @@ public class RestDialog extends BaseTransformDialog {
 
   private MetaSelectionLine wSelectionLine;
 
+  /** retry */
+  private TextVar wRetryTimes;
+
+  private TextVar wRetryDelay;
+  private TableView wStatusCodes;
+  private TableView wMethods;
+
   public RestDialog(
       Shell parent, IVariables variables, RestMeta transformMeta, PipelineMeta 
pipelineMeta) {
     super(parent, variables, transformMeta, pipelineMeta);
@@ -425,6 +435,9 @@ public class RestDialog extends BaseTransformDialog {
 
     setupMatrixParamTabContent(lsMod, margin, wMatrixParametersTab, 
wMatrixParametersComp);
 
+    // retry
+    addRetryTabItem(wTabFolder, lsMod);
+
     FormData fdTabFolder = new FormData();
     fdTabFolder.left = new FormAttachment(0, 0);
     fdTabFolder.top = new FormAttachment(wTransformName, margin);
@@ -455,6 +468,117 @@ public class RestDialog extends BaseTransformDialog {
     return transformName;
   }
 
+  private void addRetryTabItem(CTabFolder wTabFolder, ModifyListener lsMod) {
+    CTabItem retryTab = new CTabItem(wTabFolder, SWT.NONE);
+    retryTab.setFont(GuiResource.getInstance().getFontDefault());
+    retryTab.setText(BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Title"));
+
+    Composite wRetryComp = new Composite(wTabFolder, SWT.NONE);
+    PropsUi.setLook(wRetryComp);
+    FormLayout pl = new FormLayout();
+    pl.marginWidth = PropsUi.getFormMargin();
+    pl.marginHeight = PropsUi.getFormMargin();
+    wRetryComp.setLayout(pl);
+
+    // Retry Times
+    Label wlRetryTimes = new Label(wRetryComp, SWT.RIGHT);
+    wlRetryTimes.setText(BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Times"));
+    PropsUi.setLook(wlRetryTimes);
+    wlRetryTimes.setLayoutData(FormDataBuilder.builder().top(0, 
5).left().width(300).build());
+
+    wRetryTimes = new TextVar(variables, wRetryComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wRetryTimes);
+    wRetryTimes.setLayoutData(
+        FormDataBuilder.builder().top(0, 5).left(wlRetryTimes, 5).right(100, 
0).build());
+
+    // Retry Delay (ms)
+    Label wlRetryDelay = new Label(wRetryComp, SWT.RIGHT);
+    wlRetryDelay.setText(BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Delay"));
+    PropsUi.setLook(wlRetryDelay);
+    wlRetryDelay.setLayoutData(
+        FormDataBuilder.builder().top(wlRetryTimes, 
5).left().width(300).build());
+
+    wRetryDelay = new TextVar(variables, wRetryComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wRetryDelay);
+    wRetryDelay.setLayoutData(
+        FormDataBuilder.builder().top(wRetryTimes, 5).left(wlRetryDelay, 
5).right(100, 0).build());
+
+    // Status Codes table (left)
+    retryResponseCodeLeft(wRetryComp, wlRetryDelay, lsMod);
+    // HTTP Methods table (right)
+    retryRequestMethodRight(wRetryComp, wRetryDelay, lsMod);
+
+    wRetryComp.setLayoutData(
+        FormDataBuilder.builder().top().left().right(100, 0).bottom(100, 
0).build());
+    wRetryComp.layout();
+    retryTab.setControl(wRetryComp);
+  }
+
+  private void retryResponseCodeLeft(Composite wRetryComp, Control top, 
ModifyListener lsMod) {
+    // Status Codes table (left)
+    Label wlStatusCodes = new Label(wRetryComp, SWT.LEFT);
+    wlStatusCodes.setText(BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Http.Status"));
+    PropsUi.setLook(wlStatusCodes);
+    wlStatusCodes.setLayoutData(FormDataBuilder.builder().top(top, 30).left(0, 
0).build());
+
+    String colName = BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Http.Status.Header");
+    ColumnInfo[] headers =
+        new ColumnInfo[] {new ColumnInfo(colName, ColumnInfo.COLUMN_TYPE_TEXT, 
false)};
+
+    wStatusCodes =
+        new TableView(
+            variables,
+            wRetryComp,
+            SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI,
+            headers,
+            0,
+            false,
+            lsMod,
+            props);
+
+    PropsUi.setLook(wStatusCodes);
+    wStatusCodes.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlStatusCodes, PropsUi.getMargin())
+            .left(0, 0)
+            .right(50, -PropsUi.getMargin() / 2)
+            .bottom(100, 0)
+            .build());
+  }
+
+  private void retryRequestMethodRight(Composite wRetryComp, Control top, 
ModifyListener lsMod) {
+    // HTTP Methods table (right)
+    Label wlMethods = new Label(wRetryComp, SWT.LEFT);
+    wlMethods.setText(BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Http.Method"));
+    PropsUi.setLook(wlMethods);
+    wlMethods.setLayoutData(
+        FormDataBuilder.builder().top(top, 30).left(50, PropsUi.getMargin() / 
2).build());
+
+    String colName = BaseMessages.getString(PKG, 
"RestDialog.Tab.Retry.Http.Method.Header");
+    ColumnInfo[] headers =
+        new ColumnInfo[] {new ColumnInfo(colName, ColumnInfo.COLUMN_TYPE_TEXT, 
false)};
+
+    wMethods =
+        new TableView(
+            variables,
+            wRetryComp,
+            SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI,
+            headers,
+            0,
+            false,
+            lsMod,
+            props);
+
+    PropsUi.setLook(wMethods);
+    wMethods.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlMethods, PropsUi.getMargin())
+            .left(50, PropsUi.getMargin() / 2)
+            .right(100, 0)
+            .bottom(100, 0)
+            .build());
+  }
+
   private void setupMatrixParamTabContent(
       ModifyListener lsMod,
       int margin,
@@ -1460,10 +1584,46 @@ public class RestDialog extends BaseTransformDialog {
     wFields.setRowNums();
     wFields.optWidth(true);
 
+    // get retry data
+    getDataForRetry();
+
     wTransformName.selectAll();
     wTransformName.setFocus();
   }
 
+  private void getDataForRetry() {
+    if (input.getRetryTimes() != null) {
+      wRetryTimes.setText(input.getRetryTimes().toString());
+    } else {
+      wRetryTimes.setText(RestConst.DEFAULT_RETRY_TIMES + Const.EMPTY_STRING);
+    }
+
+    if (input.getRetryDelayMs() != null) {
+      wRetryDelay.setText(input.getRetryDelayMs().toString());
+    } else {
+      wRetryDelay.setText(RestConst.DEFAULT_RETRY_DELAY_MS + 
Const.EMPTY_STRING);
+    }
+
+    // http response code
+    List<String> codes = input.getRetryStatusCodes();
+    if (codes != null && !codes.isEmpty()) {
+      wStatusCodes.table.removeAll();
+      for (String code : codes) {
+        TableItem item = new TableItem(wStatusCodes.table, SWT.NONE);
+        item.setText(1, code);
+      }
+    }
+
+    // http request methods
+    List<String> methods = input.getRetryMethods();
+    if (methods != null && !methods.isEmpty()) {
+      wMethods.table.removeAll();
+      for (String method : methods) {
+        wMethods.add(method);
+      }
+    }
+  }
+
   private void cancel() {
     transformName = null;
     input.setChanged(changed);
@@ -1538,9 +1698,43 @@ public class RestDialog extends BaseTransformDialog {
     input.setApplicationType(wApplicationType.getText());
     transformName = wTransformName.getText(); // return value
 
+    okForRetry();
     dispose();
   }
 
+  private void okForRetry() {
+    // retry times
+    if (!Utils.isEmpty(wRetryTimes.getText())) {
+      int times = Integer.parseInt(wRetryTimes.getText());
+      input.setRetryTimes(Math.max(times, 0));
+    } else {
+      input.setRetryTimes(RestConst.DEFAULT_RETRY_TIMES);
+    }
+
+    // retry delay
+    if (!Utils.isEmpty(wRetryDelay.getText())) {
+      long delay = Long.parseLong(wRetryDelay.getText());
+      input.setRetryDelayMs(Math.max(delay, RestConst.DEFAULT_RETRY_DELAY_MS / 
2));
+    } else {
+      input.setRetryDelayMs(RestConst.DEFAULT_RETRY_DELAY_MS);
+    }
+
+    input.getRetryStatusCodes().clear();
+    for (int i = 0; i < wStatusCodes.nrNonEmpty(); i++) {
+      TableItem item = wStatusCodes.getNonEmpty(i);
+      input.getRetryStatusCodes().add(item.getText(1));
+    }
+
+    input.getRetryMethods().clear();
+    for (int i = 0; i < wMethods.nrNonEmpty(); i++) {
+      TableItem item = wMethods.getNonEmpty(i);
+      String text = item.getText(1);
+      if (!Utils.isEmpty(text)) {
+        input.getRetryMethods().add(text.toUpperCase());
+      }
+    }
+  }
+
   private void getParametersFields(TableView tView) {
     try {
       IRowMeta r = pipelineMeta.getPrevTransformFields(variables, 
transformName);
diff --git 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestMeta.java
 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestMeta.java
index 4ac1f9155f..886288992b 100644
--- 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestMeta.java
+++ 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/RestMeta.java
@@ -36,6 +36,7 @@ import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.rest.common.RestConst;
 import org.apache.hop.pipeline.transforms.rest.fields.HeaderField;
 import org.apache.hop.pipeline.transforms.rest.fields.MatrixParameterField;
 import org.apache.hop.pipeline.transforms.rest.fields.ParameterField;
@@ -185,12 +186,44 @@ public class RestMeta extends BaseTransformMeta<Rest, 
RestData> {
   @HopMetadataProperty(key = "result", injectionKey = "RESULT")
   private ResultField resultField;
 
+  /**
+   * retry config retryTimes=0 retryDelayMs=500ms retryStatusCode=[429, 500, 
502, 503, 504]
+   * retryMethods=[post,get,delete,put,head,option,patch]
+   */
+  /*--------------------------------------------------------------------
+  | retry config(retryTimes=0,retryDelayMs=500ms)
+  | retryStatusCode=[429, 500, 502, 503, 504]
+  | retryMethods=[get,delete,put]
+  --------------------------------------------------------------------- */
+  @HopMetadataProperty(key = "retryTimes", injectionKey = "RETRY_TIMES")
+  private Integer retryTimes;
+
+  @HopMetadataProperty(key = "retryDelayMs", injectionKey = "RETRY_DELAY_MS")
+  private Long retryDelayMs;
+
+  @HopMetadataProperty(
+      key = "retryStatusCode",
+      injectionKey = "RETRY_STATUS_CODE",
+      groupKey = "retryStatusCodes",
+      injectionGroupKey = "RETRY_STATUS_CODES")
+  private List<String> retryStatusCodes;
+
+  @HopMetadataProperty(
+      key = "retryMethod",
+      injectionKey = "RETRY_METHOD",
+      groupKey = "retryMethods",
+      injectionGroupKey = "RETRY_METHODS")
+  private List<String> retryMethods;
+
   public RestMeta() {
     super(); // allocate BaseTransformMeta
     headerFields = new ArrayList<>();
     parameterFields = new ArrayList<>();
     matrixParameterFields = new ArrayList<>();
     resultField = new ResultField();
+
+    this.retryStatusCodes = new ArrayList<>();
+    this.retryMethods = new ArrayList<>();
   }
 
   @Override
@@ -216,6 +249,12 @@ public class RestMeta extends BaseTransformMeta<Rest, 
RestData> {
     this.applicationType = APPLICATION_TYPE_TEXT_PLAIN;
     this.readTimeout = String.valueOf(DEFAULT_READ_TIMEOUT);
     this.connectionTimeout = String.valueOf(DEFAULT_CONNECTION_TIMEOUT);
+
+    // retry config.
+    this.retryTimes = RestConst.DEFAULT_RETRY_TIMES;
+    this.retryDelayMs = RestConst.DEFAULT_RETRY_DELAY_MS;
+    this.retryStatusCodes.addAll(RestConst.retryStatusCodes());
+    this.retryMethods.addAll(RestConst.retryMethods());
   }
 
   @Override
diff --git 
a/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/common/RestConst.java
 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/common/RestConst.java
new file mode 100644
index 0000000000..8b2e346e41
--- /dev/null
+++ 
b/plugins/transforms/rest/src/main/java/org/apache/hop/pipeline/transforms/rest/common/RestConst.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.rest.common;
+
+import java.util.Set;
+import lombok.experimental.UtilityClass;
+import org.apache.hop.pipeline.transforms.rest.RestMeta;
+
+/** rest const */
+@UtilityClass
+public class RestConst {
+  public static final int DEFAULT_RETRY_TIMES = 0;
+  public static final long DEFAULT_RETRY_DELAY_MS = 200L;
+
+  /**
+   * By default, the HTTP status codes that should be retried include 429, 
500, 502, 503, and 504.
+   */
+  public static Set<String> retryStatusCodes() {
+    return Set.of("429", "500", "502", "503", "504");
+  }
+
+  /**
+   * By default, the HTTP methods that should be retried include GET, PUT, and 
DELETE, as these
+   * methods are considered idempotent and safe to retry.
+   */
+  public static Set<String> retryMethods() {
+    return Set.of(RestMeta.HTTP_METHOD_GET, RestMeta.HTTP_METHOD_DELETE, 
RestMeta.HTTP_METHOD_PUT);
+  }
+}
diff --git 
a/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_en_US.properties
 
b/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_en_US.properties
index dbc2df37a1..545f466c85 100644
--- 
a/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_en_US.properties
+++ 
b/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_en_US.properties
@@ -111,3 +111,10 @@ RestMeta.CheckResult.UrlMissing=URL is missing\!
 RestMeta.CheckResult.UrlOk=URL is specified.
 RestMeta.Exception.UnableToReadTransformMeta=Unable to read transform 
information from XML
 RestMeta.keyword=rest
+RestDialog.Tab.Retry.Title=Retry Option
+RestDialog.Tab.Retry.Times=Retry times
+RestDialog.Tab.Retry.Delay=Retry delay(ms)
+RestDialog.Tab.Retry.Http.Status=Retry HTTP status codes
+RestDialog.Tab.Retry.Http.Status.Header=Status Code
+RestDialog.Tab.Retry.Http.Method=Retry HTTP methods
+RestDialog.Tab.Retry.Http.Method.Header=Method
\ No newline at end of file
diff --git 
a/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_zh_CN.properties
 
b/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_zh_CN.properties
index 75646a3ef4..5c1f83fa57 100644
--- 
a/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_zh_CN.properties
+++ 
b/plugins/transforms/rest/src/main/resources/org/apache/hop/pipeline/transforms/rest/messages/messages_zh_CN.properties
@@ -106,3 +106,10 @@ RestMeta.CheckResult.UrlMissing=\u7F3A\u5931 URL\!
 RestMeta.CheckResult.UrlOk=\u5DF2\u6307\u5B9A URL
 RestMeta.Exception.UnableToReadTransformMeta=\u65E0\u6CD5\u4ECE XML 
\u7247\u65AD\u52A0\u8F7D\u8BE5 Transform \u4FE1\u606F
 RestMeta.keyword=rest
+RestDialog.Tab.Retry.Title=\u91CD\u8BD5\u8BBE\u7F6E
+RestDialog.Tab.Retry.Times=\u6700\u5927\u91CD\u8BD5\u6B21\u6570
+RestDialog.Tab.Retry.Delay=\u91CD\u8BD5\u5EF6\u8FDF(ms)
+RestDialog.Tab.Retry.Http.Status=\u91CD\u8BD5http\u54CD\u5E94\u7801
+RestDialog.Tab.Retry.Http.Status.Header=\u54CD\u5E94\u7801
+RestDialog.Tab.Retry.Http.Method=\u91CD\u8BD5http\u65B9\u6CD5
+RestDialog.Tab.Retry.Http.Method.Header=\u65B9\u6CD5\u540D\u79F0

Reply via email to