Files
Nigel f403c82b49 feat: 添加 Rest Api Node 及相关功能节点
新增 Rest Api Node,支持多种 HTTP 方法(GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)并支持重试设置。同时添加 String Replace Node 和 Retry Settings Node,优化 Key/Value Node 和 Post/Get Node 的功能。更新 README 文档以反映新功能和节点使用说明。
2025-04-20 01:14:42 +08:00

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"
}