Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
8b809b80
Commit
8b809b80
authored
Mar 09, 2024
by
Yeuoly
Committed by
takatost
Mar 10, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: http reqeust
parent
707a3a0a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
285 additions
and
3 deletions
+285
-3
ssrf_proxy.py
api/core/helper/ssrf_proxy.py
+4
-0
entities.py
api/core/workflow/nodes/http_request/entities.py
+3
-2
http_executor.py
api/core/workflow/nodes/http_request/http_executor.py
+240
-0
http_request_node.py
api/core/workflow/nodes/http_request/http_request_node.py
+38
-1
No files found.
api/core/helper/ssrf_proxy.py
View file @
8b809b80
...
@@ -38,6 +38,10 @@ def patch(url, *args, **kwargs):
...
@@ -38,6 +38,10 @@ def patch(url, *args, **kwargs):
return
_patch
(
url
=
url
,
*
args
,
proxies
=
httpx_proxies
,
**
kwargs
)
return
_patch
(
url
=
url
,
*
args
,
proxies
=
httpx_proxies
,
**
kwargs
)
def
delete
(
url
,
*
args
,
**
kwargs
):
def
delete
(
url
,
*
args
,
**
kwargs
):
if
'follow_redirects'
in
kwargs
:
if
kwargs
[
'follow_redirects'
]:
kwargs
[
'allow_redirects'
]
=
kwargs
[
'follow_redirects'
]
kwargs
.
pop
(
'follow_redirects'
)
return
_delete
(
url
=
url
,
*
args
,
proxies
=
requests_proxies
,
**
kwargs
)
return
_delete
(
url
=
url
,
*
args
,
proxies
=
requests_proxies
,
**
kwargs
)
def
head
(
url
,
*
args
,
**
kwargs
):
def
head
(
url
,
*
args
,
**
kwargs
):
...
...
api/core/workflow/nodes/http_request/entities.py
View file @
8b809b80
...
@@ -17,9 +17,10 @@ class HttpRequestNodeData(BaseNodeData):
...
@@ -17,9 +17,10 @@ class HttpRequestNodeData(BaseNodeData):
header
:
Union
[
None
,
str
]
header
:
Union
[
None
,
str
]
type
:
Literal
[
'no-auth'
,
'api-key'
]
type
:
Literal
[
'no-auth'
,
'api-key'
]
config
:
Config
class
Body
(
BaseModel
):
class
Body
(
BaseModel
):
type
:
Literal
[
None
,
'form-data'
,
'x-www-form-urlencoded'
,
'raw'
]
type
:
Literal
[
None
,
'form-data'
,
'x-www-form-urlencoded'
,
'raw'
,
'json'
]
data
:
Union
[
None
,
str
]
data
:
Union
[
None
,
str
]
variables
:
list
[
VariableSelector
]
variables
:
list
[
VariableSelector
]
...
@@ -28,4 +29,4 @@ class HttpRequestNodeData(BaseNodeData):
...
@@ -28,4 +29,4 @@ class HttpRequestNodeData(BaseNodeData):
authorization
:
Authorization
authorization
:
Authorization
headers
:
str
headers
:
str
params
:
str
params
:
str
body
:
Body
\ No newline at end of file
\ No newline at end of file
api/core/workflow/nodes/http_request/http_executor.py
0 → 100644
View file @
8b809b80
from
copy
import
deepcopy
from
typing
import
Any
,
Union
from
urllib.parse
import
urlencode
import
httpx
import
re
import
requests
import
core.helper.ssrf_proxy
as
ssrf_proxy
from
core.workflow.nodes.http_request.entities
import
HttpRequestNodeData
HTTP_REQUEST_DEFAULT_TIMEOUT
=
(
10
,
60
)
class
HttpExecutorResponse
:
status_code
:
int
headers
:
dict
[
str
,
str
]
body
:
str
def
__init__
(
self
,
status_code
:
int
,
headers
:
dict
[
str
,
str
],
body
:
str
):
"""
init
"""
self
.
status_code
=
status_code
self
.
headers
=
headers
self
.
body
=
body
class
HttpExecutor
:
server_url
:
str
method
:
str
authorization
:
HttpRequestNodeData
.
Authorization
params
:
dict
[
str
,
Any
]
headers
:
dict
[
str
,
Any
]
body
:
Union
[
None
,
str
]
files
:
Union
[
None
,
dict
[
str
,
Any
]]
def
__init__
(
self
,
node_data
:
HttpRequestNodeData
,
variables
:
dict
[
str
,
Any
]):
"""
init
"""
self
.
server_url
=
node_data
.
url
self
.
method
=
node_data
.
method
self
.
authorization
=
node_data
.
authorization
self
.
params
=
{}
self
.
headers
=
{}
self
.
body
=
None
# init template
self
.
_init_template
(
node_data
,
variables
)
def
_init_template
(
self
,
node_data
:
HttpRequestNodeData
,
variables
:
dict
[
str
,
Any
]):
"""
init template
"""
# extract all template in url
url_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
url
)
or
[]
url_template
=
list
(
set
(
url_template
))
original_url
=
node_data
.
url
for
url
in
url_template
:
if
not
url
:
continue
original_url
=
original_url
.
replace
(
f
'{{{{{url}}}}}'
,
str
(
variables
.
get
(
url
,
''
)))
self
.
server_url
=
original_url
# extract all template in params
param_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
params
)
or
[]
param_template
=
list
(
set
(
param_template
))
original_params
=
node_data
.
params
for
param
in
param_template
:
if
not
param
:
continue
original_params
=
original_params
.
replace
(
f
'{{{{{param}}}}}'
,
str
(
variables
.
get
(
param
,
''
)))
# fill in params
kv_paris
=
original_params
.
split
(
'
\n
'
)
for
kv
in
kv_paris
:
kv
=
kv
.
split
(
':'
)
if
len
(
kv
)
!=
2
:
raise
ValueError
(
f
'Invalid params {kv}'
)
k
,
v
=
kv
self
.
params
[
k
]
=
v
# extract all template in headers
header_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
headers
)
or
[]
header_template
=
list
(
set
(
header_template
))
original_headers
=
node_data
.
headers
for
header
in
header_template
:
if
not
header
:
continue
original_headers
=
original_headers
.
replace
(
f
'{{{{{header}}}}}'
,
str
(
variables
.
get
(
header
,
''
)))
# fill in headers
kv_paris
=
original_headers
.
split
(
'
\n
'
)
for
kv
in
kv_paris
:
kv
=
kv
.
split
(
':'
)
if
len
(
kv
)
!=
2
:
raise
ValueError
(
f
'Invalid headers {kv}'
)
k
,
v
=
kv
self
.
headers
[
k
]
=
v
# extract all template in body
body_template
=
re
.
findall
(
r'{{(.*?)}}'
,
node_data
.
body
.
data
or
''
)
or
[]
body_template
=
list
(
set
(
body_template
))
original_body
=
node_data
.
body
.
data
or
''
for
body
in
body_template
:
if
not
body
:
continue
original_body
=
original_body
.
replace
(
f
'{{{{{body}}}}}'
,
str
(
variables
.
get
(
body
,
''
)))
if
node_data
.
body
.
type
==
'json'
:
self
.
headers
[
'Content-Type'
]
=
'application/json'
elif
node_data
.
body
.
type
==
'x-www-form-urlencoded'
:
self
.
headers
[
'Content-Type'
]
=
'application/x-www-form-urlencoded'
# elif node_data.body.type == 'form-data':
# self.headers['Content-Type'] = 'multipart/form-data'
if
node_data
.
body
.
type
in
[
'form-data'
,
'x-www-form-urlencoded'
]:
body
=
{}
kv_paris
=
original_body
.
split
(
'
\n
'
)
for
kv
in
kv_paris
:
kv
=
kv
.
split
(
':'
)
if
len
(
kv
)
!=
2
:
raise
ValueError
(
f
'Invalid body {kv}'
)
body
[
kv
[
0
]]
=
kv
[
1
]
if
node_data
.
body
.
type
==
'form-data'
:
self
.
files
=
{
k
:
(
''
,
v
)
for
k
,
v
in
body
.
items
()
}
else
:
self
.
body
=
urlencode
(
body
)
else
:
self
.
body
=
original_body
def
_assembling_headers
(
self
)
->
dict
[
str
,
Any
]:
authorization
=
deepcopy
(
self
.
authorization
)
headers
=
deepcopy
(
self
.
headers
)
or
[]
if
self
.
authorization
.
type
==
'api-key'
:
if
self
.
authorization
.
config
.
api_key
is
None
:
raise
ValueError
(
'api_key is required'
)
if
not
self
.
authorization
.
config
.
header
:
authorization
.
config
.
header
=
'Authorization'
if
self
.
authorization
.
config
.
type
==
'bearer'
:
headers
[
authorization
.
config
.
header
]
=
f
'Bearer {authorization.config.api_key}'
elif
self
.
authorization
.
config
.
type
==
'basic'
:
headers
[
authorization
.
config
.
header
]
=
f
'Basic {authorization.config.api_key}'
elif
self
.
authorization
.
config
.
type
==
'custom'
:
headers
[
authorization
.
config
.
header
]
=
authorization
.
config
.
api_key
return
headers
def
_validate_and_parse_response
(
self
,
response
:
Union
[
httpx
.
Response
,
requests
.
Response
])
->
HttpExecutorResponse
:
"""
validate the response
"""
if
isinstance
(
response
,
httpx
.
Response
):
# get key-value pairs headers
headers
=
{}
for
k
,
v
in
response
.
headers
.
items
():
headers
[
k
]
=
v
return
HttpExecutorResponse
(
response
.
status_code
,
headers
,
response
.
text
)
elif
isinstance
(
response
,
requests
.
Response
):
# get key-value pairs headers
headers
=
{}
for
k
,
v
in
response
.
headers
.
items
():
headers
[
k
]
=
v
return
HttpExecutorResponse
(
response
.
status_code
,
headers
,
response
.
text
)
else
:
raise
ValueError
(
f
'Invalid response type {type(response)}'
)
def
_do_http_request
(
self
,
headers
:
dict
[
str
,
Any
])
->
httpx
.
Response
:
"""
do http request depending on api bundle
"""
# do http request
kwargs
=
{
'url'
:
self
.
server_url
,
'headers'
:
headers
,
'params'
:
self
.
params
,
'timeout'
:
HTTP_REQUEST_DEFAULT_TIMEOUT
,
'follow_redirects'
:
True
}
if
self
.
method
==
'get'
:
response
=
ssrf_proxy
.
get
(
**
kwargs
)
elif
self
.
method
==
'post'
:
response
=
ssrf_proxy
.
post
(
data
=
self
.
body
,
files
=
self
.
files
,
**
kwargs
)
elif
self
.
method
==
'put'
:
response
=
ssrf_proxy
.
put
(
data
=
self
.
body
,
files
=
self
.
files
,
**
kwargs
)
elif
self
.
method
==
'delete'
:
response
=
ssrf_proxy
.
delete
(
data
=
self
.
body
,
files
=
self
.
files
,
**
kwargs
)
elif
self
.
method
==
'patch'
:
response
=
ssrf_proxy
.
patch
(
data
=
self
.
body
,
files
=
self
.
files
,
**
kwargs
)
elif
self
.
method
==
'head'
:
response
=
ssrf_proxy
.
head
(
**
kwargs
)
elif
self
.
method
==
'options'
:
response
=
ssrf_proxy
.
options
(
**
kwargs
)
else
:
raise
ValueError
(
f
'Invalid http method {self.method}'
)
return
response
def
invoke
(
self
)
->
HttpExecutorResponse
:
"""
invoke http request
"""
# assemble headers
headers
=
self
.
_assembling_headers
()
# do http request
response
=
self
.
_do_http_request
(
headers
)
# validate response
return
self
.
_validate_and_parse_response
(
response
)
def
to_raw_request
(
self
)
->
str
:
"""
convert to raw request
"""
server_url
=
self
.
server_url
if
self
.
params
:
server_url
+=
f
'?{urlencode(self.params)}'
raw_request
=
f
'{self.method.upper()} {server_url} HTTP/1.1
\n
'
for
k
,
v
in
self
.
headers
.
items
():
raw_request
+=
f
'{k}: {v}
\n
'
raw_request
+=
'
\n
'
raw_request
+=
self
.
body
or
''
return
raw_request
\ No newline at end of file
api/core/workflow/nodes/http_request/http_request_node.py
View file @
8b809b80
from
os
import
error
from
typing
import
cast
from
core.workflow.entities.base_node_data_entities
import
BaseNodeData
from
core.workflow.entities.base_node_data_entities
import
BaseNodeData
from
core.workflow.entities.node_entities
import
NodeRunResult
,
NodeType
from
core.workflow.entities.node_entities
import
NodeRunResult
,
NodeType
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.entities.variable_pool
import
VariablePool
from
core.workflow.nodes.base_node
import
BaseNode
from
core.workflow.nodes.base_node
import
BaseNode
from
core.workflow.nodes.http_request.entities
import
HttpRequestNodeData
from
core.workflow.nodes.http_request.entities
import
HttpRequestNodeData
from
core.workflow.nodes.http_request.http_executor
import
HttpExecutor
from
models.workflow
import
WorkflowNodeExecutionStatus
class
HttpRequestNode
(
BaseNode
):
class
HttpRequestNode
(
BaseNode
):
_node_data_cls
=
HttpRequestNodeData
_node_data_cls
=
HttpRequestNodeData
node_type
=
NodeType
.
HTTP_REQUEST
node_type
=
NodeType
.
HTTP_REQUEST
def
_run
(
self
,
variable_pool
:
VariablePool
)
->
NodeRunResult
:
def
_run
(
self
,
variable_pool
:
VariablePool
)
->
NodeRunResult
:
pass
node_data
:
HttpRequestNodeData
=
cast
(
self
.
_node_data_cls
,
self
.
node_data
)
# extract variables
variables
=
{
variable_selector
.
variable
:
variable_pool
.
get_variable_value
(
variable_selector
=
variable_selector
.
value_selector
)
for
variable_selector
in
node_data
.
variables
}
# init http executor
try
:
http_executor
=
HttpExecutor
(
node_data
=
node_data
,
variables
=
variables
)
# invoke http executor
response
=
http_executor
.
invoke
()
except
Exception
as
e
:
return
NodeRunResult
(
status
=
WorkflowNodeExecutionStatus
.
FAILED
,
inputs
=
variables
,
error
=
str
(
e
),
process_data
=
http_executor
.
to_raw_request
()
)
return
NodeRunResult
(
status
=
WorkflowNodeExecutionStatus
.
SUCCEEDED
,
inputs
=
variables
,
outputs
=
{
'status_code'
:
response
.
status_code
,
'body'
:
response
,
'headers'
:
response
.
headers
},
process_data
=
http_executor
.
to_raw_request
()
)
@
classmethod
@
classmethod
def
_extract_variable_selector_to_variable_mapping
(
cls
,
node_data
:
BaseNodeData
)
->
dict
[
list
[
str
],
str
]:
def
_extract_variable_selector_to_variable_mapping
(
cls
,
node_data
:
BaseNodeData
)
->
dict
[
list
[
str
],
str
]:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment