scopedvr-map-core/server/admin_client.gd
2026-11-01 19:31:33 +01:00

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