230 lines
6.1 KiB
GDScript3
230 lines
6.1 KiB
GDScript3
|
|
extends RefCounted
|
||
|
|
|
||
|
|
const promises = preload("res://addons/godot_colyseus/lib/promises.gd")
|
||
|
|
const Promise = promises.Promise
|
||
|
|
const RunPromise = promises.RunPromise
|
||
|
|
|
||
|
|
var _connected = false
|
||
|
|
var _client_promise: promises.Promise
|
||
|
|
|
||
|
|
class RequestInfo:
|
||
|
|
var method: String = "GET"
|
||
|
|
var path: String = "/"
|
||
|
|
var headers: PackedStringArray = []
|
||
|
|
var body
|
||
|
|
|
||
|
|
func _init(method: String = "GET",path: String = "/"):
|
||
|
|
self.method = method
|
||
|
|
self.path = path
|
||
|
|
|
||
|
|
func add_header(key: String, value):
|
||
|
|
headers.append(str(key, ": ", value))
|
||
|
|
return self
|
||
|
|
|
||
|
|
func http_method():
|
||
|
|
match method.to_upper():
|
||
|
|
"GET":
|
||
|
|
return HTTPClient.METHOD_GET
|
||
|
|
"POST":
|
||
|
|
return HTTPClient.METHOD_POST
|
||
|
|
"PUT":
|
||
|
|
return HTTPClient.METHOD_PUT
|
||
|
|
"DELETE":
|
||
|
|
return HTTPClient.METHOD_DELETE
|
||
|
|
"HEAD":
|
||
|
|
return HTTPClient.METHOD_HEAD
|
||
|
|
"OPTIONS":
|
||
|
|
return HTTPClient.METHOD_OPTIONS
|
||
|
|
|
||
|
|
func get_body():
|
||
|
|
if body == null:
|
||
|
|
body = ""
|
||
|
|
if body is Dictionary or body is Array:
|
||
|
|
body = JSON.stringify(body)
|
||
|
|
return body
|
||
|
|
|
||
|
|
class Response:
|
||
|
|
var _response_chunks: Array = []
|
||
|
|
var _body
|
||
|
|
var _has_response = false
|
||
|
|
|
||
|
|
var _status_code: int = 0
|
||
|
|
var _content_length: int = 0
|
||
|
|
var _headers
|
||
|
|
|
||
|
|
func body() -> PackedByteArray:
|
||
|
|
if _body == null:
|
||
|
|
_body = PackedByteArray()
|
||
|
|
for chunk in _response_chunks:
|
||
|
|
_body.append_array(chunk)
|
||
|
|
return _body
|
||
|
|
|
||
|
|
func text() -> String:
|
||
|
|
return body().get_string_from_utf8()
|
||
|
|
|
||
|
|
func json():
|
||
|
|
var test_json_conv = JSON.new()
|
||
|
|
var err = test_json_conv.parse(text())
|
||
|
|
if err == OK:
|
||
|
|
return test_json_conv.get_data()
|
||
|
|
print(str(test_json_conv.get_error_message(), ":", test_json_conv.get_error_line()))
|
||
|
|
return null
|
||
|
|
|
||
|
|
func headers() -> Dictionary:
|
||
|
|
return _headers
|
||
|
|
|
||
|
|
func status_code() -> int:
|
||
|
|
return _status_code
|
||
|
|
|
||
|
|
func content_length() -> int:
|
||
|
|
return _content_length
|
||
|
|
|
||
|
|
func _to_string():
|
||
|
|
var lines = PackedStringArray()
|
||
|
|
lines.append(str("StatusCode: ", status_code()))
|
||
|
|
lines.append(str("ContentLength: ", content_length()))
|
||
|
|
lines.append(str("Headers: "))
|
||
|
|
var header = headers()
|
||
|
|
for key in header.keys():
|
||
|
|
lines.append(str(" ", key, ": ", header[key]))
|
||
|
|
lines.append(str("Body: [", body().size(), "]"))
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
var _old_status
|
||
|
|
|
||
|
|
func _init(server: String):
|
||
|
|
var url = parseUrl(server)
|
||
|
|
_client_promise = promises.RunPromise.new(Callable(self, "_setup"), [url.host, url.port, url.ssl]);
|
||
|
|
|
||
|
|
static func parseUrl(url) -> Dictionary:
|
||
|
|
var regex = RegEx.new()
|
||
|
|
regex.compile("(\\w+):\\/\\/([^\\/:]+)(:(\\d+))?")
|
||
|
|
var result = regex.search(url)
|
||
|
|
var scheme = result.get_string(1)
|
||
|
|
var ssl = scheme == "https"
|
||
|
|
var host = result.get_string(2)
|
||
|
|
var portstr = result.get_string(4)
|
||
|
|
var port = -1
|
||
|
|
if portstr != "":
|
||
|
|
port = int(portstr)
|
||
|
|
return {
|
||
|
|
"host": host,
|
||
|
|
"ssl": ssl,
|
||
|
|
"port": port,
|
||
|
|
}
|
||
|
|
|
||
|
|
var host: String
|
||
|
|
func _setup(promise: promises.Promise, host, port, ssl):
|
||
|
|
var client = HTTPClient.new()
|
||
|
|
self.host = host
|
||
|
|
var error = client.connect_to_host(host, port, TLSOptions.client() if ssl else null)
|
||
|
|
if error != OK:
|
||
|
|
promise.reject(str("ErrorCode: ", error))
|
||
|
|
var root = Engine.get_main_loop()
|
||
|
|
while true:
|
||
|
|
await root.process_frame
|
||
|
|
client.poll()
|
||
|
|
var status = client.get_status()
|
||
|
|
|
||
|
|
match status:
|
||
|
|
HTTPClient.STATUS_CONNECTED:
|
||
|
|
promise.resolve(client)
|
||
|
|
break
|
||
|
|
HTTPClient.STATUS_DISCONNECTED:
|
||
|
|
promise.reject("Disconnected from Host")
|
||
|
|
break
|
||
|
|
HTTPClient.STATUS_CANT_CONNECT:
|
||
|
|
promise.reject("Can't Connect to Host")
|
||
|
|
break
|
||
|
|
|
||
|
|
func _request(promise: Promise, request: RequestInfo):
|
||
|
|
if _client_promise.get_state() == promises.Promise.State.Waiting:
|
||
|
|
await _client_promise.completed
|
||
|
|
if _client_promise.get_state() == Promise.State.Failed:
|
||
|
|
promise.reject(_client_promise.get_error())
|
||
|
|
return
|
||
|
|
var client: HTTPClient = _client_promise.get_data()
|
||
|
|
var body = request.get_body()
|
||
|
|
var error
|
||
|
|
if body is String:
|
||
|
|
error = client.request(request.http_method(), request.path, request.headers, body)
|
||
|
|
elif body is PackedByteArray:
|
||
|
|
error = client.request_raw(request.http_method(), request.path, request.headers, body)
|
||
|
|
else:
|
||
|
|
promise.reject("Unsupport body type")
|
||
|
|
return
|
||
|
|
if error != OK:
|
||
|
|
promise.reject(str("Error code ", error))
|
||
|
|
return
|
||
|
|
var root = Engine.get_main_loop()
|
||
|
|
var response = Response.new()
|
||
|
|
while true:
|
||
|
|
await root.process_frame
|
||
|
|
error = client.poll()
|
||
|
|
var status = client.get_status()
|
||
|
|
match status:
|
||
|
|
HTTPClient.STATUS_DISCONNECTED:
|
||
|
|
if response._has_response:
|
||
|
|
promise.resolve(response)
|
||
|
|
else:
|
||
|
|
promise.reject("Disconnected from Host")
|
||
|
|
return
|
||
|
|
HTTPClient.STATUS_CANT_CONNECT:
|
||
|
|
promise.reject("Can't Connect to Host")
|
||
|
|
return
|
||
|
|
HTTPClient.STATUS_CONNECTION_ERROR:
|
||
|
|
promise.reject("Connection Error")
|
||
|
|
return
|
||
|
|
HTTPClient.STATUS_BODY:
|
||
|
|
if not response._has_response:
|
||
|
|
response._has_response = true
|
||
|
|
response._status_code = client.get_response_code()
|
||
|
|
response._content_length = client.get_response_body_length()
|
||
|
|
response._headers = client.get_response_headers_as_dictionary()
|
||
|
|
var chunk = client.read_response_body_chunk()
|
||
|
|
if chunk.is_empty():
|
||
|
|
continue
|
||
|
|
response._response_chunks.append(chunk)
|
||
|
|
HTTPClient.STATUS_CONNECTED:
|
||
|
|
promise.resolve(response)
|
||
|
|
return
|
||
|
|
pass
|
||
|
|
|
||
|
|
func _resolve(promise: Promise, request: RequestInfo, response: Response):
|
||
|
|
if response.status_code() == 301:
|
||
|
|
var new_request = RequestInfo.new(request.method, request.path)
|
||
|
|
new_request.headers = request.headers
|
||
|
|
new_request.body = request.body
|
||
|
|
|
||
|
|
var path: String = response.headers()["Location"]
|
||
|
|
if path.begins_with("http://") or path.begins_with("https://"):
|
||
|
|
var idx = path.find('/', 8)
|
||
|
|
var host
|
||
|
|
if idx >= 0:
|
||
|
|
host = path.substr(0, idx)
|
||
|
|
path = path.substr(idx)
|
||
|
|
else:
|
||
|
|
host = path
|
||
|
|
path = '/'
|
||
|
|
var url = parseUrl(host)
|
||
|
|
if url.host != self.host:
|
||
|
|
printerr("Cannot connect to new host ", host, self.host)
|
||
|
|
promise.resolve(response)
|
||
|
|
return
|
||
|
|
else:
|
||
|
|
new_request.path = path
|
||
|
|
|
||
|
|
promise.resolve(fetch(new_request))
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
promise.resolve(response)
|
||
|
|
|
||
|
|
func fetch(request = null) -> Promise:
|
||
|
|
if request == null:
|
||
|
|
request = RequestInfo.new()
|
||
|
|
elif request is String:
|
||
|
|
var path = request
|
||
|
|
request = RequestInfo.new()
|
||
|
|
request.path = path
|
||
|
|
return RunPromise.new(_request, [request])
|