Przeglądaj źródła

fix: honour webhook response from loop body nodes and harden controller parsing

- Record _webhookResponse from loop body node output in both the executed
  and cached body-node paths, using the same last-writer-wins-with-warning
  semantics as the main node walk.
- Guard the status field against non-integer types before reading it,
  logging and falling back to 500 instead of throwing.
- Log a warning when a header value is skipped for not being a string.
- Send {} instead of an empty string when body is absent and the content
  type is JSON, so an empty response is still valid JSON for strict clients.
fszontagh 1 miesiąc temu
rodzic
commit
1dfb78a6af

+ 17 - 0
src/runner/workflow_engine.cpp

@@ -1637,6 +1637,14 @@ bool WorkflowEngine::executeLoopBody(
                     iteration_results[body_node_id] = cached_result;
                     result.node_results[body_node_id + "_iter_" + std::to_string(i)] = cached_result;
 
+                    if (cached_result.output.contains("_webhookResponse")) {
+                        if (!result.webhook_response.is_null()) {
+                            LOG_WARN("Node {} in a loop body overrides a webhook response already set",
+                                     body_node_id);
+                        }
+                        result.webhook_response = cached_result.output["_webhookResponse"];
+                    }
+
                     if (callback) {
                         callback("loop.node.completed", {
                             {"executionId", result.execution_id},
@@ -1697,6 +1705,15 @@ bool WorkflowEngine::executeLoopBody(
             std::string result_key = body_node_id + "_iter_" + std::to_string(i);
             result.node_results[result_key] = body_result;
 
+            if (body_result.status == NodeStatus::Completed &&
+                body_result.output.contains("_webhookResponse")) {
+                if (!result.webhook_response.is_null()) {
+                    LOG_WARN("Node {} in a loop body overrides a webhook response already set",
+                             body_node_id);
+                }
+                result.webhook_response = body_result.output["_webhookResponse"];
+            }
+
             if (callback) {
                 nlohmann::json event_data = {
                     {"executionId", result.execution_id},

+ 15 - 1
src/webserver/api/webhook_controller.cpp

@@ -183,7 +183,16 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
         if (parsed_ok && parsed.is_object() && parsed.contains("_webhookResponse")) {
             const auto& spec = parsed["_webhookResponse"];
 
-            int status_code = spec.value("status", 200);
+            int status_code = 200;
+            if (spec.contains("status")) {
+                if (spec["status"].is_number_integer()) {
+                    status_code = spec["status"].get<int>();
+                } else {
+                    LOG_WARN("Webhook response status must be a whole number, got {}; sending 500",
+                             spec["status"].dump());
+                    status_code = 500;
+                }
+            }
             if (status_code < 100 || status_code > 599) {
                 LOG_WARN("Webhook response asked for status {}, which is not a valid HTTP status; sending 500",
                          status_code);
@@ -194,6 +203,7 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
             if (spec.contains("headers") && spec["headers"].is_object()) {
                 for (auto it = spec["headers"].begin(); it != spec["headers"].end(); ++it) {
                     if (!it.value().is_string()) {
+                        LOG_WARN("Webhook response header {} is not a string, skipping it", it.key());
                         continue;
                     }
                     // Content-Type reaches httplib through set_content rather
@@ -215,6 +225,10 @@ void WebhookController::handleWebhook(const httplib::Request& req, httplib::Resp
             if (spec.contains("body")) {
                 const auto& value = spec["body"];
                 body = value.is_string() ? value.get<std::string>() : value.dump();
+            } else if (content_type.find("json") != std::string::npos) {
+                // An empty body is not valid JSON, and a strict client would
+                // fail to parse it against the Content-Type we are sending.
+                body = "{}";
             }
 
             res.status = status_code;