mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-13 01:29:01 +08:00
fix: change http node params from dict to list tuple (#11665)
This commit is contained in:
parent
cf0ff88120
commit
9c7a1bc067
@ -37,7 +37,7 @@ BODY_TYPE_TO_CONTENT_TYPE = {
|
||||
class Executor:
|
||||
method: Literal["get", "head", "post", "put", "delete", "patch"]
|
||||
url: str
|
||||
params: Mapping[str, str] | None
|
||||
params: list[tuple[str, str]] | None
|
||||
content: str | bytes | None
|
||||
data: Mapping[str, Any] | None
|
||||
files: Mapping[str, tuple[str | None, bytes, str]] | None
|
||||
@ -67,7 +67,7 @@ class Executor:
|
||||
self.method = node_data.method
|
||||
self.auth = node_data.authorization
|
||||
self.timeout = timeout
|
||||
self.params = {}
|
||||
self.params = []
|
||||
self.headers = {}
|
||||
self.content = None
|
||||
self.files = None
|
||||
@ -89,14 +89,48 @@ class Executor:
|
||||
self.url = self.variable_pool.convert_template(self.node_data.url).text
|
||||
|
||||
def _init_params(self):
|
||||
params = _plain_text_to_dict(self.node_data.params)
|
||||
for key in params:
|
||||
params[key] = self.variable_pool.convert_template(params[key]).text
|
||||
self.params = params
|
||||
"""
|
||||
Almost same as _init_headers(), difference:
|
||||
1. response a list tuple to support same key, like 'aa=1&aa=2'
|
||||
2. param value may have '\n', we need to splitlines then extract the variable value.
|
||||
"""
|
||||
result = []
|
||||
for line in self.node_data.params.splitlines():
|
||||
if not (line := line.strip()):
|
||||
continue
|
||||
|
||||
key, *value = line.split(":", 1)
|
||||
if not (key := key.strip()):
|
||||
continue
|
||||
|
||||
value = value[0].strip() if value else ""
|
||||
result.append(
|
||||
(self.variable_pool.convert_template(key).text, self.variable_pool.convert_template(value).text)
|
||||
)
|
||||
|
||||
self.params = result
|
||||
|
||||
def _init_headers(self):
|
||||
"""
|
||||
Convert the header string of frontend to a dictionary.
|
||||
|
||||
Each line in the header string represents a key-value pair.
|
||||
Keys and values are separated by ':'.
|
||||
Empty values are allowed.
|
||||
|
||||
Examples:
|
||||
'aa:bb\n cc:dd' -> {'aa': 'bb', 'cc': 'dd'}
|
||||
'aa:\n cc:dd\n' -> {'aa': '', 'cc': 'dd'}
|
||||
'aa\n cc : dd' -> {'aa': '', 'cc': 'dd'}
|
||||
|
||||
"""
|
||||
headers = self.variable_pool.convert_template(self.node_data.headers).text
|
||||
self.headers = _plain_text_to_dict(headers)
|
||||
self.headers = {
|
||||
key.strip(): (value[0].strip() if value else "")
|
||||
for line in headers.splitlines()
|
||||
if line.strip()
|
||||
for key, *value in [line.split(":", 1)]
|
||||
}
|
||||
|
||||
def _init_body(self):
|
||||
body = self.node_data.body
|
||||
@ -288,33 +322,6 @@ class Executor:
|
||||
return raw
|
||||
|
||||
|
||||
def _plain_text_to_dict(text: str, /) -> dict[str, str]:
|
||||
"""
|
||||
Convert a string of key-value pairs to a dictionary.
|
||||
|
||||
Each line in the input string represents a key-value pair.
|
||||
Keys and values are separated by ':'.
|
||||
Empty values are allowed.
|
||||
|
||||
Examples:
|
||||
'aa:bb\n cc:dd' -> {'aa': 'bb', 'cc': 'dd'}
|
||||
'aa:\n cc:dd\n' -> {'aa': '', 'cc': 'dd'}
|
||||
'aa\n cc : dd' -> {'aa': '', 'cc': 'dd'}
|
||||
|
||||
Args:
|
||||
convert_text (str): The input string to convert.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: A dictionary of key-value pairs.
|
||||
"""
|
||||
return {
|
||||
key.strip(): (value[0].strip() if value else "")
|
||||
for line in text.splitlines()
|
||||
if line.strip()
|
||||
for key, *value in [line.split(":", 1)]
|
||||
}
|
||||
|
||||
|
||||
def _generate_random_string(n: int) -> str:
|
||||
"""
|
||||
Generate a random string of lowercase ASCII letters.
|
||||
|
@ -48,7 +48,7 @@ def test_executor_with_json_body_and_number_variable():
|
||||
assert executor.method == "post"
|
||||
assert executor.url == "https://api.example.com/data"
|
||||
assert executor.headers == {"Content-Type": "application/json"}
|
||||
assert executor.params == {}
|
||||
assert executor.params == []
|
||||
assert executor.json == {"number": 42}
|
||||
assert executor.data is None
|
||||
assert executor.files is None
|
||||
@ -101,7 +101,7 @@ def test_executor_with_json_body_and_object_variable():
|
||||
assert executor.method == "post"
|
||||
assert executor.url == "https://api.example.com/data"
|
||||
assert executor.headers == {"Content-Type": "application/json"}
|
||||
assert executor.params == {}
|
||||
assert executor.params == []
|
||||
assert executor.json == {"name": "John Doe", "age": 30, "email": "john@example.com"}
|
||||
assert executor.data is None
|
||||
assert executor.files is None
|
||||
@ -156,7 +156,7 @@ def test_executor_with_json_body_and_nested_object_variable():
|
||||
assert executor.method == "post"
|
||||
assert executor.url == "https://api.example.com/data"
|
||||
assert executor.headers == {"Content-Type": "application/json"}
|
||||
assert executor.params == {}
|
||||
assert executor.params == []
|
||||
assert executor.json == {"object": {"name": "John Doe", "age": 30, "email": "john@example.com"}}
|
||||
assert executor.data is None
|
||||
assert executor.files is None
|
||||
@ -195,7 +195,7 @@ def test_extract_selectors_from_template_with_newline():
|
||||
variable_pool=variable_pool,
|
||||
)
|
||||
|
||||
assert executor.params == {"test": "line1\nline2"}
|
||||
assert executor.params == [("test", "line1\nline2")]
|
||||
|
||||
|
||||
def test_executor_with_form_data():
|
||||
@ -244,7 +244,7 @@ def test_executor_with_form_data():
|
||||
assert executor.url == "https://api.example.com/upload"
|
||||
assert "Content-Type" in executor.headers
|
||||
assert "multipart/form-data" in executor.headers["Content-Type"]
|
||||
assert executor.params == {}
|
||||
assert executor.params == []
|
||||
assert executor.json is None
|
||||
assert executor.files is None
|
||||
assert executor.content is None
|
||||
@ -265,3 +265,72 @@ def test_executor_with_form_data():
|
||||
assert "Hello, World!" in raw_request
|
||||
assert "number_field" in raw_request
|
||||
assert "42" in raw_request
|
||||
|
||||
|
||||
def test_init_headers():
|
||||
def create_executor(headers: str) -> Executor:
|
||||
node_data = HttpRequestNodeData(
|
||||
title="test",
|
||||
method="get",
|
||||
url="http://example.com",
|
||||
headers=headers,
|
||||
params="",
|
||||
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||
)
|
||||
timeout = HttpRequestNodeTimeout(connect=10, read=30, write=30)
|
||||
return Executor(node_data=node_data, timeout=timeout, variable_pool=VariablePool())
|
||||
|
||||
executor = create_executor("aa\n cc:")
|
||||
executor._init_headers()
|
||||
assert executor.headers == {"aa": "", "cc": ""}
|
||||
|
||||
executor = create_executor("aa:bb\n cc:dd")
|
||||
executor._init_headers()
|
||||
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||
|
||||
executor = create_executor("aa:bb\n cc:dd\n")
|
||||
executor._init_headers()
|
||||
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||
|
||||
executor = create_executor("aa:bb\n\n cc : dd\n\n")
|
||||
executor._init_headers()
|
||||
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||
|
||||
|
||||
def test_init_params():
|
||||
def create_executor(params: str) -> Executor:
|
||||
node_data = HttpRequestNodeData(
|
||||
title="test",
|
||||
method="get",
|
||||
url="http://example.com",
|
||||
headers="",
|
||||
params=params,
|
||||
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||
)
|
||||
timeout = HttpRequestNodeTimeout(connect=10, read=30, write=30)
|
||||
return Executor(node_data=node_data, timeout=timeout, variable_pool=VariablePool())
|
||||
|
||||
# Test basic key-value pairs
|
||||
executor = create_executor("key1:value1\nkey2:value2")
|
||||
executor._init_params()
|
||||
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||
|
||||
# Test empty values
|
||||
executor = create_executor("key1:\nkey2:")
|
||||
executor._init_params()
|
||||
assert executor.params == [("key1", ""), ("key2", "")]
|
||||
|
||||
# Test duplicate keys (which is allowed for params)
|
||||
executor = create_executor("key1:value1\nkey1:value2")
|
||||
executor._init_params()
|
||||
assert executor.params == [("key1", "value1"), ("key1", "value2")]
|
||||
|
||||
# Test whitespace handling
|
||||
executor = create_executor(" key1 : value1 \n key2 : value2 ")
|
||||
executor._init_params()
|
||||
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||
|
||||
# Test empty lines and extra whitespace
|
||||
executor = create_executor("key1:value1\n\nkey2:value2\n\n")
|
||||
executor._init_params()
|
||||
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||
|
@ -14,18 +14,10 @@ from core.workflow.nodes.http_request import (
|
||||
HttpRequestNodeBody,
|
||||
HttpRequestNodeData,
|
||||
)
|
||||
from core.workflow.nodes.http_request.executor import _plain_text_to_dict
|
||||
from models.enums import UserFrom
|
||||
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
|
||||
|
||||
|
||||
def test_plain_text_to_dict():
|
||||
assert _plain_text_to_dict("aa\n cc:") == {"aa": "", "cc": ""}
|
||||
assert _plain_text_to_dict("aa:bb\n cc:dd") == {"aa": "bb", "cc": "dd"}
|
||||
assert _plain_text_to_dict("aa:bb\n cc:dd\n") == {"aa": "bb", "cc": "dd"}
|
||||
assert _plain_text_to_dict("aa:bb\n\n cc : dd\n\n") == {"aa": "bb", "cc": "dd"}
|
||||
|
||||
|
||||
def test_http_request_node_binary_file(monkeypatch):
|
||||
data = HttpRequestNodeData(
|
||||
title="test",
|
||||
|
Loading…
x
Reference in New Issue
Block a user