mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 18:06:36 +08:00
fix: content type of webhook (#25032)
This commit is contained in:
parent
5ce7b2d98d
commit
7120c6414c
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user