src/fl/stl/asio/http/README.md
This module provides HTTP/1.1 streaming transport for FastLED's JSON-RPC system, enabling bidirectional communication over HTTP using chunked transfer encoding. The transport supports three RPC modes: SYNC (immediate response), ASYNC (ACK + later result), and ASYNC_STREAM (ACK + multiple updates + final result).
Remote (application layer)
↓
HttpStreamTransport (base transport interface)
↓
HttpStreamClient / HttpStreamServer (platform-specific implementations)
↓
HttpConnection (connection lifecycle)
↓
ChunkedReader / ChunkedWriter (HTTP/1.1 encoding)
↓
TCP Socket (platform-specific: POSIX, ESP-IDF, etc.)
Purpose: Parse and generate HTTP/1.1 chunked transfer encoding
Files:
chunked_encoding.h - Interface declarationschunked_encoding.cpp.hpp - ImplementationChunkedReader API:
class ChunkedReader {
public:
// Constructor
ChunkedReader();
// Feed data from socket
void feed(const uint8_t* data, size_t len);
// Check if complete chunk is available
bool hasChunk() const;
// Read next chunk (returns empty if none available)
fl::optional<fl::vector<uint8_t>> readChunk();
// Check if final chunk (size 0) received
bool isFinal() const;
// Reset state
void reset();
private:
enum State {
READ_SIZE, // Reading chunk size (hex + CRLF)
READ_DATA, // Reading chunk data
READ_TRAILER, // Reading trailing CRLF
FINAL // Final chunk received
};
State mState;
fl::vector<uint8_t> mBuffer;
size_t mChunkSize;
size_t mBytesRead;
};
ChunkedWriter API:
class ChunkedWriter {
public:
// Constructor
ChunkedWriter();
// Write chunk (returns formatted chunk data)
fl::vector<uint8_t> writeChunk(const uint8_t* data, size_t len);
// Write final chunk (size 0)
fl::vector<uint8_t> writeFinal();
private:
// Format chunk: <size-hex>\r\n<data>\r\n
static fl::vector<uint8_t> formatChunk(const uint8_t* data, size_t len);
};
Chunked Encoding Format:
<chunk-size-hex>\r\n
<chunk-data>
\r\n
<chunk-size-hex>\r\n
<chunk-data>
\r\n
...
0\r\n
\r\n
Purpose: Parse HTTP/1.1 request and response messages
Files:
http_parser.h - Interface declarationshttp_parser.cpp.hpp - ImplementationHttpRequestParser API:
struct HttpRequest {
fl::string method; // "GET", "POST", etc.
fl::string uri; // "/rpc"
fl::string version; // "HTTP/1.1"
fl::map<fl::string, fl::string> headers;
fl::vector<uint8_t> body; // Decoded body (if chunked, already decoded)
};
class HttpRequestParser {
public:
// Constructor
HttpRequestParser();
// Feed data from socket
void feed(const uint8_t* data, size_t len);
// Check if request is complete
bool isComplete() const;
// Get parsed request
fl::optional<HttpRequest> getRequest();
// Reset state
void reset();
private:
enum State {
READ_REQUEST_LINE, // "POST /rpc HTTP/1.1\r\n"
READ_HEADERS, // "Header: Value\r\n" ... "\r\n"
READ_BODY, // Body content (chunked or Content-Length)
COMPLETE // Request fully parsed
};
State mState;
fl::vector<uint8_t> mBuffer;
HttpRequest mRequest;
ChunkedReader mChunkedReader; // For Transfer-Encoding: chunked
size_t mContentLength;
bool mIsChunked;
};
HttpResponseParser API:
struct HttpResponse {
fl::string version; // "HTTP/1.1"
int statusCode; // 200, 404, etc.
fl::string reasonPhrase; // "OK", "Not Found", etc.
fl::map<fl::string, fl::string> headers;
fl::vector<uint8_t> body; // Decoded body
};
class HttpResponseParser {
public:
// Constructor
HttpResponseParser();
// Feed data from socket
void feed(const uint8_t* data, size_t len);
// Check if response is complete
bool isComplete() const;
// Get parsed response
fl::optional<HttpResponse> getResponse();
// Reset state
void reset();
private:
enum State {
READ_STATUS_LINE, // "HTTP/1.1 200 OK\r\n"
READ_HEADERS, // "Header: Value\r\n" ... "\r\n"
READ_BODY, // Body content (chunked or Content-Length)
COMPLETE // Response fully parsed
};
State mState;
fl::vector<uint8_t> mBuffer;
HttpResponse mResponse;
ChunkedReader mChunkedReader;
size_t mContentLength;
bool mIsChunked;
};
Purpose: Manage HTTP connection lifecycle with reconnection and heartbeat
Files:
connection.h - Interface declarationsconnection.cpp.hpp - ImplementationHttpConnection API:
class HttpConnection {
public:
enum State {
DISCONNECTED, // Not connected
CONNECTING, // Connection in progress
CONNECTED, // Connected and ready
RECONNECTING, // Auto-reconnect in progress
CLOSED // Connection permanently closed
};
// Constructor
HttpConnection(const char* host, uint16_t port);
// Connection management
bool connect(); // Initiate connection
void disconnect(); // Close connection
void close(); // Close permanently (no reconnect)
bool isConnected() const; // Check if connected
State getState() const; // Get current state
// Auto-reconnect settings
void setAutoReconnect(bool enable);
void setReconnectInterval(uint32_t minMs, uint32_t maxMs);
// Heartbeat/keepalive settings
void setHeartbeatInterval(uint32_t intervalMs);
void setTimeout(uint32_t timeoutMs);
// Update loop (handles reconnection, heartbeat, timeout detection)
void update(uint32_t currentTimeMs);
// Socket I/O (platform-specific implementations provided by subclasses)
virtual int send(const uint8_t* data, size_t len) = 0;
virtual int recv(uint8_t* buffer, size_t maxLen) = 0;
virtual bool isSocketConnected() const = 0;
protected:
// Platform-specific connection (override in subclasses)
virtual bool platformConnect() = 0;
virtual void platformDisconnect() = 0;
private:
State mState;
fl::string mHost;
uint16_t mPort;
// Auto-reconnect
bool mAutoReconnect;
uint32_t mReconnectIntervalMin; // Min backoff (default: 1000ms)
uint32_t mReconnectIntervalMax; // Max backoff (default: 30000ms)
uint32_t mReconnectIntervalCurrent;
uint32_t mLastReconnectAttempt;
int mReconnectAttempts;
// Heartbeat
uint32_t mHeartbeatInterval; // Default: 30000ms (30s)
uint32_t mLastHeartbeat;
// Timeout detection
uint32_t mTimeout; // Default: 60000ms (60s)
uint32_t mLastActivity;
};
Reconnection Logic:
Heartbeat Logic:
mHeartbeatInterval ms (default 30s)mTimeout ms (default 60s)Purpose: Platform-specific HTTP client/server implementations using POSIX sockets
Files:
native_client.h - Native client interfacenative_client.cpp.hpp - Native client implementationnative_server.h - Native server interfacenative_server.cpp.hpp - Native server implementationNativeHttpClient API:
class NativeHttpClient : public HttpConnection {
public:
// Constructor
NativeHttpClient(const char* host, uint16_t port);
~NativeHttpClient();
// Socket I/O (POSIX implementation)
int send(const uint8_t* data, size_t len) override;
int recv(uint8_t* buffer, size_t maxLen) override;
bool isSocketConnected() const override;
protected:
// Platform-specific connection (POSIX sockets)
bool platformConnect() override;
void platformDisconnect() override;
private:
int mSocket; // POSIX socket descriptor
};
NativeHttpServer API:
struct HttpClientConnection {
int socket; // Client socket descriptor
fl::string remoteAddr; // Client IP address
uint16_t remotePort; // Client port
HttpRequestParser parser; // Request parser for this client
ChunkedWriter writer; // Response writer for this client
};
class NativeHttpServer {
public:
// Constructor
NativeHttpServer(uint16_t port);
~NativeHttpServer();
// Server lifecycle
bool start(); // Start listening
void stop(); // Stop server
bool isListening() const;
// Client management
void update(); // Accept new clients, read data
fl::vector<int> getClientIds() const;
// Request handling
fl::optional<HttpRequest> readRequest(int clientId);
void writeResponse(int clientId, const HttpResponse& response);
void writeChunk(int clientId, const uint8_t* data, size_t len);
void writeChunkFinal(int clientId);
// Close client
void closeClient(int clientId);
private:
uint16_t mPort;
int mListenSocket;
fl::map<int, HttpClientConnection> mClients;
// Accept new client connection
void acceptClient();
};
Purpose: Base class for HTTP streaming transport, implements RequestSource/ResponseSink for Remote
Files:
stream_transport.h - Interface declarationsstream_transport.cpp.hpp - ImplementationHttpStreamTransport API:
class HttpStreamTransport {
public:
// Constructor
HttpStreamTransport(const char* host, uint16_t port);
virtual ~HttpStreamTransport() = default;
// Connection management
virtual bool connect() = 0;
virtual void disconnect() = 0;
virtual bool isConnected() const = 0;
// RequestSource implementation (for Remote)
fl::optional<fl::json> readRequest();
// ResponseSink implementation (for Remote)
void writeResponse(const fl::json& response);
// Update loop (handles reconnection, heartbeat, I/O)
virtual void update(uint32_t currentTimeMs) = 0;
// Configuration
void setAutoReconnect(bool enable);
void setHeartbeatInterval(uint32_t intervalMs);
void setTimeout(uint32_t timeoutMs);
protected:
// Platform-specific socket I/O
virtual int send(const uint8_t* data, size_t len) = 0;
virtual int recv(uint8_t* buffer, size_t maxLen) = 0;
// Request/response parsing helpers
fl::optional<fl::json> parseJsonFromChunk(const fl::vector<uint8_t>& chunk);
fl::vector<uint8_t> formatJsonChunk(const fl::json& json);
HttpConnection* mConnection;
ChunkedReader mReader;
ChunkedWriter mWriter;
fl::queue<fl::json> mRequestQueue; // Buffered requests
fl::queue<fl::json> mResponseQueue; // Buffered responses
};
Purpose: Client-side HTTP streaming for RPC
Files:
stream_client.h - Interface declarationsstream_client.cpp.hpp - ImplementationHttpStreamClient API:
class HttpStreamClient : public HttpStreamTransport {
public:
// Constructor
HttpStreamClient(const char* host, uint16_t port);
~HttpStreamClient();
// Connection management (overrides)
bool connect() override;
void disconnect() override;
bool isConnected() const override;
// Update loop (overrides)
void update(uint32_t currentTimeMs) override;
// Send RPC request (client → server)
void sendRequest(const fl::json& request);
// Receive RPC response (server → client)
fl::optional<fl::json> receiveResponse();
protected:
// Platform-specific socket I/O (delegates to NativeHttpClient)
int send(const uint8_t* data, size_t len) override;
int recv(uint8_t* buffer, size_t maxLen) override;
private:
NativeHttpClient mClient;
HttpResponseParser mResponseParser;
bool mRequestPending;
};
Usage Example (Client):
// Create HTTP client
auto client = fl::make_shared<HttpStreamClient>("localhost", 8080);
client->setAutoReconnect(true);
client->setHeartbeatInterval(30000); // 30s
// Create Remote with client as transport
fl::Remote remote(
[&client]() { return client->readRequest(); },
[&client](const fl::json& r) { client->writeResponse(r); }
);
// Update loop
while (true) {
client->update(millis());
remote.update(millis());
delay(10);
}
Purpose: Server-side HTTP streaming for RPC
Files:
stream_server.h - Interface declarationsstream_server.cpp.hpp - ImplementationHttpStreamServer API:
class HttpStreamServer : public HttpStreamTransport {
public:
// Constructor
HttpStreamServer(uint16_t port);
~HttpStreamServer();
// Server lifecycle
bool start();
void stop();
bool isListening() const;
// Connection management (overrides)
bool connect() override; // No-op for server
void disconnect() override; // Close all clients
bool isConnected() const override; // True if any client connected
// Update loop (overrides)
void update(uint32_t currentTimeMs) override;
protected:
// Socket I/O (multi-client)
int send(const uint8_t* data, size_t len) override;
int recv(uint8_t* buffer, size_t maxLen) override;
private:
NativeHttpServer mServer;
fl::vector<int> mClientIds;
int mCurrentClient; // Client to read from (round-robin)
};
Usage Example (Server):
// Create HTTP server
auto server = fl::make_shared<HttpStreamServer>(8080);
server->start();
// Create Remote with server as transport
fl::Remote remote(
[&server]() { return server->readRequest(); },
[&server](const fl::json& r) { server->writeResponse(r); }
);
// Bind RPC methods
remote.bind("add", [](int a, int b) { return a + b; });
remote.bindAsync("longTask", [](ResponseSend& send, const Json& params) {
send.send(Json::object().set("ack", true));
// ... do work ...
send.send(Json::object().set("value", 42));
});
// Update loop
while (true) {
server->update(millis());
remote.update(millis());
delay(10);
}
POST /rpc HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
<chunk-size>\r\n
{"jsonrpc":"2.0","method":"add","params":[1,2],"id":1}\r\n
0\r\n
\r\n
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
<chunk-size>\r\n
{"jsonrpc":"2.0","result":3,"id":1}\r\n
0\r\n
\r\n
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"ack":true},"id":1}\r\n
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"value":42},"id":1}\r\n
0\r\n
\r\n
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"ack":true},"id":1}\r\n
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"update":10},"id":1}\r\n
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"update":20},"id":1}\r\n
<chunk-size>\r\n
{"jsonrpc":"2.0","result":{"value":100,"stop":true},"id":1}\r\n
0\r\n
\r\n
Send periodically to keep connection alive:
<chunk-size>\r\n
{"jsonrpc":"2.0","method":"rpc.ping","id":null}\r\n
Expected pong response:
<chunk-size>\r\n
{"jsonrpc":"2.0","result":"pong","id":null}\r\n
[DISCONNECTED] --connect()--> [CONNECTING] --success--> [CONNECTED]
^ | |
| fail error
| | |
+--<autoReconnect=false>---+ |
| |
+--<autoReconnect=true>---> [RECONNECTING] <---------+
|
exponential backoff
(1s, 2s, 4s, ..., 30s)
|
retry connect()
States:
DISCONNECTED: Initial state, no connectionCONNECTING: Connection attempt in progressCONNECTED: Connection established, ready for I/ORECONNECTING: Auto-reconnect in progress (exponential backoff)CLOSED: Connection permanently closed (no reconnect)Timeouts:
mTimeout (default 60s): Max idle time before disconnectmHeartbeatInterval (default 30s): Ping intervalmReconnectInterval (dynamic): Backoff delay between reconnect attemptsUser Code
|
v
Remote::sendRequest(method, params)
|
v
HttpStreamClient::writeResponse(jsonrpc)
|
v
ChunkedWriter::writeChunk(json)
|
v
NativeHttpClient::send(chunk)
|
v
TCP Socket → Server
TCP Socket ← Client
|
v
NativeHttpServer::recv(data)
|
v
HttpRequestParser::feed(data)
|
v
ChunkedReader::readChunk()
|
v
HttpStreamServer::readRequest()
|
v
Remote::update() → Rpc::handle(request)
|
v
User RPC Function (sync/async/stream)
|
v
ResponseSink::writeResponse(result)
|
v
ChunkedWriter::writeChunk(result)
|
v
NativeHttpServer::send(chunk)
|
v
TCP Socket → Client
Supported:
Implementation:
NativeHttpClient uses POSIX socket(), connect(), send(), recv()NativeHttpServer uses POSIX bind(), listen(), accept()select() or poll() for multi-clientPlanned:
Not Implemented Yet: ESP32 support will be added in future iterations
examples/Asio/RpcServer/)examples/Asio/RpcClient/)examples/Asio/RpcBidirectional/)This is a basic transport layer for development/testing. The following security features are NOT implemented:
For Production Use: Add HTTPS (TLS), authentication (tokens, OAuth), and rate limiting
Status: ✅ Architecture design complete Next: Implement chunked encoding parser (Task 2.2)