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 65c47f1a02 Issue #2666 : Store HTTP action reply in a variable (#7969)
65c47f1a02 is described below

commit 65c47f1a02d2b37c12e78703725fe20bd2eaca04
Author: Matt Casters <[email protected]>
AuthorDate: Sun Aug 16 13:07:41 2026 +0200

    Issue #2666 : Store HTTP action reply in a variable (#7969)
    
    Add an optional result variable to the HTTP workflow action so the
    response body can be reused by later actions. Split Authentication,
    Upload, and Reply onto their own scrolled dialog tabs.
---
 .../modules/ROOT/pages/workflow/actions/http.adoc  |  31 +++-
 .../hop/workflow/actions/http/ActionHttp.java      |  65 ++++++++
 .../workflow/actions/http/ActionHttpDialog.java    | 174 +++++++++++++--------
 .../http/messages/messages_en_US.properties        |   6 +
 .../actions/http/ActionHttpExecutionTest.java      |  46 ++++++
 .../actions/http/ActionHttpLoadSaveTest.java       |   2 +
 .../hop/workflow/actions/http/ActionHttpTest.java  |  15 ++
 .../http/src/test/resources/http-action.xml        |   1 +
 8 files changed, 267 insertions(+), 73 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/http.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/http.adoc
index 6daa331fea..5e32f4f2e6 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/http.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/http.adoc
@@ -45,6 +45,7 @@ If HTTP traffic is too heavy in your corporate environment, 
you may choose to us
 |Option|Description
 |Action name|The name of the workflow action.
 |URL|The HTTP URL of the file to retrieve, or the directory name to store an 
uploaded file to.
+|Ignore SSL certificate check|If selected, all SSL certificate checks are 
ignored.
 |Run for every result row?|Check this if you want to run this action for every 
row that was generated by a previous pipeline.
 Use the "Copy rows to result".
 If selected, an HTTP request will be made for each result.
@@ -52,21 +53,41 @@ Otherwise, the file is only retrieved once
 |Input field which contains URL|If the "Run for every result row?" option is 
selected, the field specified here will determine the file URL for each row
 |Input field which contains upload file name|If the "Run for every result 
row?" option is selected, the field specified here will determine the local 
file that will be uploaded to the URL associated with the "Input field which 
contains URL" field.
 |Input field which contains destination file name|If the "Run for every result 
row?" option is selected, the field specified here will determine the local 
file where the result downloaded from the URL associated with the "Input field 
which contains URL" field.
-2+|Authentication
+|===
+
+=== Authentication Tab
+
+[options="header", width="90%", cols="1,3"]
+|===
+|Option|Description
 |Username|If the site requires authentication, use this username to log in
 |Password|If a username is defined, this is the password for it
 |Proxy server for upload|The URL of a proxy server that you want to connect to 
the HTTP URL through
 |Proxy port|If a proxy server is defined, this is the port number it listens on
 |Ignore proxy for hosts|A regular expression list of exceptions for proxy 
redirection.
 This may be useful when working on an intranet or VPN
-2+|Upload file
-|Upload file|If you are uploading a file, this will be its name on the remote 
server
-2+|Webserver reply
-|Target file|If you are downloading a file, this its name on your local 
filesystem
+|===
+
+=== Upload Tab
+
+[options="header", width="90%", cols="1,3"]
+|===
+|Option|Description
+|Upload file|If you are uploading a file, this is the local file that will be 
sent to the HTTP server
+|===
+
+=== Reply Tab
+
+[options="header", width="90%", cols="1,3"]
+|===
+|Option|Description
+|Target file|If you are downloading a file, this is its name on your local 
filesystem
 |Append to specified target file?|If selected, and if the target file already 
exists, Hop will append all new data to the end of the file
 |Add date and time to file name?|If selected, the date and time of the HTTP 
request (in yyyMMdd_HHmmss format) will be added to the target filename
 |Target file extension|If the previous option is selected, this field 
specifies the extension (letters after the dot) of the target filename
 |Add filename to result filename|Any files that are copied will appear as a 
result from this action; shows a list of files that were copied in this action.
+|Result variable|If specified, the HTTP reply body is stored in this workflow 
variable so later actions can use it.
+When running for every result row, the last reply is stored.
 |===
 
 === Headers Tab
diff --git 
a/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttp.java
 
b/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttp.java
index 2a36bcb9cb..eaca76ea6a 100644
--- 
a/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttp.java
+++ 
b/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttp.java
@@ -18,6 +18,7 @@
 package org.apache.hop.workflow.actions.http;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -29,10 +30,13 @@ import java.net.MalformedURLException;
 import java.net.PasswordAuthentication;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import javax.net.ssl.HttpsURLConnection;
 import lombok.Getter;
 import lombok.Setter;
