112 lines
4.2 KiB
GDScript
112 lines
4.2 KiB
GDScript
class_name AdminClient
|
|
extends Node
|
|
## Client der Match-Server-Admin-API v1 (Doku: docs/admin-api.md im
|
|
## scopedvr-match-server-Repo). Knoten in den Baum hängen, dann
|
|
## `var res = await client.call_api("GET", "/status")`.
|
|
##
|
|
## Antwort: {ok: bool (HTTP 2xx), status: int, body: Dictionary, transport_error: String}
|
|
## transport_error ist deutsch und leer, wenn der Server geantwortet hat.
|
|
## Das Token geht NUR als Header raus und wird nie geloggt oder zurückgegeben.
|
|
|
|
const TIMEOUT_S := 30.0
|
|
const MAX_RESPONSE_BYTES := 8 * 1024 * 1024
|
|
|
|
var host: String = ""
|
|
var port: int = 24580
|
|
var tls: bool = false
|
|
var token: String = ""
|
|
|
|
|
|
## Server-Eintrag aus dem ServerStore übernehmen.
|
|
func use(entry: Dictionary) -> void:
|
|
host = str(entry.get("host", ""))
|
|
port = int(entry.get("port", 24580))
|
|
tls = bool(entry.get("tls", false))
|
|
token = str(entry.get("token", ""))
|
|
|
|
|
|
static func base_url(p_host: String, p_port: int, p_tls: bool) -> String:
|
|
var h := p_host
|
|
if h.contains(":") and not h.begins_with("["):
|
|
h = "[%s]" % h # IPv6
|
|
return "%s://%s:%d/api/v1" % ["https" if p_tls else "http", h, p_port]
|
|
|
|
|
|
## Prüft die Eingabe für „Server hinzufügen“. Leerer Text = ok.
|
|
static func check_address(p_host: String, p_port: int) -> String:
|
|
var h := p_host.strip_edges()
|
|
if h.is_empty():
|
|
return "Bitte eine Adresse eingeben (IP oder Name)."
|
|
if h.contains("/") or h.contains(" ") or h.contains("://"):
|
|
return "Nur die Adresse eintragen, ohne http:// und ohne Schrägstriche (z. B. 192.168.1.20)."
|
|
if p_port < 1 or p_port > 65535:
|
|
return "Der Port muss zwischen 1 und 65535 liegen."
|
|
return ""
|
|
|
|
|
|
func call_api(method: String, path: String, body: Variant = null, query: Dictionary = {}, raw: PackedByteArray = PackedByteArray()) -> Dictionary:
|
|
var err := check_address(host, port)
|
|
if err != "":
|
|
return _transport_fail(err)
|
|
var url := base_url(host, port, tls) + path
|
|
if not query.is_empty():
|
|
var parts: PackedStringArray = []
|
|
for k in query.keys():
|
|
parts.append("%s=%s" % [str(k).uri_encode(), str(query[k]).uri_encode()])
|
|
url += "?" + "&".join(parts)
|
|
var headers: PackedStringArray = ["Accept: application/json"]
|
|
if token != "":
|
|
headers.append("Authorization: Bearer " + token)
|
|
var payload := raw
|
|
if body != null:
|
|
payload = JSON.stringify(body).to_utf8_buffer()
|
|
headers.append("Content-Type: application/json")
|
|
elif not raw.is_empty():
|
|
headers.append("Content-Type: application/octet-stream")
|
|
var http := HTTPRequest.new()
|
|
http.timeout = TIMEOUT_S
|
|
http.body_size_limit = MAX_RESPONSE_BYTES
|
|
http.accept_gzip = false
|
|
add_child(http)
|
|
var m := _method(method)
|
|
var rerr := http.request_raw(url, headers, m, payload)
|
|
if rerr != OK:
|
|
http.queue_free()
|
|
return _transport_fail("Anfrage konnte nicht gestartet werden (Fehler %d)." % rerr)
|
|
var done: Array = await http.request_completed
|
|
http.queue_free()
|
|
var result: int = done[0]
|
|
if result != HTTPRequest.RESULT_SUCCESS:
|
|
return _transport_fail(_result_text(result))
|
|
var code: int = done[1]
|
|
var parsed: Variant = JSON.parse_string((done[3] as PackedByteArray).get_string_from_utf8())
|
|
var out: Dictionary = parsed if parsed is Dictionary else {}
|
|
return {"ok": code >= 200 and code < 300, "status": code, "body": out, "transport_error": ""}
|
|
|
|
|
|
static func _method(m: String) -> int:
|
|
match m.to_upper():
|
|
"POST": return HTTPClient.METHOD_POST
|
|
"PUT": return HTTPClient.METHOD_PUT
|
|
"PATCH": return HTTPClient.METHOD_PATCH
|
|
"DELETE": return HTTPClient.METHOD_DELETE
|
|
return HTTPClient.METHOD_GET
|
|
|
|
|
|
static func _transport_fail(msg: String) -> Dictionary:
|
|
return {"ok": false, "status": 0, "body": {}, "transport_error": msg}
|
|
|
|
|
|
static func _result_text(result: int) -> String:
|
|
match result:
|
|
HTTPRequest.RESULT_CANT_CONNECT, HTTPRequest.RESULT_CONNECTION_ERROR:
|
|
return "Keine Verbindung zum Server. Läuft er, ist die Admin-API eingeschaltet, und stimmen Adresse und Port?"
|
|
HTTPRequest.RESULT_CANT_RESOLVE:
|
|
return "Die Adresse wurde nicht gefunden. Tippfehler?"
|
|
HTTPRequest.RESULT_TIMEOUT:
|
|
return "Der Server antwortet nicht (Zeitüberschreitung)."
|
|
HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
|
|
return "Sichere Verbindung (https) fehlgeschlagen. Gibt der Server wirklich https her?"
|
|
HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED:
|
|
return "Die Antwort des Servers ist zu groß."
|
|
return "Verbindungsfehler (Code %d)." % result
|