Back to Fastled

Client.ino - HTTP Client Tutorial

examples/Asio/Client/README.md

3.10.38.8 KB
Original Source

Client.ino - HTTP Client Tutorial

Educational tutorial for FastLED's HTTP fetch API (fl::fetch) with explicit types for learning.

Overview

This comprehensive tutorial demonstrates 4 different async approaches for HTTP networking in FastLED:

  1. Promise-based - JavaScript-like .then() and .catch_() callbacks
  2. Await-based - Synchronous-style fl::await_top_level() pattern
  3. JSON Promise - Promise pattern with automatic JSON parsing
  4. JSON Await - Await pattern with JSON response handling

The example cycles through all 4 approaches every 10 seconds with LED feedback to help you understand different async patterns.

Educational Focus

All types are explicitly declared (no auto) to help you understand FastLED's async type system:

  • fl::promise<T> - Represents a future value
  • fl::result<T> - Wraps success value or error
  • fl::response - HTTP response with status/headers/body
  • fl::optional<T> - May or may not contain a value
  • fl::Error - Error information
  • fl::json - JSON parsing with safe access

LED Status Indicators

ColorApproachMeaning
GreenPromise-basedHTTP success (.then)
BlueAwait-basedHTTP success (await)
BlueJSON PromiseJSON parsed (promise)
CyanJSON AwaitJSON parsed (await)
RedAnyNetwork or HTTP error
YellowJSON PromiseNon-JSON response
OrangeJSON AwaitNon-JSON response

Usage

1. Compile the Tutorial

bash
# For WASM (recommended - full networking)
bash compile wasm --examples Client

# For POSIX (host-based testing)
bash compile posix --examples Client

2. Run the Tutorial

WASM (browser):

bash
bash run wasm Client

POSIX (console):

bash
.build/meson-quick/examples/Client.exe

3. With Local Test Server (Optional)

For testing without internet connection:

bash
# Terminal 1: Start local test server
uv run python examples/Asio/Client/test_server.py

# Terminal 2: Modify tutorial to use localhost:8081
# (Edit ClientReal.h to change URLs from fastled.io/httpbin.org to localhost:8081)

Async Approaches Explained

APPROACH 1: Promise-based (.then/.catch)

JavaScript-like async pattern with method chaining:

cpp
fl::promise<fl::response> promise = fl::fetch_get("http://fastled.io");
promise.then([](const fl::response& response) {
    if (response.ok()) {
        FL_WARN("Success: " << response.text());
    }
}).catch_([](const fl::Error& error) {
    FL_WARN("Error: " << error.message);
});

When to use:

  • ✅ Non-blocking UI/LED updates during request
  • ✅ Multiple parallel requests
  • ✅ Event-driven architectures

APPROACH 2: Await-based (fl::await_top_level)

Synchronous-style code that blocks until completion:

cpp
fl::promise<fl::response> promise = fl::fetch_get("http://fastled.io");
fl::result<fl::response> result = fl::await_top_level(promise);

if (result.ok()) {
    const fl::response& response = result.value();
    FL_WARN("Success: " << response.text());
} else {
    FL_WARN("Error: " << result.error_message());
}

When to use:

  • ✅ Simple request/response patterns
  • ✅ Traditional imperative code style
  • ✅ Arduino loop() context (safe to block)

⚠️ CRITICAL: await_top_level() blocks execution - ONLY use in loop(), NEVER in callbacks!

APPROACH 3: JSON Promise

Promise pattern with automatic JSON parsing:

cpp
fl::fetch_get("https://httpbin.org/json").then([](const fl::response& response) {
    if (response.is_json()) {
        fl::json data = response.json();
        fl::string author = data["slideshow"]["author"] | fl::string("unknown");
        FL_WARN("Author: " << author);
    }
}).catch_([](const fl::Error& error) {
    FL_WARN("Error: " << error.message);
});

Features:

  • Automatic Content-Type detection
  • Safe access with default values (| operator)
  • Nested object/array access
  • No manual parsing needed

APPROACH 4: JSON Await

Await pattern with JSON responses:

cpp
fl::promise<fl::response> promise = fl::fetch_get("https://httpbin.org/get");
fl::result<fl::response> result = fl::await_top_level(promise);

if (result.ok() && result.value().is_json()) {
    fl::json data = result.value().json();
    fl::string origin = data["origin"] | fl::string("unknown");
    FL_WARN("Origin: " << origin);
}

