@tool class_name APIClient extends Node signal response_received(response_body) signal request_failed(error) const RESULT_CANT_CONNECT = "Failed to connect" const RESULT_CANT_RESOLVE = "Failed to resolve" const RESULT_CONNECTION_ERROR = "Connection error" const RESULT_TIMEOUT = "Connection timeout" const RESULT_SERVER_ERROR = "Server error" const REQUEST_FAILED = "Error in the request" const REQUEST_TIMEOUT = "Request timed out on the client side" const URL_NOT_SET = "URL parameter is not set" const PARSE_FAILED = "Parsing failed" ## Location of the API endpoint. @export var api_url : String ## HTTP request method to use. Typically GET or POST. @export var request_method : HTTPClient.Method = HTTPClient.METHOD_POST @export_group("Advanced") ## Location of an API key file, if authorization is required by the endpoint. @export_file("*.txt") var api_key_file : String ## Time in seconds before the request fails due to timeout. @export var request_timeout : float = 0.0 @export var _send_request_action : bool = false : set(value): if value and Engine.is_editor_hint(): request() # For Godot 4.4 # @export_tool_button("Send Request") var _send_request_action = request @onready var _http_request : HTTPRequest = $HTTPRequest @onready var _timeout_timer : Timer= $TimeoutTimer ## State flag for whether the connection has timed out on the client-side. var timed_out : bool = false func get_http_request() -> HTTPRequest: return _http_request func get_api_key() -> String: if api_key_file.is_empty(): return "" var file := FileAccess.open(api_key_file, FileAccess.READ) var error := FileAccess.get_open_error() if error != OK: push_error("API Key reading error: %d" % error) return "" var content = file.get_as_text() file.close() return content func get_api_url() -> String: return api_url func get_api_method() -> int: return request_method func mock_empty_body() -> String: var form : Dictionary = {} return JSON.stringify(form) func mock_request(body : String): await(get_tree().create_timer(10.0).timeout) _on_request_completed(HTTPRequest.RESULT_SUCCESS, "200", [], body) func request(body : String = "", request_headers : Array = []) -> void: var local_http_request : HTTPRequest = get_http_request() var key : String = get_api_key() var url : String = get_api_url() var method : int = get_api_method() if url.is_empty(): request_failed.emit(URL_NOT_SET) push_error(URL_NOT_SET) return request_headers.append("Content-Type: application/json") if key: request_headers.append("x-api-key: %s" % key) if request_timeout > 0.0: local_http_request.timeout = request_timeout var error = local_http_request.request(url, request_headers, method, body) if error != OK: request_failed.emit(REQUEST_FAILED) push_error("HTTP Request error: %d" % error) return if request_timeout > 0.0: _timeout_timer.start(request_timeout + 1.0) func request_raw(data : PackedByteArray = [], request_headers : Array = []) -> void: var local_http_request : HTTPRequest = get_http_request() var key : String = get_api_key() var url : String = get_api_url() var method : int = get_api_method() if url.is_empty(): request_failed.emit(URL_NOT_SET) push_error(URL_NOT_SET) return request_headers.append("Content-Type: application/json") if key: request_headers.append("x-api-key: %s" % key) if request_timeout > 0.0: local_http_request.timeout = request_timeout var error = local_http_request.request_raw(url, request_headers, method, data) if error != OK: request_failed.emit(REQUEST_FAILED) push_error("HTTP Request error: %d" % error) return if request_timeout > 0.0: _timeout_timer.start(request_timeout + 1.0) func _on_request_completed(result, response_code, headers, body) -> void: # If already timed out on client-side, then return. if timed_out: return _timeout_timer.stop() if result == HTTPRequest.RESULT_SUCCESS: var body_string : String if body is PackedByteArray: body_string = body.get_string_from_utf8() elif body is String: body_string = body var json := JSON.new() var error = json.parse(body_string) if error != OK: request_failed.emit(PARSE_FAILED) push_error("Parse error: %d" % error) return var parsed_data = json.data response_received.emit(json.data) else: var error_message : String match(result): HTTPRequest.RESULT_CANT_CONNECT: error_message = RESULT_CANT_CONNECT HTTPRequest.RESULT_CANT_RESOLVE: error_message = RESULT_CANT_RESOLVE HTTPRequest.RESULT_CONNECTION_ERROR: error_message = RESULT_CONNECTION_ERROR HTTPRequest.RESULT_TIMEOUT: error_message = RESULT_TIMEOUT _: error_message = RESULT_SERVER_ERROR request_failed.emit(error_message) push_error("HTTP Result error: %d" % result) func _on_http_request_request_completed(result, response_code, headers, body) -> void: _on_request_completed(result, response_code, headers, body) func _on_timeout_timer_timeout() -> void: timed_out = true request_failed.emit(REQUEST_TIMEOUT) push_warning(REQUEST_TIMEOUT)