improve webhook request headers

This commit is contained in:
hjlarry 2025-10-17 11:27:48 +08:00
parent b855d95430
commit 5b2f323a87
3 changed files with 43 additions and 16 deletions

View File

@ -85,15 +85,30 @@ class TriggerWebhookNode(Node):
# Get the raw webhook data (should be injected by Celery task)
webhook_data = webhook_inputs.get("webhook_data", {})
def _to_sanitized(name: str) -> str:
return name.replace("-", "_")
def _get_normalized(mapping: dict[str, Any], key: str) -> Any:
if not isinstance(mapping, dict):
return None
if key in mapping:
return mapping[key]
alternate = key.replace("-", "_") if "-" in key else key.replace("_", "-")
if alternate in mapping:
return mapping[alternate]
return None
# Extract configured headers (case-insensitive)
webhook_headers = webhook_data.get("headers", {})
webhook_headers_lower = {k.lower(): v for k, v in webhook_headers.items()}
for header in self._node_data.headers:
header_name = header.name
# Try exact match first, then case-insensitive match
value = webhook_headers.get(header_name) or webhook_headers_lower.get(header_name.lower())
outputs[header_name] = value
value = _get_normalized(webhook_headers, header_name)
if value is None:
value = _get_normalized(webhook_headers_lower, header_name.lower())
sanitized_name = _to_sanitized(header_name)
outputs[sanitized_name] = value
# Extract configured query parameters
for param in self._node_data.params:

View File

@ -35,6 +35,13 @@ class WebhookService:
__WEBHOOK_NODE_CACHE_KEY__ = "webhook_nodes"
MAX_WEBHOOK_NODES_PER_WORKFLOW = 5 # Maximum allowed webhook nodes per workflow
@staticmethod
def _sanitize_key(key: str) -> str:
"""Normalize external keys (headers/params) to workflow-safe variables."""
if not isinstance(key, str):
return key
return key.replace("-", "_")
@classmethod
def get_webhook_trigger_and_workflow(
cls, webhook_id: str, is_debug: bool = False
@ -590,10 +597,12 @@ class WebhookService:
ValueError: If required headers are missing
"""
headers_lower = {k.lower(): v for k, v in headers.items()}
headers_sanitized = {cls._sanitize_key(k).lower(): v for k, v in headers.items()}
for header_config in header_configs:
if header_config.get("required", False):
header_name = header_config.get("name", "")
if header_name.lower() not in headers_lower:
sanitized_name = cls._sanitize_key(header_name).lower()
if header_name.lower() not in headers_lower and sanitized_name not in headers_sanitized:
raise ValueError(f"Required header missing: {header_name}")
@classmethod

View File

@ -70,7 +70,12 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
if (!draft.variables)
draft.variables = []
const hasReservedConflict = newData.some(item => item.name === WEBHOOK_RAW_VARIABLE_NAME)
const sanitizedEntries = newData.map(item => ({
item,
sanitizedName: sourceType === 'header' ? item.name.replace(/-/g, '_') : item.name,
}))
const hasReservedConflict = sanitizedEntries.some(entry => entry.sanitizedName === WEBHOOK_RAW_VARIABLE_NAME)
if (hasReservedConflict) {
Toast.notify({
type: 'error',
@ -80,25 +85,24 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
})
return false
}
const existingOtherVarNames = new Set(
draft.variables
.filter(v => v.label !== sourceType && v.variable !== WEBHOOK_RAW_VARIABLE_NAME)
.map(v => v.variable),
)
const crossScopeConflict = newData.find(item => existingOtherVarNames.has(item.name))
const crossScopeConflict = sanitizedEntries.find(entry => existingOtherVarNames.has(entry.sanitizedName))
if (crossScopeConflict) {
Toast.notify({
type: 'error',
message: t('appDebug.varKeyError.keyAlreadyExists', {
key: crossScopeConflict.name,
key: crossScopeConflict.sanitizedName,
}),
})
return false
}
if(hasDuplicateStr(newData.map(item => item.name))) {
if(hasDuplicateStr(sanitizedEntries.map(entry => entry.sanitizedName))) {
Toast.notify({
type: 'error',
message: t('appDebug.varKeyError.keyAlreadyExists', {
@ -108,8 +112,8 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
return false
}
for (const item of newData) {
const { isValid, errorMessageKey } = checkKeys([item.name], false)
for (const { sanitizedName } of sanitizedEntries) {
const { isValid, errorMessageKey } = checkKeys([sanitizedName], false)
if (!isValid) {
Toast.notify({
type: 'error',
@ -122,7 +126,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
}
// Create set of new variable names for this source
const newVarNames = new Set(newData.map(item => item.name))
const newVarNames = new Set(sanitizedEntries.map(entry => entry.sanitizedName))
// Find variables from current source that will be deleted and clean up references
draft.variables
@ -141,9 +145,8 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
})
// Add or update variables
newData.forEach((item) => {
const varName = item.name
const existingVarIndex = draft.variables.findIndex(v => v.variable === varName)
sanitizedEntries.forEach(({ item, sanitizedName }) => {
const existingVarIndex = draft.variables.findIndex(v => v.variable === sanitizedName)
const inputVarType = 'type' in item
? item.type
@ -152,7 +155,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
const newVar: Variable = {
value_type: inputVarType,
label: sourceType, // Use sourceType as label to identify source
variable: varName,
variable: sanitizedName,
value_selector: [],
required: item.required,
}