Combines:

  • Synchronous-style code (await)
  • Automatic JSON parsing
  • Safe nested access

API Reference

fl::fetch_get(url, options)

Make HTTP GET request:

cpp
// Simple GET
fl::promise<fl::response> promise = fl::fetch_get("http://example.com");

// With options (timeout, headers, etc.)
fl::fetch_options opts("");
opts.timeout(5000).header("User-Agent", "FastLED");
fl::promise<fl::response> promise = fl::fetch_get("http://example.com", opts);

fl::response

HTTP response object:

Methods:

  • int status() - HTTP status code (200, 404, etc.)
  • const string& status_text() - Status text ("OK", "Not Found")
  • bool ok() - True if status 200-299
  • const string& text() - Response body as string
  • fl::json json() - Parse response as JSON (cached)
  • bool is_json() - Check if response is JSON
  • optional<string> get_content_type() - Get Content-Type header

fl::promise<T>

Represents future value:

Methods:

  • promise<T>& then(callback) - Success handler
  • promise<T>& catch_(callback) - Error handler

fl::result<T>

Wraps success or error:

Methods:

  • bool ok() - Check if successful
  • const T& value() - Get success value (throws if error)
  • const string& error_message() - Get error message

fl::json

JSON object with safe access:

Methods:

  • Json operator[](key) - Access nested value
  • T operator|(default) - Get value or default
  • bool contains(key) - Check if key exists
  • bool is_array() - Check if JSON array
  • size_t size() - Array/object size

Tutorial Behavior

The example automatically cycles through all 4 approaches:

0-10s:  Approach 1 (Promise)        → Green LEDs on success
10-20s: Approach 2 (Await)          → Blue LEDs on success
20-30s: Approach 3 (JSON Promise)   → Blue LEDs on success
30-40s: Approach 4 (JSON Await)     → Cyan LEDs on success
40s+:   Repeats from Approach 1

Watch the serial output and LED colors to understand each approach!

Platform Support

  • WASM - Full networking via browser fetch API
  • POSIX (Linux, macOS) - Native sockets
  • Windows - Native sockets with fcntl emulation
  • ⚠️ ESP32 - Limited support (different networking API)
  • Arduino - No networking (mock responses only)

Common Patterns

Error Handling

Promise-based:

cpp
fetch_get(url).then([](const fl::response& r) {
    if (!r.ok()) {
        FL_WARN("HTTP error: " << r.status());
    }
}).catch_([](const fl::Error& e) {
    FL_WARN("Network error: " << e.message);
});

Await-based:

cpp
fl::result<fl::response> result = fl::await_top_level(fetch_get(url));
if (!result.ok()) {
    FL_WARN("Request failed: " << result.error_message());
}

Custom Headers

cpp
fl::fetch_options opts("");
opts.header("Authorization", "Bearer token123")
    .header("X-Custom", "value")
    .timeout(10000);  // 10 seconds

fl::fetch_get("http://api.example.com/data", opts);

Multiple Parallel Requests

cpp
// Start multiple requests (non-blocking)
fl::fetch_get("http://api1.com/data").then(handle_api1);
fl::fetch_get("http://api2.com/data").then(handle_api2);
fl::fetch_get("http://api3.com/data").then(handle_api3);

// All requests run in parallel!

Troubleshooting

WASM: "Network request failed"

Cause: CORS policy blocking request

Solution: Use CORS-enabled endpoints or run local test server:

bash
uv run python examples/Asio/Client/test_server.py

POSIX/Windows: "Connection refused"

Cause: Firewall or no internet connection

Solutions:

  1. Check firewall settings
  2. Use local test server (localhost bypasses firewall)
  3. Try different URL

"await_top_level blocked forever"

Cause: Used await_top_level() in callback context

Solution: Only use await_top_level() in loop(), use .then() in callbacks

Next Steps

  • Validation Testing: See ClientValidation.ino for automated test suite
  • Server Tutorial: See Server.ino for HTTP server example
  • Advanced: Combine fetch with LED effects (e.g., weather-reactive LEDs)
  • Production: Add retry logic, caching, rate limiting

See Also

  • ClientValidation.ino - Automated test suite for fetch API
  • Server.ino - HTTP server tutorial
  • src/fl/fetch.h - Fetch API source code
  • docs/agents/cpp-standards.md - C++ coding standards