新增 Rest Api Node,支持多种 HTTP 方法(GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)并支持重试设置。同时添加 String Replace Node 和 Retry Settings Node,优化 Key/Value Node 和 Post/Get Node 的功能。更新 README 文档以反映新功能和节点使用说明。
195 lines
7.4 KiB
Python
195 lines
7.4 KiB
Python
import requests
|
|
import json
|
|
import io
|
|
|
|
class RestApiNode:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"target_url": ("STRING", {"default": "https://example.com/api", "forceInput":True}),
|
|
"request_body": ("STRING", {"default": "{}", "multiline": True, "forceInput":True}),
|
|
"method": (["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], {"default": "GET"}),
|
|
},
|
|
"optional": {
|
|
"headers": ("KEY_VALUE", {"default": None}),
|
|
"RETRY_SETTING": ("RETRY_SETTING", {"default": None}),
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def HIDE_INPUTS(s, method):
|
|
hide_inputs = {}
|
|
|
|
if method in ["HEAD", "OPTIONS", "DELETE"]:
|
|
hide_inputs["request_body"] = True
|
|
|
|
return hide_inputs
|
|
|
|
RETURN_TYPES = ("STRING", "BYTES", "JSON", "DICT", "INT", "ANY")
|
|
RETURN_NAMES = ("text", "file", "json", "headers", "status_code", "any")
|
|
|
|
FUNCTION = "make_request"
|
|
|
|
CATEGORY = "RequestNode/REST API"
|
|
|
|
def make_request(self, target_url, method, request_body, headers=None, RETRY_SETTING=None):
|
|
|
|
request_headers = {'Content-Type': 'application/json'}
|
|
if headers:
|
|
request_headers.update(headers)
|
|
|
|
default_max_retry = 3
|
|
max_retry = default_max_retry
|
|
retry_until_status_code = 0
|
|
retry_until_not_status_code = 0
|
|
retry_interval = 1000
|
|
retry_non_2xx = False
|
|
|
|
if RETRY_SETTING is not None:
|
|
if "max_retry" in RETRY_SETTING:
|
|
max_retry = RETRY_SETTING["max_retry"]
|
|
retry_until_status_code = RETRY_SETTING.get("retry_until_status_code", 0)
|
|
retry_until_not_status_code = RETRY_SETTING.get("retry_until_not_status_code", 0)
|
|
retry_interval = RETRY_SETTING.get("retry_interval", 1000)
|
|
|
|
if "max_retry" in RETRY_SETTING and not any(key in RETRY_SETTING for key in ["retry_until_status_code", "retry_until_not_status_code"]):
|
|
retry_non_2xx = True
|
|
|
|
params = {}
|
|
body_data = None
|
|
|
|
if method in ["POST", "PUT", "PATCH"]:
|
|
try:
|
|
body_data = json.loads(request_body)
|
|
except json.JSONDecodeError:
|
|
body_data = {"error": "Invalid JSON in request_body"}
|
|
elif method == "GET":
|
|
try:
|
|
params_data = json.loads(request_body)
|
|
if isinstance(params_data, dict):
|
|
params = params_data
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
def send_request():
|
|
if method == "GET":
|
|
return requests.get(target_url, params=params, headers=request_headers)
|
|
elif method == "POST":
|
|
return requests.post(target_url, json=body_data, headers=request_headers)
|
|
elif method == "PUT":
|
|
return requests.put(target_url, json=body_data, headers=request_headers)
|
|
elif method == "DELETE":
|
|
return requests.delete(target_url, headers=request_headers)
|
|
elif method == "PATCH":
|
|
return requests.patch(target_url, json=body_data, headers=request_headers)
|
|
elif method == "HEAD":
|
|
return requests.head(target_url, headers=request_headers)
|
|
elif method == "OPTIONS":
|
|
return requests.options(target_url, headers=request_headers)
|
|
else:
|
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
|
|
max_attempts = 1
|
|
attempts = 0
|
|
last_exception = None
|
|
|
|
has_specific_retry_conditions = (retry_until_status_code > 0 or retry_until_not_status_code > 0 or retry_non_2xx)
|
|
|
|
if has_specific_retry_conditions and max_retry == 0:
|
|
import sys
|
|
max_attempts = sys.maxsize
|
|
elif max_retry > 0:
|
|
max_attempts = max_retry + 1
|
|
else:
|
|
max_attempts = 1
|
|
|
|
try:
|
|
while attempts < max_attempts:
|
|
try:
|
|
attempts += 1
|
|
response = send_request()
|
|
|
|
if retry_until_status_code > 0 and response.status_code != retry_until_status_code:
|
|
if attempts < max_attempts:
|
|
import time
|
|
time.sleep(retry_interval / 1000)
|
|
continue
|
|
elif retry_until_not_status_code > 0 and response.status_code == retry_until_not_status_code:
|
|
if attempts < max_attempts:
|
|
import time
|
|
time.sleep(retry_interval / 1000)
|
|
continue
|
|
elif retry_non_2xx and (response.status_code < 200 or response.status_code >= 300):
|
|
if attempts < max_attempts:
|
|
import time
|
|
time.sleep(retry_interval / 1000)
|
|
continue
|
|
|
|
break
|
|
|
|
except Exception as e:
|
|
last_exception = e
|
|
if attempts < max_attempts:
|
|
import time
|
|
time.sleep(retry_interval / 1000)
|
|
else:
|
|
raise
|
|
|
|
text_output = response.text
|
|
|
|
if method == "HEAD":
|
|
file_output = io.BytesIO(b"")
|
|
any_output = b""
|
|
else:
|
|
file_output = io.BytesIO(response.content)
|
|
any_output = response.content
|
|
|
|
try:
|
|
if method == "HEAD":
|
|
json_output = dict(response.headers)
|
|
else:
|
|
json_output = response.json()
|
|
except json.JSONDecodeError:
|
|
if method == "HEAD":
|
|
json_output = dict(response.headers)
|
|
else:
|
|
json_output = {"error": "Response is not valid JSON"}
|
|
|
|
if attempts > 1:
|
|
retry_info = {"retries": attempts - 1}
|
|
if max_retry > 0:
|
|
retry_info["max_retry"] = max_retry
|
|
if retry_until_status_code > 0:
|
|
retry_info["retry_until_status_code"] = retry_until_status_code
|
|
if retry_until_not_status_code > 0:
|
|
retry_info["retry_until_not_status_code"] = retry_until_not_status_code
|
|
if retry_non_2xx:
|
|
retry_info["retry_non_2xx"] = True
|
|
if isinstance(json_output, dict):
|
|
json_output["retry_info"] = retry_info
|
|
|
|
except Exception as e:
|
|
error_message = str(e)
|
|
text_output = error_message
|
|
file_output = io.BytesIO(error_message.encode())
|
|
json_output = {"error": error_message}
|
|
headers_output = {"error": error_message}
|
|
status_code = 0
|
|
any_output = error_message
|
|
else:
|
|
headers_output = dict(response.headers)
|
|
status_code = response.status_code
|
|
|
|
return (text_output, file_output, json_output, headers_output, status_code, any_output)
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"RestApiNode": RestApiNode
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"RestApiNode": "Rest Api Node"
|
|
} |