@@ -65,6 +69,7 @@ import org.apache.hop.workflow.WorkflowMeta;
 import org.apache.hop.workflow.action.ActionBase;
 import org.apache.hop.workflow.action.validator.ActionValidatorUtils;
 import org.apache.hop.workflow.action.validator.AndValidator;
+import org.apache.hop.workflow.engine.IWorkflowEngine;
 import org.w3c.dom.Node;
 
 /** This defines an HTTP action. */
@@ -146,6 +151,9 @@ public class ActionHttp extends ActionBase {
   @HopMetadataProperty(key = "addfilenameresult")
   private boolean addFilenameResult;
 
+  @HopMetadataProperty(key = "reply_variable")
+  private String replyVariableName;
+
   @HopMetadataProperty(key = "header", groupKey = "headers")
   private List<Header> headers;
 
@@ -403,15 +411,30 @@ public class ActionHttp extends ActionBase {
                   PKG, "ActionHTTP.Log.ReplayInfo", 
connection.getContentType(), date));
         }
 
+        ByteArrayOutputStream replyBuffer = null;
+        String resolvedReplyVariable =
+            Utils.isEmpty(replyVariableName) ? "" : resolve(replyVariableName);
+        if (!Utils.isEmpty(resolvedReplyVariable)) {
+          replyBuffer = new ByteArrayOutputStream();
+        }
+
         byte[] buffer = new byte[8192];
         int bytesRead;
         while ((bytesRead = input.read(buffer)) != -1) {
           outputFile.write(buffer, 0, bytesRead);
+          if (replyBuffer != null) {
+            replyBuffer.write(buffer, 0, bytesRead);
+          }
         }
         bytesReadThisRow += ((CountingInputStream) input).getCount();
         bytesWrittenThisRow += ((CountingOutputStream) outputFile).getCount();
         httpLineageResponseBytes = ((CountingInputStream) input).getCount();
 
