Files
MusicPlayer/server/http_transport.gd
T
Sansan f7619baf34 Use /api/v1 with token authentication and typed responses
Closes #38, #39 and #40; adds the client side of #41.

- Every request goes to /api/v1. MusicServerClient gets a token with
  Settings.api_password the first time one is needed, reuses it, and on a
  401 gets a new one and sends the request once more. Switching server gets
  a new token too. Callers never see the token.
- Sync is started with POST, as MusicServer 5.0 requires.
- The client has every endpoint in the server's OpenAPI spec, including
  statistics, soundtracks and songs by UUID, version, downloads and tokens.
- tools/generate_api_types.gd generates a typed class per schema in
  server/openapi.json into server/generated/. Song info, statistics and
  versions are passed to callers as these classes. A list the server sends
  as null is an empty list.
- FakeHttpTransport hands out tokens and answers 401 without one, so every
  test goes through the token flow. New tests cover the token handling,
  every endpoint and each generated class.
2026-10-06 22:45:28 +02:00

51 lines
1.7 KiB
GDScript

class_name HttpTransport
extends RefCounted
## Sends HTTP requests for [code]MusicServerClient[/code] using [HTTPRequest] nodes.
##
## Tests subclass this to answer requests without a network connection.
## Seconds before a request is given up and reported as failed. This covers the whole
## download, so it is generous enough for long songs on a slow connection.
var timeout_seconds: float = 120.0
var _owner: Node
func _init(owner: Node) -> void:
_owner = owner
## Requests [param url] and calls [param on_complete] with
## [code](response_code: int, body: PackedByteArray)[/code]. Connection errors and
## timeouts are reported with response code 0. [param headers] are sent as-is; a
## [param body] is sent as JSON.
func request(
url: String,
method: int,
on_complete: Callable,
headers: PackedStringArray = PackedStringArray(),
body: String = "",
) -> void:
var http_request: HTTPRequest = HTTPRequest.new()
http_request.timeout = timeout_seconds
_owner.add_child(http_request)
http_request.request_completed.connect(
func(
result: int,
response_code: int,
_headers: PackedStringArray,
response_body: PackedByteArray,
) -> void:
http_request.queue_free()
on_complete.call(response_code if result == HTTPRequest.RESULT_SUCCESS else 0,
response_body)
)
var all_headers: PackedStringArray = headers.duplicate()
if method == HTTPClient.METHOD_PUT || !body.is_empty():
all_headers.append("Content-Type: application/json")
var error: int = http_request.request(url, all_headers, method, body)
if error != OK:
http_request.queue_free()
push_error("Could not send request to %s (error %d)" % [url, error])
on_complete.call(0, PackedByteArray())