Files
airplane-mode/addons/godot_colyseus/lib/http.gd

230 lines
6.1 KiB
GDScript3
Raw Normal View History

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])