commit a84d0021403d34d3dcc91c94c13e2c98c75b94bf Author: wawahuy Date: Tue Jul 22 01:17:05 2025 +0700 feat: support http get diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..029519e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dad5af2 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# ComfyUI HTTP - REST API Nodes + +Powerful REST API nodes for ComfyUI that enable seamless HTTP/REST integration into your workflows. + +## Installation + +1. Clone the repository into your ComfyUI `custom_nodes` directory: +```bash +cd ComfyUI/custom_nodes +git clone https://github.com/wawahuy/ComfyUI-HTTP.git +``` + +2. Install dependencies: +```bash +cd ComfyUI-HTTP +pip install -r requirements.txt +``` + +3. Restart ComfyUI + +## Contributing + +We welcome contributions! Please: + +1. Fork the repository +2. Create a feature branch +3. Commit your changes +4. Submit a pull request + +## License + +MIT License - see LICENSE file for details. + +## Support + +If you encounter issues or have questions, please create an issue on the GitHub repository. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d0dd26d --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +from .http_get import HTTPGetNode +from .http_get_json_field import HTTPGetJSONFieldNode + +NODE_CLASS_MAPPINGS = { + "HTTPGetNode": HTTPGetNode, + "HTTPGetJSONFieldNode": HTTPGetJSONFieldNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "HTTPGetNode": "HTTP Get", + "HTTPGetJSONFieldNode": "HTTP Get JSON Field", +} + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/http_get.py b/http_get.py new file mode 100644 index 0000000..ff56f49 --- /dev/null +++ b/http_get.py @@ -0,0 +1,123 @@ +import requests +import json +import time +from typing import Dict, Any, Tuple + +class HTTPGetNode: + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + return { + "required": { + "url": ("STRING", { + "multiline": False, + "default": "https://httpbin.org/json" + }), + }, + "optional": { + "headers": ("STRING", { + "multiline": True, + "default": '{"Content-Type": "application/json", "User-Agent": "ComfyUI-HTTPPull-Starter/1.0"}' + }), + "params": ("STRING", { + "multiline": True, + "default": '{"workflow": "started", "source": "comfyui"}' + }), + "timeout": ("INT", { + "default": 30, + "min": 5, + "max": 300, + "step": 5 + }), + "retry_count": ("INT", { + "default": 1, + "min": 1, + "max": 5, + "step": 1 + }), + "retry_delay": ("INT", { + "default": 5, + "min": 1, + "max": 30, + "step": 1 + }), + } + } + + RETURN_TYPES = ("STRING", "STRING", "INT", "BOOLEAN", "STRING") + RETURN_NAMES = ("response_text", "response_json", "status_code", "success", "workflow_id") + FUNCTION = "http_get" + CATEGORY = "HTTP/API" + OUTPUT_NODE = False + + def http_get(self, url: str, headers: str = "", params: str = "", + timeout: int = 30, retry_count: int = 1, retry_delay: int = 5) -> Tuple[str, str, int, bool, str]: + + workflow_id = f"wf_{int(time.time())}" + + last_error = "" + + for attempt in range(retry_count): + try: + request_headers = {"Content-Type": "application/json"} + if headers: + try: + parsed_headers = json.loads(headers) + request_headers.update(parsed_headers) + except json.JSONDecodeError: + print(f"Warning: Invalid headers JSON format: {headers}") + + request_params = {} + if params: + try: + request_params = json.loads(params) + request_params["workflow_id"] = workflow_id + request_params["attempt"] = attempt + 1 + except json.JSONDecodeError: + print(f"Warning: Invalid params JSON format: {params}") + + print(f"HTTP Get - Attempt {attempt + 1}/{retry_count} - URL: {url}") + + response = requests.get( + url=url, + headers=request_headers, + params=request_params, + timeout=timeout + ) + + response_text = response.text + + response_json = "{}" + try: + json.loads(response_text) + response_json = response_text + except json.JSONDecodeError: + response_json = "{}" + + status_code = response.status_code + success = 200 <= status_code < 300 + + if success: + print(f"HTTP Get - Success: Workflow {workflow_id} started") + return (response_text, response_json, status_code, True, workflow_id) + else: + last_error = f"HTTP error {status_code}: {response_text[:200]}" + + except requests.exceptions.Timeout: + last_error = f"Request timeout after {timeout} seconds" + print(f"HTTP Get - Attempt {attempt + 1} failed: {last_error}") + + except requests.exceptions.ConnectionError: + last_error = f"Connection error to {url}" + print(f"HTTP Get - Attempt {attempt + 1} failed: {last_error}") + + except Exception as e: + last_error = f"HTTP Get Error: {str(e)}" + print(f"HTTP Get - Attempt {attempt + 1} failed: {last_error}") + + if attempt < retry_count - 1: + print(f"Waiting {retry_delay} seconds before retry...") + time.sleep(retry_delay) + + print(f"HTTP Get - All attempts failed for workflow {workflow_id}") + return (last_error, "{}", 0, False, workflow_id) diff --git a/http_get_json_field.py b/http_get_json_field.py new file mode 100644 index 0000000..9c7b6c3 --- /dev/null +++ b/http_get_json_field.py @@ -0,0 +1,77 @@ +import json +from typing import Dict, Any, Tuple, Union + +class HTTPGetJSONFieldNode: + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + return { + "required": { + "json_input": ("STRING", { + "multiline": True, + "default": '{"data": {"message": "Hello World"}, "status": "success"}' + }), + "field_path": ("STRING", { + "multiline": False, + "default": "data.message" + }), + }, + "optional": { + "default_value": ("STRING", { + "multiline": False, + "default": "" + }), + "return_as_string": ("BOOLEAN", { + "default": True + }), + } + } + + RETURN_TYPES = ("STRING", "STRING", "BOOLEAN") + RETURN_NAMES = ("field_value", "error_message", "found") + FUNCTION = "get_json_field" + CATEGORY = "HTTP/API" + + def get_json_field(self, json_input: str, field_path: str, + default_value: str = "", return_as_string: bool = True) -> Tuple[str, str, bool]: + try: + data = json.loads(json_input) + + path_parts = field_path.split('.') + + current_value = data + for part in path_parts: + if isinstance(current_value, dict) and part in current_value: + current_value = current_value[part] + elif isinstance(current_value, list): + try: + index = int(part) + if 0 <= index < len(current_value): + current_value = current_value[index] + else: + return (default_value, f"List index {index} out of range", False) + except ValueError: + return (default_value, f"Cannot access list with key '{part}'", False) + else: + return (default_value, f"Field '{part}' not found in path '{field_path}'", False) + + if return_as_string: + if isinstance(current_value, (dict, list)): + result_value = json.dumps(current_value, ensure_ascii=False) + else: + result_value = str(current_value) + else: + result_value = str(current_value) + + print(f"JSON Field Get - Path: {field_path}, Found: {type(current_value).__name__}") + return (result_value, "", True) + + except json.JSONDecodeError as e: + error_message = f"Invalid JSON format: {str(e)}" + print(f"JSON Field Error: {error_message}") + return (default_value, error_message, False) + + except Exception as e: + error_message = f"Error getting field: {str(e)}" + print(f"JSON Field Error: {error_message}") + return (default_value, error_message, False) \ No newline at end of file diff --git a/node_metadata.json b/node_metadata.json new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fd45fd7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests>=2.25.0 +pillow>=8.0.0 +numpy>=1.19.0 \ No newline at end of file