fix: content type of webhook (#25032)

This commit is contained in:
非法操作 2025-09-03 15:13:01 +08:00 committed by GitHub
parent 5ce7b2d98d
commit 7120c6414c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 111 additions and 20 deletions

View File

@ -7,7 +7,7 @@ from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.base.entities import BaseNodeData, RetryConfig
from core.workflow.nodes.enums import ErrorStrategy, NodeType
from .entities import WebhookData
from .entities import ContentType, WebhookData
class TriggerWebhookNode(BaseNode):
@ -104,6 +104,11 @@ class TriggerWebhookNode(BaseNode):
param_name = body_param.name
param_type = body_param.type
if self._node_data.content_type == ContentType.TEXT:
# For text/plain, the entire body is a single string parameter
outputs[param_name] = str(webhook_data.get("body", {}).get("raw", ""))
continue
if param_type == "file":
# Get File object (already processed by webhook controller)
file_obj = webhook_data.get("files", {}).get(param_name)

View File

@ -1,3 +1,4 @@
import json
import logging
from collections.abc import Mapping
from typing import Any
@ -157,11 +158,21 @@ class WebhookService:
"error": f"HTTP method mismatch. Expected {configured_method}, got {request_method}",
}
# Validate Content-type
configured_content_type = node_data.get("content_type", "application/json").lower()
request_content_type = webhook_data["headers"].get("Content-Type", "").lower()
if not request_content_type:
request_content_type = webhook_data["headers"].get("content-type", "application/json").lower()
if configured_content_type != request_content_type:
return {
"valid": False,
"error": f"Content-type mismatch. Expected {configured_content_type}, got {request_content_type}",
}
# Validate required headers (case-insensitive)
headers = node_data.get("headers", [])
# Create case-insensitive header lookup
webhook_headers_lower = {k.lower(): v for k, v in webhook_data["headers"].items()}
for header in headers:
if header.get("required", False):
header_name = header.get("name", "")
@ -175,22 +186,30 @@ class WebhookService:
param_name = param.get("name", "")
if param_name not in webhook_data["query_params"]:
return {"valid": False, "error": f"Required query parameter missing: {param_name}"}
if configured_content_type == "text/plain":
# For text/plain, just validate that we have a body if any body params are configured as required
body_params = node_data.get("body", [])
if body_params and any(param.get("required", False) for param in body_params):
body_data = webhook_data.get("body", {})
raw_content = body_data.get("raw", "")
if not raw_content or not isinstance(raw_content, str):
return {"valid": False, "error": "Required body content missing for text/plain request"}
else:
body_params = node_data.get("body", [])
for body_param in body_params:
if body_param.get("required", False):
param_name = body_param.get("name", "")
param_type = body_param.get("type", "string")
# Validate required body parameters
body_params = node_data.get("body", [])
for body_param in body_params:
if body_param.get("required", False):
param_name = body_param.get("name", "")
param_type = body_param.get("type", "string")
# Check if parameter exists
if param_type == "file":
file_obj = webhook_data.get("files", {}).get(param_name)
if not file_obj:
return {"valid": False, "error": f"Required file parameter missing: {param_name}"}
else:
if param_name not in webhook_data.get("body", {}):
return {"valid": False, "error": f"Required body parameter missing: {param_name}"}
# Check if parameter exists
if param_type == "file":
file_obj = webhook_data.get("files", {}).get(param_name)
if not file_obj:
return {"valid": False, "error": f"Required file parameter missing: {param_name}"}
else:
if param_name not in webhook_data.get("body", {}):
return {"valid": False, "error": f"Required body parameter missing: {param_name}"}
return {"valid": True}
@ -253,8 +272,6 @@ class WebhookService:
@classmethod
def generate_webhook_response(cls, node_config: Mapping[str, Any]) -> tuple[dict[str, Any], int]:
"""Generate HTTP response based on node configuration."""
import json
node_data = node_config.get("data", {})
# Get configured status code and response body

View File

@ -204,6 +204,75 @@ class TestWebhookServiceUnit:
assert result["valid"] is False
assert "Required file parameter missing: upload" in result["error"]
def test_validate_webhook_request_text_plain_with_required_body(self):
"""Test webhook validation for text/plain content type with required body content."""
# Test case 1: text/plain with raw content - should pass
webhook_data = {
"method": "POST",
"headers": {"content-type": "text/plain"},
"query_params": {},
"body": {"raw": "Hello World"},
"files": {}
}
node_config = {
"data": {
"method": "post",
"content_type": "text/plain",
"body": [{"name": "message", "type": "string", "required": True}]
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is True
# Test case 2: text/plain without raw content but required - should fail
webhook_data_no_body = {
"method": "POST",
"headers": {"content-type": "text/plain"},
"query_params": {},
"body": {},
"files": {}
}
result = WebhookService.validate_webhook_request(webhook_data_no_body, node_config)
assert result["valid"] is False
assert "Required body content missing for text/plain request" in result["error"]
# Test case 3: text/plain with empty raw content but required - should fail
webhook_data_empty_body = {
"method": "POST",
"headers": {"content-type": "text/plain"},
"query_params": {},
"body": {"raw": ""},
"files": {}
}
result = WebhookService.validate_webhook_request(webhook_data_empty_body, node_config)
assert result["valid"] is False
assert "Required body content missing for text/plain request" in result["error"]
def test_validate_webhook_request_text_plain_no_body_params(self):
"""Test webhook validation for text/plain content type with no body params configured."""
webhook_data = {
"method": "POST",
"headers": {"content-type": "text/plain"},
"query_params": {},
"body": {"raw": "Hello World"},
"files": {}
}
node_config = {
"data": {
"method": "post",
"content_type": "text/plain",
"body": [] # No body params configured
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is True
def test_validate_webhook_request_validation_exception(self):
"""Test webhook validation with exception handling."""
webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}}
@ -214,7 +283,7 @@ class TestWebhookServiceUnit:
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is False
assert "Validation failed:" in result["error"]
assert "Validation failed" in result["error"]
def test_generate_webhook_response_default(self):
"""Test webhook response generation with default values."""