mirror of https://github.com/langgenius/dify.git
improve webhook request headers
This commit is contained in:
parent
b855d95430
commit
5b2f323a87
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue