본문으로 건너뛰기
cccskills
무료GitHub에 공개

web-services

Use when calling web APIs or backends from Godot — HTTPRequest, REST/JSON clients, auth headers, downloads, TLS, WebSocketPeer clients, retries, and keeping secrets out of the build

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter스타 879(GitHub 879 + cccskills 0)

실행 점수 미검증

설치 방법 보기

포함된 파일(4)

  • SKILL.md12.9 KB
  • references/downloads-and-uploads.md7.9 KB
  • references/rest-client-patterns.md7.0 KB
  • references/websocket-client.md3.1 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Web Services in Godot 4.3+

Talk to REST APIs, leaderboards, analytics, login services, and other HTTP/WebSocket backends. All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.

Related skills: multiplayer-basics for real-time game networking with WebSocketMultiplayerPeer, save-load for persisting tokens and cached responses, web-export for CORS and browser limits, mobile-development for Android export permissions, multithreading for heavy response parsing off the main thread.


1. Picking the Tool

NeedUseWhy
Ordinary REST call, file or image downloadHTTPRequest nodeHandles connect, redirects, gzip, timeout; emits one signal when done
Streaming a response, custom keep-alive, many requests to one hostHTTPClientLow-level; you call poll() and drive the state machine yourself
Persistent two-way channel (chat, live events, lobby service)WebSocketPeerClient socket; call poll() every frame
Real-time gameplay sync with RPCsWebSocketMultiplayerPeerPlugs into MultiplayerAPI — see multiplayer-basics

One HTTPRequest node runs one request at a time. A second request() while one is in flight returns ERR_BUSY. Create one node per concurrent request (Section 3) rather than queueing on a shared node.


2. A Single Request with await

request() only starts the request; its return value says whether it could be sent, not whether the server answered. The result arrives on request_completed(result, response_code, headers, body).

func fetch_motd() -> void:
    var http := HTTPRequest.new()
    http.timeout = 10.0 # Default 0.0 means wait forever.
    add_child(http)

    var err := http.request("https://api.example.com/v1/motd")
    if err != OK:
        push_error("Could not start request: %s" % error_string(err))
        http.queue_free()
        return

    var res: Array = await http.request_completed
    http.queue_free()

    var result: int = res[0]
    var code: int = res[1]
    var body: PackedByteArray = res[3]
    if result != HTTPRequest.RESULT_SUCCESS:
        push_warning("Network failure (HTTPRequest.Result %d)" % result)
    elif code != 200:
        push_warning("Server answered HTTP %d" % code)
    else:
        print(body.get_string_from_utf8())
public async void FetchMotd()
{
    var http = new HttpRequest { Timeout = 10.0 }; // Default 0.0 means wait forever.
    AddChild(http);

    Error err = http.Request("https://api.example.com/v1/motd");
    if (err != Error.Ok)
    {
        GD.PushError($"Could not start request: {err}");
        http.QueueFree();
        return;
    }

    Variant[] res = await ToSignal(http, HttpRequest.SignalName.RequestCompleted);
    http.QueueFree();

    long result = res[0].AsInt64();
    long code = res[1].AsInt64();
    byte[] body = res[3].AsByteArray();
    if (result != (long)HttpRequest.Result.Success)
        GD.PushWarning($"Network failure (HttpRequest.Result {result})");
    else if (code != 200)
        GD.PushWarning($"Server answered HTTP {code}");
    else
        GD.Print(body.GetStringFromUtf8());
}

Check both values: result is the transport outcome (DNS, TLS, timeout — RESULT_CANT_RESOLVE, RESULT_TLS_HANDSHAKE_ERROR, RESULT_TIMEOUT, …), response_code is the HTTP status. A 404 or 500 arrives with RESULT_SUCCESS.


3. A Reusable JSON API Client (Autoload)

Wrap the boilerplate once. Register this as an autoload named Api; every call spawns its own HTTPRequest, so calls can overlap.

# api.gd — autoload "Api"
extends Node

const BASE_URL := "https://api.example.com/v1"
const TIMEOUT_SEC := 10.0

var auth_token: String = ""


