From 15048a1514fdf11ad162cdc7cf28a7b5a8659a76 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 3 Apr 2026 12:05:13 +0300 Subject: [PATCH] Fix PR review comments: add body template JSON error handling, semaphore leak fix, and upgrade --- .../thingsboard/rule/engine/rest/TbHttpClient.java | 14 +++++++++++++- .../rule/engine/rest/TbRestApiCallNode.java | 7 ++++++- .../rule/engine/rest/TbRestApiCallNodeTest.java | 7 ++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 4a855f1579..fefda74666 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -266,6 +266,11 @@ public class TbHttpClient { }); } catch (InterruptedException e) { log.warn("Timeout during waiting for reply!", e); + } catch (Exception e) { + if (semaphore != null) { + semaphore.release(); + } + onFailure.accept(msg, e); } } @@ -342,7 +347,14 @@ public class TbHttpClient { if (StringUtils.isNotBlank(config.getRequestBodyTemplate())) { boolean escapeJson = !config.isParseToPlainText(); String processedTemplate = TbNodeUtils.processPattern(config.getRequestBodyTemplate(), msg, escapeJson); - return config.isParseToPlainText() ? processedTemplate : JacksonUtil.toJsonNode(processedTemplate); + if (config.isParseToPlainText()) { + return processedTemplate; + } + try { + return JacksonUtil.toJsonNode(processedTemplate); + } catch (Exception e) { + throw new RuntimeException("Request body template produced invalid JSON: " + processedTemplate, e); + } } return getData(msg, config.isParseToPlainText()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index 50f4ef8d9d..1172fd8749 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -58,6 +58,7 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { static final String PARSE_TO_PLAIN_TEXT = "parseToPlainText"; static final String MAX_IN_MEMORY_BUFFER_SIZE_IN_KB = "maxInMemoryBufferSizeInKb"; + static final String REQUEST_BODY_TEMPLATE = "requestBodyTemplate"; static final String TRIM_DOUBLE_QUOTES = "trimDoubleQuotes"; protected TbHttpClient httpClient; @@ -112,12 +113,16 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { ((ObjectNode) oldConfiguration).put(MAX_IN_MEMORY_BUFFER_SIZE_IN_KB, 256); } case 3: + if (!oldConfiguration.has(REQUEST_BODY_TEMPLATE)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).putNull(REQUEST_BODY_TEMPLATE); + } Set knownProperties = Set.of( "restEndpointUrlPattern", "requestMethod", "headers", "readTimeoutMs", "maxParallelRequestsCount", "parseToPlainText", "enableProxy", "useSystemProxyProperties", "proxyHost", "proxyPort", "proxyUser", "proxyPassword", "credentials", "ignoreRequestBody", - "maxInMemoryBufferSizeInKb" + "requestBodyTemplate", "maxInMemoryBufferSizeInKb" ); Iterator fieldNames = oldConfiguration.fieldNames(); while (fieldNames.hasNext()) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 5299f9160a..e8e63ee2a8 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -463,7 +463,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(1, "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + @@ -479,7 +479,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(2, "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + @@ -494,7 +494,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(3, """ { "restEndpointUrlPattern": "http://localhost/api", @@ -543,6 +543,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "credentials": { "type": "anonymous" }, + "requestBodyTemplate": null, "maxInMemoryBufferSizeInKb": 256 }""") );