Skip to content

HTTP Client

Snowpulse provides an asynchronous HTTP API through AppContext::httpClient() when SNOWPULSE_HAS_HTTP=1.

Platform Backend
Windows/macOS libcurl worker thread
Web (FULL) Emscripten Fetch
iOS libcurl worker thread; current build is HTTP-only
Android asynchronous OkHttp over JNI using the Android trust store
Web (PLAYABLE_AD) Compiled out

App calls IHttpClient::update() each active frame and dispatches completed callbacks from there. Native network work remains off the application thread.

Requests

The supported methods are:

  • get(path, params)
  • head(path, params)
  • del(path, params)
  • post(path, body, contentType) and a query-parameter overload
  • put(path, body, contentType) and a query-parameter overload
  • patch(path, body, contentType) and a query-parameter overload

Set baseUrl() once, then pass relative paths, or pass an absolute HTTP(S) URL to bypass the base URL.

#if SNOWPULSE_HAS_HTTP
auto* http = context()->httpClient();
http->baseUrl("https://api.example.com");

snowpulse::HttpParams params;
params.add("q", "reference");
params.add("page", 2);
params.add("includeArchived", false);

http->get("/search", params)->onComplete(
    [](const snowpulse::HttpClientResult& result) {
        if (result.ok()) {
            std::printf("%s\n", result.body.c_str());
        }
        else if (!result.cancelled) {
            std::fprintf(stderr, "HTTP %ld: %s\n",
                         result.status,
                         result.error.c_str());
        }
    });
#endif

HttpParams::add() accepts strings, C strings, booleans, integers, floats, and doubles. Keys and values are percent-encoded and repeated entries are retained.

POST Bodies

Pass the serialized body and content type directly to post(). Its overload with HttpParams appends an encoded query string while keeping the body unchanged. The same body/content-type shape is available for put() and patch().

#if SNOWPULSE_HAS_HTTP
auto* http = context()->httpClient();

snowpulse::HttpParams query;
query.add("validateOnly", true);

const std::string body = R"({"name":"example","enabled":true})";
http->post("/documents", query, body, "application/json")
    ->onComplete([](const snowpulse::HttpClientResult& result) {
        if (!result.ok()) {
            std::fprintf(stderr, "POST failed: %s\n", result.error.c_str());
        }
    });
#endif

Results

HttpClientResult contains:

  • status: HTTP status code, or 0 when no response code is available
  • body: response bytes stored in a std::string
  • headers: response headers (normalized to lowercase by libcurl; OkHttp preserves received names; the web backend currently leaves this empty)
  • error: transport/backend error text
  • cancelled: whether cancellation was requested

ok() is true only for a non-cancelled, error-free 2xx response.

Request Options and Cancellation

HttpRequest exposes header(key, value), timeout(seconds), and cancel(). Cancellation is cooperative: native libcurl requests abort through their progress callback, Android cancels its OkHttp Call, and web Fetch reports the request as cancelled when its completion callback returns.

header() and timeout() only change a request before its backend calls snapshotAndMarkStarted(). The current Android backend deliberately waits for the next update(), so fluent options set immediately after request creation are reliable there:

#if SNOWPULSE_HAS_HTTP && defined(SNOW_PLATFORM_ANDROID)
auto* request = context()->httpClient()->post(
    "/documents", R"({"name":"example"})", "application/json");
request->header("X-Client-Version", "1.0");
request->timeout(5.0f);
request->onComplete([](const snowpulse::HttpClientResult& result) {
    // Inspect result.ok(), result.cancelled, status, body, and error.
});
#endif

Desktop and iOS worker threads may snapshot immediately after enqueue, so a fluent option call can race with startup. Web snapshots synchronously inside get()/post() before returning. Consequently, custom headers and timeouts are not currently portable request options; the body contentType argument is portable. This limitation does not affect onComplete() or cancel().

Requests are owned by the HTTP client. A returned HttpRequest* is valid only until its completed callback is dispatched and the client removes it. Keep the pointer only while the request is pending, clear it in the callback, and do not capture an object that may be destroyed before completion.

auto* pending = context()->httpClient()->get("/documents", {});
pending->onComplete([](const snowpulse::HttpClientResult& result) {
    if (result.cancelled) {
        // The cancellation result has reached the application thread.
    }
});

// Before completion, when the surrounding operation no longer needs it:
pending->cancel();

On Android, dispatch is deferred until the next application-thread update(), so fluent header(), timeout(), and onComplete() calls are included before OkHttp starts. Redirects are enabled, cleartext HTTP is rejected by default, and TLS uses Android's system certificate store. Completion callbacks always dispatch from the Snowpulse application thread and are dropped safely after request or Activity destruction.