## Returns {ok: bool, code: int, data: Variant, error: String}.
func request_json(path: String, method: HTTPClient.Method = HTTPClient.METHOD_GET, payload: Variant = null) -> Dictionary:
    var http := HTTPRequest.new()
    http.timeout = TIMEOUT_SEC
    add_child(http)

    var headers := PackedStringArray(["Accept: application/json"])
    var body := ""
    if payload != null:
        headers.append("Content-Type: application/json")
        body = JSON.stringify(payload)
    if not auth_token.is_empty():
        headers.append("Authorization: Bearer " + auth_token)

    var err := http.request(BASE_URL + path, headers, method, body)
    if err != OK:
        http.queue_free()
        return {"ok": false, "code": 0, "data": null, "error": "request() failed: " + error_string(err)}

    var res: Array = await http.request_completed
    http.queue_free()
    if res[0] != HTTPRequest.RESULT_SUCCESS:
        return {"ok": false, "code": 0, "data": null, "error": "transport error %d" % res[0]}

    var code: int = res[1]
    var text := (res[3] as PackedByteArray).get_string_from_utf8()
    var data: Variant = JSON.parse_string(text) if not text.is_empty() else null
    return {"ok": code >= 200 and code < 300, "code": code, "data": data, "error": "" if code < 400 else text}
// Api.cs — autoload "Api"
using System.Collections.Generic;
using System.Threading.Tasks;
using Godot;

public partial class Api : Node
{
    private const string BaseUrl = "https://api.example.com/v1";
    private const double TimeoutSec = 10.0;

    public string AuthToken { get; set; } = "";

    public readonly record struct ApiResult(bool Ok, long Code, Variant Data, string Error);

    public async Task<ApiResult> RequestJson(string path, HttpClient.Method method = HttpClient.Method.Get, Variant payload = default)
    {
        var http = new HttpRequest { Timeout = TimeoutSec };
        AddChild(http);

        var headers = new List<string> { "Accept: application/json" };
        string body = "";
        if (payload.VariantType != Variant.Type.Nil)
        {
            headers.Add("Content-Type: application/json");
            body = Json.Stringify(payload);
        }
        if (AuthToken != "")
            headers.Add("Authorization: Bearer " + AuthToken);

        Error err = http.Request(BaseUrl + path, headers.ToArray(), method, body);
        if (err != Error.Ok)
        {
            http.QueueFree();
            return new ApiResult(false, 0, default, $"Request() failed: {err}");
        }

        Variant[] res = await ToSignal(http, HttpRequest.SignalName.RequestCompleted);
        http.QueueFree();
        if (res[0].AsInt64() != (long)HttpRequest.Result.Success)
            return new ApiResult(false, 0, default, $"Transport error {res[0]}");

        long code = res[1].AsInt64();
        string text = res[3].AsByteArray().GetStringFromUtf8();
        Variant data = text == "" ? default : Json.ParseString(text);
        return new ApiResult(code is >= 200 and < 300, code, data, code < 400 ? "" : text);
    }
}

Call it from anywhere:

var res := await Api.request_json("/scores", HTTPClient.METHOD_POST, {"name": "Ada", "score": 1200})
if not res.ok:
    show_error(res.error)
var api = GetNode<Api>("/root/Api");
var res = await api.RequestJson("/scores", HttpClient.Method.Post, new Godot.Collections.Dictionary { { "name", "Ada" }, { "score", 1200 } });
if (!res.Ok)
    ShowError(res.Error);

Retries with exponential backoff, request cancellation on scene change, and an auth refresh flow: references/rest-client-patterns.md.


4. JSON Pitfalls

  • Every JSON number parses as float. {"id": 5} gives 5.0. Cast before using it as an index, enum, or dictionary key: int(data["id"]) in GDScript, data["id"].AsInt32() in C#. == compares 5.0 == 5 as true, but match patterns are type-strict, and a dictionary keyed by 5 has no entry for 5.0.
  • JSON.parse_string() returns null on bad input and logs an error. When you need the line and message, use an instance: var json := JSON.new(); if json.parse(text) != OK: print(json.get_error_message(), json.get_error_line()).
  • Validate shape before indexing. Check data is Dictionary and data.has("items") — a server error page in HTML parses to null, and an empty array is still ok.
  • A stringify → parse round trip turns ints into floats (stringify itself writes 5, the parse brings back 5.0), and JSON.stringify(obj) only works on Variant types — convert custom objects or Resources to a Dictionary first.

5. Downloads, Uploads, and Images