+        if (replyBuffer != null) {
+          storeReplyInVariable(
+              resolvedReplyVariable, replyBuffer.toByteArray(), 
connection.getContentType());
+        }
+
         if (isBasic()) {
           logBasic(
               BaseMessages.getString(
@@ -557,6 +580,48 @@ public class ActionHttp extends ActionBase {
             
AndValidator.putValidators(ActionValidatorUtils.integerValidator()));
   }
 
+  /**
+   * Stores the HTTP reply body in a workflow variable so later actions can 
use it. The value is set
+   * on this action and on the parent workflow (and its parents).
+   */
+  private void storeReplyInVariable(String variableName, byte[] replyBytes, 
String contentType) {
+    if (Utils.isEmpty(variableName) || replyBytes == null) {
+      return;
+    }
+    String reply = new String(replyBytes, charsetFromContentType(contentType));
+    setVariable(variableName, reply);
+    IWorkflowEngine<WorkflowMeta> parent = getParentWorkflow();
+    while (parent != null) {
+      parent.setVariable(variableName, reply);
+      parent = parent.getParentWorkflow();
+    }
+    if (isBasic()) {
+      logBasic(BaseMessages.getString(PKG, 
"ActionHTTP.Log.ReplyStoredInVariable", variableName));
+    }
+  }
+
+  static Charset charsetFromContentType(String contentType) {
+    if (Utils.isEmpty(contentType)) {
+      return StandardCharsets.UTF_8;
+    }
+    String lower = contentType.toLowerCase(Locale.ROOT);
+    int idx = lower.indexOf("charset=");
+    if (idx < 0) {
+      return StandardCharsets.UTF_8;
+    }
+    String charsetName = contentType.substring(idx + 8).trim();
+    int separator = charsetName.indexOf(';');
+    if (separator >= 0) {
+      charsetName = charsetName.substring(0, separator).trim();
+    }
+    charsetName = charsetName.replace("\"", "").trim();
+    try {
+      return Charset.forName(charsetName);
+    } catch (Exception e) {
+      return StandardCharsets.UTF_8;
+    }
+  }
+
   @Getter
   @Setter
   public static final class Header {
diff --git 
a/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttpDialog.java
 
b/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttpDialog.java
index 92e4de3096..71b66f1b58 100644
--- 
a/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttpDialog.java
+++ 
b/plugins/actions/http/src/main/java/org/apache/hop/workflow/actions/http/ActionHttpDialog.java
@@ -39,9 +39,11 @@ import org.apache.hop.workflow.action.IAction;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.custom.ScrolledComposite;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
@@ -120,6 +122,8 @@ public class ActionHttpDialog extends ActionDialog {
 
   private Button wAddFilenameToResult;
 
+  private TextVar wReplyVariable;
+
   private ActionHttp action;
 
   private boolean changed;
@@ -151,15 +155,7 @@ public class ActionHttpDialog extends ActionDialog {
     // START OF GENERAL TAB ///
     // ////////////////////////
 
-    CTabItem wGeneralTab = new CTabItem(wTabFolder, SWT.NONE);
-    wGeneralTab.setFont(GuiResource.getInstance().getFontDefault());
-    wGeneralTab.setText(BaseMessages.getString(PKG, 
"ActionHTTP.Tab.General.Label"));
-    Composite wGeneralComp = new Composite(wTabFolder, SWT.NONE);
-    PropsUi.setLook(wGeneralComp);
-    FormLayout generalLayout = new FormLayout();
-    generalLayout.marginWidth = 3;
-    generalLayout.marginHeight = 3;
-    wGeneralComp.setLayout(generalLayout);
+    Composite wGeneralComp = addScrolledTab(wTabFolder, 
"ActionHTTP.Tab.General.Label");
 
     setupUrlLine(lsMod, middle, margin, wGeneralComp);
     setupIgnoreSslLine(middle, margin, wGeneralComp);
@@ -168,10 +164,18 @@ public class ActionHttpDialog extends ActionDialog {
     setupUploadFileLine(lsMod, middle, margin, wGeneralComp);
     setupDestFileLine(lsMod, middle, margin, wGeneralComp);
 
+    finishScrolledTab(wGeneralComp);
+
+    // ///////////////////////////////////////////////////////////
+    // / END OF GENERAL TAB
+    // ///////////////////////////////////////////////////////////
+
     // ////////////////////////
-    // START OF AuthenticationGROUP///
-    // /
-    Group wAuthentication = setupAuthGroup(wGeneralComp);
+    // START OF AUTHENTICATION TAB ///
+    // ////////////////////////
+
+    Composite wAuthComp = addScrolledTab(wTabFolder, 
"ActionHTTP.Tab.Authentication.Label");
+    Group wAuthentication = setupAuthGroup(wAuthComp);
 
     setupUsernameLine(lsMod, middle, margin, wAuthentication);
     setupPasswordLine(lsMod, middle, margin, wAuthentication);
@@ -181,78 +185,69 @@ public class ActionHttpDialog extends ActionDialog {
 
     FormData fdAuthentication = new FormData();
     fdAuthentication.left = new FormAttachment(0, margin);
-    fdAuthentication.top = new FormAttachment(wFieldTarget, margin);
+    fdAuthentication.top = new FormAttachment(0, margin);
     fdAuthentication.right = new FormAttachment(100, -margin);
     wAuthentication.setLayoutData(fdAuthentication);
+
+    finishScrolledTab(wAuthComp);
+
     // ///////////////////////////////////////////////////////////
-    // / END OF AuthenticationGROUP GROUP
+    // / END OF AUTHENTICATION TAB
     // ///////////////////////////////////////////////////////////
 
     // ////////////////////////
-    // START OF UpLoadFileGROUP///
-    // /
-    Group wUpLoadFile = setupUploadFileGroup(wGeneralComp);
+    // START OF UPLOAD TAB ///
+    // ////////////////////////
+
+    Composite wUploadComp = addScrolledTab(wTabFolder, 
"ActionHTTP.Tab.Upload.Label");
+    Group wUpLoadFile = setupUploadFileGroup(wUploadComp);
 
-    setupUploadFileLine(lsMod, middle, margin, wAuthentication, wUpLoadFile);
+    setupUploadFileLine(lsMod, middle, margin, wUpLoadFile);
 
     FormData fdUpLoadFile = new FormData();
     fdUpLoadFile.left = new FormAttachment(0, margin);
-    fdUpLoadFile.top = new FormAttachment(wAuthentication, margin);
+    fdUpLoadFile.top = new FormAttachment(0, margin);
     fdUpLoadFile.right = new FormAttachment(100, -margin);
     wUpLoadFile.setLayoutData(fdUpLoadFile);
+
+    finishScrolledTab(wUploadComp);
+
     // ///////////////////////////////////////////////////////////
-    // / END OF UpLoadFileGROUP GROUP
+    // / END OF UPLOAD TAB
     // ///////////////////////////////////////////////////////////
 
     // ////////////////////////
-    // START OF TargetFileGroupGROUP///
-    // /
-    Group wTargetFileGroup = setupWebServerReplyGroup(wGeneralComp);
+    // START OF REPLY TAB ///
+    // ////////////////////////
+
+    Composite wReplyComp = addScrolledTab(wTabFolder, 
"ActionHTTP.Tab.Reply.Label");
+    Group wTargetFileGroup = setupWebServerReplyGroup(wReplyComp);
 
     setupTargetFileLine(lsMod, middle, margin, wTargetFileGroup);
     setupAppendFileLine(middle, margin, wTargetFileGroup);
     setupAddDateTimeLine(middle, margin, wTargetFileGroup);
     setupTargetExtensionLine(lsMod, middle, margin, wTargetFileGroup);
     setupAddFilenameLine(middle, margin, wTargetFileGroup);
+    setupReplyVariableLine(lsMod, middle, margin, wTargetFileGroup);
 
     FormData fdTargetFileGroup = new FormData();
     fdTargetFileGroup.left = new FormAttachment(0, margin);
-    fdTargetFileGroup.top = new FormAttachment(wUpLoadFile, margin);
+    fdTargetFileGroup.top = new FormAttachment(0, margin);
     fdTargetFileGroup.right = new FormAttachment(100, -margin);
     wTargetFileGroup.setLayoutData(fdTargetFileGroup);
-    // ///////////////////////////////////////////////////////////
-    // / END OF TargetFileGroupGROUP GROUP
-    // ///////////////////////////////////////////////////////////
-
-    FormData fdGeneralComp = new FormData();
-    fdGeneralComp.left = new FormAttachment(0, 0);
-    fdGeneralComp.top = new FormAttachment(0, margin);
-    fdGeneralComp.right = new FormAttachment(100, 0);
-    fdGeneralComp.bottom = new FormAttachment(100, 0);
-    wGeneralComp.setLayoutData(fdGeneralComp);
 
-    wGeneralComp.layout();
-    wGeneralTab.setControl(wGeneralComp);
+    finishScrolledTab(wReplyComp);
 
     // ///////////////////////////////////////////////////////////
-    // / END OF GENERAL TAB
+    // / END OF REPLY TAB
     // ///////////////////////////////////////////////////////////
 
     // ////////////////////////
     // START OF Headers TAB ///
     // ////////////////////////
 
-    CTabItem wHeadersTab = new CTabItem(wTabFolder, SWT.NONE);
-    wHeadersTab.setFont(GuiResource.getInstance().getFontDefault());
-    wHeadersTab.setText(BaseMessages.getString(PKG, 
"ActionHTTP.Tab.Headers.Label"));
-    Composite wHeadersComp = new Composite(wTabFolder, SWT.NONE);
-    PropsUi.setLook(wHeadersComp);
-    FormLayout headersLayout = new FormLayout();
-    headersLayout.marginWidth = 3;
-    headersLayout.marginHeight = 3;
-    wHeadersComp.setLayout(headersLayout);
-
-    setupHeaderTable(lsMod, margin, wHeadersTab, wHeadersComp);
+    Composite wHeadersComp = addScrolledTab(wTabFolder, 
"ActionHTTP.Tab.Headers.Label");
+    setupHeaderTable(lsMod, margin, wHeadersComp);
 
     // ///////////////////////////////////////////////////////////
     // / END OF Headers TAB
@@ -273,8 +268,35 @@ public class ActionHttpDialog extends ActionDialog {
     return action;
   }
 
-  private void setupHeaderTable(
-      ModifyListener lsMod, int margin, CTabItem wHeadersTab, Composite 
wHeadersComp) {
+  private Composite addScrolledTab(CTabFolder wTabFolder, String labelKey) {
+    CTabItem tab = new CTabItem(wTabFolder, SWT.NONE);
+    tab.setFont(GuiResource.getInstance().getFontDefault());
+    tab.setText(BaseMessages.getString(PKG, labelKey));
+
+    ScrolledComposite scrolled = new ScrolledComposite(wTabFolder, 
SWT.V_SCROLL | SWT.H_SCROLL);
+    PropsUi.setLook(scrolled);
+    scrolled.setExpandHorizontal(true);
+    scrolled.setExpandVertical(true);
+
+    Composite composite = new Composite(scrolled, SWT.NONE);
+    PropsUi.setLook(composite);
+    FormLayout layout = new FormLayout();
+    layout.marginWidth = 3;
+    layout.marginHeight = 3;
+    composite.setLayout(layout);
+
+    scrolled.setContent(composite);
+    tab.setControl(scrolled);
+    return composite;
+  }
+
+  private void finishScrolledTab(Composite composite) {
+    composite.layout(true, true);
+    Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+    ((ScrolledComposite) composite.getParent()).setMinSize(size);
+  }
+
+  private void setupHeaderTable(ModifyListener lsMod, int margin, Composite 
wHeadersComp) {
     int rows =
         action.getHeaders() == null
             ? 1
@@ -312,15 +334,8 @@ public class ActionHttpDialog extends ActionDialog {
     fdHeaders.bottom = new FormAttachment(100, -margin);
     wHeaders.setLayoutData(fdHeaders);
 
-    FormData fdHeadersComp = new FormData();
-    fdHeadersComp.left = new FormAttachment(0, 0);
-    fdHeadersComp.top = new FormAttachment(0, 0);
-    fdHeadersComp.right = new FormAttachment(100, 0);
-    fdHeadersComp.bottom = new FormAttachment(100, 0);
-    wHeadersComp.setLayoutData(fdHeadersComp);
-
-    wHeadersComp.layout();
-    wHeadersTab.setControl(wHeadersComp);
+    // The table fills the tab; keep a minimum size so the tab can still 
scroll on small screens.
+    ((ScrolledComposite) wHeadersComp.getParent()).setMinSize(300, 200);
   }
 
   private void setupAddFilenameLine(int middle, int margin, Group 
wTargetFileGroup) {
@@ -345,6 +360,27 @@ public class ActionHttpDialog extends ActionDialog {
     wAddFilenameToResult.setLayoutData(fdAddFilenameToResult);
   }
 
+  private void setupReplyVariableLine(
+      ModifyListener lsMod, int middle, int margin, Group wTargetFileGroup) {
+    Label wlReplyVariable = new Label(wTargetFileGroup, SWT.RIGHT);
+    wlReplyVariable.setText(BaseMessages.getString(PKG, 
"ActionHTTP.ReplyVariable.Label"));
+    PropsUi.setLook(wlReplyVariable);
+    FormData fdlReplyVariable = new FormData();
+    fdlReplyVariable.left = new FormAttachment(0, 0);
+    fdlReplyVariable.top = new FormAttachment(wAddFilenameToResult, margin);
+    fdlReplyVariable.right = new FormAttachment(middle, -margin);
+    wlReplyVariable.setLayoutData(fdlReplyVariable);
+    wReplyVariable = new TextVar(variables, wTargetFileGroup, SWT.SINGLE | 
SWT.LEFT | SWT.BORDER);
+    PropsUi.setLook(wReplyVariable);
+    wReplyVariable.setToolTipText(BaseMessages.getString(PKG, 
"ActionHTTP.ReplyVariable.Tooltip"));
+    wReplyVariable.addModifyListener(lsMod);
+    FormData fdReplyVariable = new FormData();
+    fdReplyVariable.left = new FormAttachment(middle, 0);
+    fdReplyVariable.top = new FormAttachment(wlReplyVariable, 0, SWT.CENTER);
+    fdReplyVariable.right = new FormAttachment(100, 0);
+    wReplyVariable.setLayoutData(fdReplyVariable);
+  }
+
   private void setupTargetExtensionLine(
       ModifyListener lsMod, int middle, int margin, Group wTargetFileGroup) {
     // TargetExt line
@@ -423,7 +459,7 @@ public class ActionHttpDialog extends ActionDialog {
     PropsUi.setLook(wlTargetFile);
     FormData fdlTargetFile = new FormData();
     fdlTargetFile.left = new FormAttachment(0, 0);
-    fdlTargetFile.top = new FormAttachment(wUploadFile, margin);
+    fdlTargetFile.top = new FormAttachment(0, margin);
     fdlTargetFile.right = new FormAttachment(middle, -margin);
     wlTargetFile.setLayoutData(fdlTargetFile);
 
@@ -432,7 +468,7 @@ public class ActionHttpDialog extends ActionDialog {
     wbTargetFile.setText(BaseMessages.getString(PKG, "System.Button.Browse"));
     FormData fdbTargetFile = new FormData();
     fdbTargetFile.right = new FormAttachment(100, 0);
-    fdbTargetFile.top = new FormAttachment(wUploadFile, margin);
+    fdbTargetFile.top = new FormAttachment(0, margin);
     wbTargetFile.setLayoutData(fdbTargetFile);
 
     wTargetFile = new TextVar(variables, wTargetFileGroup, SWT.SINGLE | 
SWT.LEFT | SWT.BORDER);
@@ -441,7 +477,7 @@ public class ActionHttpDialog extends ActionDialog {
     wTargetFile.addModifyListener(lsMod);
     FormData fdTargetFile = new FormData();
     fdTargetFile.left = new FormAttachment(middle, 0);
-    fdTargetFile.top = new FormAttachment(wUploadFile, margin);
+    fdTargetFile.top = new FormAttachment(0, margin);
     fdTargetFile.right = new FormAttachment(wbTargetFile, -margin);
     wTargetFile.setLayoutData(fdTargetFile);
 
@@ -465,14 +501,14 @@ public class ActionHttpDialog extends ActionDialog {
   }
 
   private void setupUploadFileLine(
-      ModifyListener lsMod, int middle, int margin, Group wAuthentication, 
Group wUpLoadFile) {
+      ModifyListener lsMod, int middle, int margin, Group wUpLoadFile) {
     // UploadFile line
     wlUploadFile = new Label(wUpLoadFile, SWT.RIGHT);
     wlUploadFile.setText(BaseMessages.getString(PKG, 
"ActionHTTP.UploadFile.Label"));
     PropsUi.setLook(wlUploadFile);
     FormData fdlUploadFile = new FormData();
     fdlUploadFile.left = new FormAttachment(0, 0);
-    fdlUploadFile.top = new FormAttachment(wAuthentication, margin);
+    fdlUploadFile.top = new FormAttachment(0, margin);
     fdlUploadFile.right = new FormAttachment(middle, -margin);
     wlUploadFile.setLayoutData(fdlUploadFile);
 
@@ -481,7 +517,7 @@ public class ActionHttpDialog extends ActionDialog {
     wbUploadFile.setText(BaseMessages.getString(PKG, "System.Button.Browse"));
     FormData fdbUploadFile = new FormData();
     fdbUploadFile.right = new FormAttachment(100, 0);
-    fdbUploadFile.top = new FormAttachment(wAuthentication, margin);
+    fdbUploadFile.top = new FormAttachment(0, margin);
     wbUploadFile.setLayoutData(fdbUploadFile);
 
     wUploadFile = new TextVar(variables, wUpLoadFile, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
@@ -490,7 +526,7 @@ public class ActionHttpDialog extends ActionDialog {
     wUploadFile.addModifyListener(lsMod);
     FormData fdUploadFile = new FormData();
     fdUploadFile.left = new FormAttachment(middle, 0);
-    fdUploadFile.top = new FormAttachment(wAuthentication, margin);
+    fdUploadFile.top = new FormAttachment(0, margin);
     fdUploadFile.right = new FormAttachment(wbUploadFile, -margin);
     wUploadFile.setLayoutData(fdUploadFile);
 
@@ -614,7 +650,7 @@ public class ActionHttpDialog extends ActionDialog {
     PropsUi.setLook(wlUserName);
     FormData fdlUserName = new FormData();
     fdlUserName.left = new FormAttachment(0, 0);
-    fdlUserName.top = new FormAttachment(wFieldTarget, margin);
+    fdlUserName.top = new FormAttachment(0, margin);
     fdlUserName.right = new FormAttachment(middle, -margin);
     wlUserName.setLayoutData(fdlUserName);
     wUserName = new TextVar(variables, wAuthentication, SWT.SINGLE | SWT.LEFT 
| SWT.BORDER);
@@ -623,7 +659,7 @@ public class ActionHttpDialog extends ActionDialog {
     wUserName.addModifyListener(lsMod);
     FormData fdUserName = new FormData();
     fdUserName.left = new FormAttachment(middle, 0);
-    fdUserName.top = new FormAttachment(wFieldTarget, margin);
+    fdUserName.top = new FormAttachment(0, margin);
     fdUserName.right = new FormAttachment(100, 0);
     wUserName.setLayoutData(fdUserName);
   }
@@ -867,6 +903,7 @@ public class ActionHttpDialog extends ActionDialog {
     }
 
     wAddFilenameToResult.setSelection(action.isAddFilenameToResult());
+    wReplyVariable.setText(Const.NVL(action.getReplyVariableName(), ""));
     setFlags();
   }
 
@@ -905,6 +942,7 @@ public class ActionHttpDialog extends ActionDialog {
     action.setDateTimeAdded(wRunEveryRow.getSelection() ? false : 
wDateTimeAdded.getSelection());
     action.setTargetFilenameExtension(wRunEveryRow.getSelection() ? "" : 
wTargetExt.getText());
     action.setAddFilenameToResult(wAddFilenameToResult.getSelection());
+    action.setReplyVariableName(wReplyVariable.getText());
     List<ActionHttp.Header> headers = new ArrayList<>();
     for (int i = 0; i < wHeaders.nrNonEmpty(); i++) {
       String varname = wHeaders.getNonEmpty(i).getText(1);
diff --git 
a/plugins/actions/http/src/main/resources/org/apache/hop/workflow/actions/http/messages/messages_en_US.properties
 
b/plugins/actions/http/src/main/resources/org/apache/hop/workflow/actions/http/messages/messages_en_US.properties
index 7f5e932975..3477545e68 100644
--- 
a/plugins/actions/http/src/main/resources/org/apache/hop/workflow/actions/http/messages/messages_en_US.properties
+++ 
b/plugins/actions/http/src/main/resources/org/apache/hop/workflow/actions/http/messages/messages_en_US.properties
@@ -42,6 +42,7 @@ ActionHTTP.Log.FinishedSendingFile=Finished sending content 
to server.
 ActionHTTP.Log.HeaderSet=Header set: {0} = {1}
 ActionHTTP.Log.HeadersProvided=HTTP Headers have been provided
 ActionHTTP.Log.ReplayInfo=Resource type: {0}, last modified on: {1}.
+ActionHTTP.Log.ReplyStoredInVariable=HTTP reply stored in variable [{0}]
 ActionHTTP.Log.SendingFile=Start sending content of file [{0}] to server.
 ActionHTTP.Log.StartReadingReply=Start reading reply from webserver.
 ActionHTTP.Name=HTTP
@@ -53,11 +54,16 @@ ActionHTTP.ProxyIgnoreRegexp.Label=Ignore proxy for hosts
 ActionHTTP.ProxyIgnoreRegexp.Tooltip=Specify the hosts for which to ignore the 
proxy settings. This is a list of regular expressions, separated by character 
'|'
 ActionHTTP.ProxyPort.Label=Proxy port
 ActionHTTP.ProxyPort.Tooltip=Proxy port, if a proxy server exists. Standard is 
8080 - this value is used if a proxy server is specified but no proxy port
+ActionHTTP.ReplyVariable.Label=Result variable
+ActionHTTP.ReplyVariable.Tooltip=If specified, the HTTP reply is stored in 
this workflow variable
 ActionHTTP.RunForEveryRow.Label=Run for every result row
 ActionHTTP.RunForEveryRow.Tooltip=If checked, this action is executed for each 
incoming row.
 ActionHTTP.StartAction=Start of HTTP action.
+ActionHTTP.Tab.Authentication.Label=Authentication
 ActionHTTP.Tab.General.Label=General
 ActionHTTP.Tab.Headers.Label=Headers
+ActionHTTP.Tab.Reply.Label=Reply
+ActionHTTP.Tab.Upload.Label=Upload
 ActionHTTP.TargetFile.Label=Target file
 ActionHTTP.TargetFile.Tooltip=Target file which contains the downloaded 
contents
 ActionHTTP.TargetFileAppend.Label=Append to specified target file
diff --git 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpExecutionTest.java
 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpExecutionTest.java
index 7c64cf3101..fde34bea42 100644
--- 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpExecutionTest.java
+++ 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpExecutionTest.java
@@ -20,6 +20,7 @@ package org.apache.hop.workflow.actions.http;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import com.sun.net.httpserver.Headers;
@@ -165,6 +166,51 @@ class ActionHttpExecutionTest {
     assertEquals(payload.getBytes(UTF_8).length, 
result.getBytesWrittenThisAction());
   }
 
+  @Test
+  void testHttpConfiguredSingleRunStoresReplyInVariable() throws Exception {
+    String payload = "download only payload";
+    File tempFileForDownload = File.createTempFile("downloadedFileVar", 
".tmp");
+    tempFileForDownload.deleteOnExit();
+
+    LocalWorkflowEngine workflow = new LocalWorkflowEngine();
+    ActionHttp http = new ActionHttp();
+    http.setParentWorkflow(workflow);
+    http.setRunForEveryRow(false);
+    http.setAddFilenameToResult(false);
+    http.setUrl(httpBaseUrl() + "/downloadFile");
+    http.setTargetFilename(tempFileForDownload.getCanonicalPath());
+    http.setReplyVariableName("HTTP_REPLY");
+
+    Result result = http.execute(new Result(), 0);
+
+    assertTrue(result.getResult());
+    assertEquals(0, result.getNrErrors());
+    assertEquals(payload, FileUtils.readFileToString(tempFileForDownload, 
UTF_8));
+    assertEquals(payload, http.getVariable("HTTP_REPLY"));
+    assertEquals(payload, workflow.getVariable("HTTP_REPLY"));
+  }
+
+  @Test
+  void testHttpConfiguredSingleRunDoesNotStoreReplyWhenVariableUnset() throws 
Exception {
+    File tempFileForDownload = File.createTempFile("downloadedFileNoVar", 
".tmp");
+    tempFileForDownload.deleteOnExit();
+
+    LocalWorkflowEngine workflow = new LocalWorkflowEngine();
+    ActionHttp http = new ActionHttp();
+    http.setParentWorkflow(workflow);
+    http.setRunForEveryRow(false);
+    http.setAddFilenameToResult(false);
+    http.setUrl(httpBaseUrl() + "/downloadFile");
+    http.setTargetFilename(tempFileForDownload.getCanonicalPath());
+
+    Result result = http.execute(new Result(), 0);
+
+    assertTrue(result.getResult());
+    assertEquals(0, result.getNrErrors());
+    assertNull(http.getVariable("HTTP_REPLY"));
+    assertNull(workflow.getVariable("HTTP_REPLY"));
+  }
+
   private static void startHttpServer() throws IOException {
     httpServer = HttpServer.create(new 
InetSocketAddress(ActionHttpExecutionTest.HTTP_HOST, 0), 10);
     httpServer.createContext(
diff --git 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpLoadSaveTest.java
 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpLoadSaveTest.java
index dc928bd990..2ae55cd2db 100644
--- 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpLoadSaveTest.java
+++ 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpLoadSaveTest.java
@@ -56,6 +56,7 @@ class ActionHttpLoadSaveTest {
     assertEquals(2, meta.getHeaders().size());
     assertTrue(meta.isIgnoreSsl());
     assertFalse(meta.isRunForEveryRow());
+    assertEquals("HTTP_REPLY", meta.getReplyVariableName());
   }
 
   @Test
@@ -74,5 +75,6 @@ class ActionHttpLoadSaveTest {
     assertEquals(clone.getHeaders().size(), meta.getHeaders().size());
     assertEquals(clone.isIgnoreSsl(), meta.isIgnoreSsl());
     assertEquals(clone.isRunForEveryRow(), meta.isRunForEveryRow());
+    assertEquals(clone.getReplyVariableName(), meta.getReplyVariableName());
   }
 }
diff --git 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpTest.java
 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpTest.java
index 489dadb6dc..992980baf8 100644
--- 
a/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpTest.java
+++ 
b/plugins/actions/http/src/test/java/org/apache/hop/workflow/actions/http/ActionHttpTest.java
@@ -18,6 +18,7 @@ package org.apache.hop.workflow.actions.http;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.nio.charset.StandardCharsets;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.encryption.Encr;
 import org.apache.hop.core.encryption.TwoWayPasswordEncoderPluginType;
@@ -51,4 +52,18 @@ class ActionHttpTest {
     actionHttp.setTargetFilenameExtension("zip");
     assertEquals("zip", actionHttp.getTargetFilenameExtension());
   }
+
+  @Test
+  void testCharsetFromContentType() {
+    assertEquals(StandardCharsets.UTF_8, 
ActionHttp.charsetFromContentType(null));
+    assertEquals(StandardCharsets.UTF_8, 
ActionHttp.charsetFromContentType("text/plain"));
+    assertEquals(
+        StandardCharsets.ISO_8859_1,
+        ActionHttp.charsetFromContentType("text/html; charset=ISO-8859-1"));
+    assertEquals(
+        StandardCharsets.UTF_8,
+        ActionHttp.charsetFromContentType("application/json; 
charset=\"UTF-8\""));
+    assertEquals(
+        StandardCharsets.UTF_8, ActionHttp.charsetFromContentType("text/plain; 
charset=no-such"));
+  }
 }
diff --git a/plugins/actions/http/src/test/resources/http-action.xml 
b/plugins/actions/http/src/test/resources/http-action.xml
index cfdd7373e5..5e1228cc99 100644
--- a/plugins/actions/http/src/test/resources/http-action.xml
+++ b/plugins/actions/http/src/test/resources/http-action.xml
@@ -38,6 +38,7 @@
     <proxy_port>8080</proxy_port>
     <non_proxy_hosts/>
     <addfilenameresult>Y</addfilenameresult>
+    <reply_variable>HTTP_REPLY</reply_variable>
     <headers>
         <header>
             <header_name>accept</header_name>

Reply via email to