Attempt to using Nakama as replacement of Low-Level ENet
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
# An adapter which implements the HTTP protocol.
|
||||
class_name SatoriHTTPAdapter
|
||||
|
||||
# The logger to use with the adapter.
|
||||
var logger : RefCounted = SatoriLogger.new()
|
||||
|
||||
# The timeout for requests
|
||||
var timeout : int = 3
|
||||
# If request should be automatically retried when a network error occurs.
|
||||
var auto_retry : bool = true
|
||||
# The maximum number of time a request will be retried when auto_retry is true
|
||||
var auto_retry_count : int = 3
|
||||
var auto_retry_backoff_base : int = 10
|
||||
# Whether or not to use threads when making HTTP requests.
|
||||
var use_threads : bool = true
|
||||
|
||||
var _pending = {}
|
||||
var id : int = 0
|
||||
|
||||
class AsyncRequest:
|
||||
var id : int
|
||||
var request : HTTPRequest
|
||||
var uri : String
|
||||
var method : int
|
||||
var headers : PackedStringArray
|
||||
var body : PackedByteArray
|
||||
var retry_count := 3
|
||||
var backoff_time := 10
|
||||
var logger : SatoriLogger
|
||||
|
||||
var cancelled = false
|
||||
var result : int = HTTPRequest.RESULT_NO_RESPONSE
|
||||
var response_code : int = -1
|
||||
var response_body : PackedByteArray
|
||||
var timer : SceneTreeTimer = null
|
||||
var cur_try : int = 1
|
||||
var rng = RandomNumberGenerator.new()
|
||||
|
||||
func _init(p_id : int, p_request : HTTPRequest, p_uri : String,
|
||||
p_method : int, p_headers : PackedStringArray, p_body : PackedByteArray,
|
||||
p_retry_count : int, p_backoff_time : int, p_logger : SatoriLogger):
|
||||
rng.seed = Time.get_ticks_usec()
|
||||
id = p_id
|
||||
request = p_request
|
||||
uri = p_uri
|
||||
method = p_method
|
||||
headers = p_headers
|
||||
body = p_body
|
||||
retry_count = p_retry_count
|
||||
backoff_time = p_backoff_time
|
||||
logger = p_logger
|
||||
|
||||
func should_retry():
|
||||
return cur_try < retry_count and not cancelled
|
||||
|
||||
func retry():
|
||||
var time = pow(backoff_time, cur_try) * rng.randf_range(0.5, 1)
|
||||
logger.debug("Retrying request %d. Tries left: %d. Backoff: %d ms" % [
|
||||
id, retry_count - cur_try, time
|
||||
])
|
||||
cur_try += 1
|
||||
await backoff(time)
|
||||
if cancelled:
|
||||
return
|
||||
return await make_request()
|
||||
|
||||
func make_request():
|
||||
var err = request.request(uri, headers, method, body.get_string_from_utf8())
|
||||
if err != OK:
|
||||
await request.get_tree().process_frame
|
||||
result = HTTPRequest.RESULT_CANT_CONNECT
|
||||
logger.debug("Request %d failed to start, error: %d" % [id, err])
|
||||
return
|
||||
|
||||
var args = await request.request_completed
|
||||
result = args[0]
|
||||
response_code = args[1]
|
||||
response_body = args[3]
|
||||
|
||||
func backoff(p_time : int):
|
||||
timer = request.get_tree().create_timer(p_time / 1000.0)
|
||||
await timer.timeout
|
||||
timer = null
|
||||
|
||||
func cancel():
|
||||
cancelled = true
|
||||
request.cancel_request()
|
||||
if timer:
|
||||
timer.time_left = 0
|
||||
else:
|
||||
request.call_deferred("emit_signal", "request_completed", HTTPRequest.RESULT_REQUEST_FAILED, 0, [], [])
|
||||
|
||||
func parse_result():
|
||||
if cancelled:
|
||||
return SatoriException.new("Request cancelled", -1, -1, true)
|
||||
elif result != HTTPRequest.RESULT_SUCCESS:
|
||||
if result == null:
|
||||
result = 0
|
||||
return SatoriException.new("HTTPRequest failed!", result)
|
||||
|
||||
var json = JSON.new()
|
||||
|
||||
var json_error = json.parse(response_body.get_string_from_utf8())
|
||||
if json_error != OK:
|
||||
logger.debug("Unable to parse request %d response. JSON error: %d, JSON error message: %s, response code: %d" % [
|
||||
id, json_error, json.get_error_message(), response_code
|
||||
])
|
||||
return SatoriException.new("Failed to decode JSON response", response_code)
|
||||
|
||||
var parsed = json.get_data()
|
||||
|
||||
if response_code != HTTPClient.RESPONSE_OK:
|
||||
var error = ""
|
||||
var code = -1
|
||||
if typeof(parsed) == TYPE_DICTIONARY:
|
||||
if "message" in parsed:
|
||||
error = parsed["message"]
|
||||
elif "error" in parsed:
|
||||
error = parsed["error"]
|
||||
else:
|
||||
error = str(parsed)
|
||||
code = parsed["code"] if "code" in parsed else -1
|
||||
else:
|
||||
error = str(parsed)
|
||||
if typeof(error) == TYPE_DICTIONARY:
|
||||
error = JSON.stringify(error)
|
||||
logger.debug("Request %d returned response code: %d, RPC code: %d, error: %s" % [
|
||||
id, response_code, code, error
|
||||
])
|
||||
return SatoriException.new(error, response_code, code)
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
# Send a HTTP request.
|
||||
# @param method - HTTP method to use for this request.
|
||||
# @param uri - The fully qualified URI to use.
|
||||
# @param headers - Request headers to set.
|
||||
# @param body - Request content body to set.
|
||||
# @param timeoutSec - Request timeout.
|
||||
# Returns a task which resolves to the contents of the response.
|
||||
func send_async(p_method : String, p_uri : String, p_headers : Dictionary, p_body : PackedByteArray):
|
||||
var req = HTTPRequest.new()
|
||||
req.timeout = timeout
|
||||
if use_threads and OS.get_name() != 'Web':
|
||||
req.use_threads = true # Threads not available nor needed on the web.
|
||||
|
||||
# Parse method
|
||||
var method = HTTPClient.METHOD_GET
|
||||
if p_method == "POST":
|
||||
method = HTTPClient.METHOD_POST
|
||||
elif p_method == "PUT":
|
||||
method = HTTPClient.METHOD_PUT
|
||||
elif p_method == "DELETE":
|
||||
method = HTTPClient.METHOD_DELETE
|
||||
elif p_method == "HEAD":
|
||||
method = HTTPClient.METHOD_HEAD
|
||||
var headers = PackedStringArray()
|
||||
|
||||
# Parse headers
|
||||
headers.append("Accept: application/json")
|
||||
for k in p_headers:
|
||||
headers.append("%s: %s" % [k, p_headers[k]])
|
||||
|
||||
id += 1
|
||||
var retry = auto_retry_count if auto_retry else 0
|
||||
var backoff = auto_retry_backoff_base
|
||||
_pending[id] = AsyncRequest.new(id, req, p_uri, method, headers, p_body, retry, backoff, logger)
|
||||
|
||||
logger.debug("Sending request [ID: %d, Method: %s, Uri: %s, Headers: %s, Body: %s, Timeout: %d, Retries: %d, Backoff base: %d ms]" % [
|
||||
id, p_method, p_uri, p_headers, p_body.get_string_from_utf8(), timeout, retry, backoff
|
||||
])
|
||||
|
||||
add_child(req)
|
||||
|
||||
return await _send_async(id, _pending)
|
||||
|
||||
func get_last_token():
|
||||
return id
|
||||
|
||||
func cancel_request(p_token):
|
||||
if _pending.has(p_token):
|
||||
_pending[p_token].cancel()
|
||||
|
||||
static func _clear_request(p_request : AsyncRequest, p_pending : Dictionary, p_id : int):
|
||||
if not p_request.request.is_queued_for_deletion():
|
||||
p_request.logger.debug("Freeing request %d" % p_id)
|
||||
p_request.request.queue_free()
|
||||
p_pending.erase(p_id)
|
||||
|
||||
static func _send_async(p_id : int, p_pending : Dictionary):
|
||||
|
||||
var req : AsyncRequest = p_pending[p_id]
|
||||
await req.make_request()
|
||||
|
||||
while req.result != HTTPRequest.RESULT_SUCCESS:
|
||||
req.logger.debug("Request %d failed with result: %d, response code: %d" % [
|
||||
p_id, req.result, req.response_code
|
||||
])
|
||||
if not req.should_retry():
|
||||
break
|
||||
await req.retry()
|
||||
|
||||
_clear_request(req, p_pending, p_id)
|
||||
return req.parse_result()
|
||||
Reference in New Issue
Block a user