TaskHow
Save straight to diskSet http.download_file = "user://cache/pack.zip" before request(); the body is written there, not returned
Progress barPoll get_downloaded_bytes() against get_body_size() (-1 when the server sends no length, and always -1 on Web)
Cap the sizebody_size_limit (bytes) → RESULT_BODY_SIZE_LIMIT_EXCEEDED
Binary uploadrequest_raw(url, headers, HTTPClient.METHOD_PUT, bytes) with a Content-Type header
Show a remote imageImage.new().load_png_from_buffer(body) (or _jpg_, _webp_), then ImageTexture.create_from_image(image)

Image, form-encoded, and multipart recipes with C#: references/downloads-and-uploads.md.


6. TLS and Secrets

  • HTTPS works out of the box against the bundled CA list. For a self-signed or private CA, load the .crt and pass TLSOptions.client(cert) via http.set_tls_options(). TLSOptions.client_unsafe() skips validation — never ship it.
  • Anything in the export is readable. API keys in scripts, project.godot, or an encrypted PCK can be pulled out by a determined player. Keep privileged keys (payment, admin, third-party paid APIs) on a server you control and have the game call that server.
  • Per-player tokens (session or refresh tokens) can live in user://; treat them as revocable, not secret.
  • Send credentials in headers or a POST body, never as GET query parameters — URLs end up in logs.
  • TLS 1.2 is supported everywhere and TLS 1.3 from Godot 4.4; on 4.3 a TLS 1.3-only server fails with RESULT_TLS_HANDSHAKE_ERROR, as does any server limited to TLS 1.1 or older.

7. Platform Notes

PlatformGotcha
AndroidEnable the INTERNET permission in the export preset, or every request fails
WebRequests go through the browser: the server must send CORS headers (Access-Control-Allow-Origin), and plain http:// from an https:// page is blocked as mixed content. See web-export
Dedicated serverSame API; run requests from the server so keys never reach clients — see dedicated-server

8. WebSocket Client

Use WebSocketPeer for a persistent, server-pushed event stream; use HTTPRequest for request/response. Nothing happens unless you call poll() every frame (_process). Never put long-lived tokens in the URL — URLs end up in logs.

See references/websocket-client.md for the GDScript and C# client, wss://, auth, heartbeat, and reconnect rules.


9. Common Pitfalls

SymptomCauseFix
request() returns ERR_BUSY (44)Reused one HTTPRequest for overlapping callsOne node per in-flight request
request() returns ERR_UNCONFIGUREDNode not in the tree yetadd_child() before request()
Request hangs forevertimeout left at 0.0Set timeout (5–15 s) on API calls; big downloads need a stall check instead (timeout covers the whole transfer)
Works in editor, fails on AndroidMissing INTERNET permissionTick it in the export preset
Works on desktop, fails on WebCORS or mixed contentFix server headers; use HTTPS/WSS
id is 5.0, not 5JSON numbers are floatsCast with int()
Body empty, file appears in user://download_file was setClear it for in-memory requests
Request never completes after a scene changeThe HTTPRequest was a child of a node that got freed, so the coroutine never resumesParent requests to an autoload (Section 3); don't touch freed nodes after the await

10. Checklist

  • One HTTPRequest per concurrent request; each is queue_free()d after completion
  • timeout set on API calls; large downloads use a stall check or cancel button instead
  • Both the transport result and the HTTP response_code are checked
  • JSON numbers cast to int where integers are expected; parsed data shape validated
  • No privileged API keys in the client build; a server proxies paid or admin APIs
  • Credentials sent in headers or POST bodies over HTTPS, never in GET URLs
  • TLSOptions.client_unsafe() not used outside local testing
  • Android preset has the INTERNET permission
  • Web build tested against the real server (CORS headers, HTTPS/WSS only)
  • WebSocket poll() called every frame; reconnects back off
  • Failures surface to the player (retry button, offline message) instead of failing silently

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

Use when implementing AI movement — NavigationAgent2D/3D, steering behaviors, behavior trees, and patrol patterns

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

Use when implementing animations — AnimationPlayer, AnimationTree, blend trees, state machines, sprite animation, and code-driven animation

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

jame581/GodotPrompter8792026년 10월 24일 업데이트

jame581의 스킬 모두 보기

이 스킬의 문제 신고