FastLED 3.9.15
Loading...
Searching...
No Matches
stream_server.cpp.hpp
Go to the documentation of this file.
1#pragma once
2
3#ifdef FASTLED_HAS_NETWORKING
4
6#include "fl/stl/string.h"
7#include "fl/stl/stdint.h"
8#include "fl/log/log.h"
9// Note: fl/stl/cstdio.h intentionally NOT included — workaround for
10// zackees/zccache#619 (Windows PCH path-spelling drift). Dead include.
11#include "fl/stl/noexcept.h"
12namespace fl {
13namespace net {
14namespace http {
15
16HttpStreamServer::HttpStreamServer(u16 port, u32 heartbeatIntervalMs)
17 : HttpStreamTransport("0.0.0.0", port, heartbeatIntervalMs)
18 , mPort(port)
19 , mLastProcessedClientId(0) {
20 // Create native server with default connection config
21 ConnectionConfig config;
22 mNativeServer = fl::make_unique<NativeHttpServer>(mPort, config);
23
24 // Pre-allocate receive buffer (16KB should be enough for most requests)
25 mRecvBuffer.reserve(16384);
26}
27
28HttpStreamServer::~HttpStreamServer() FL_NOEXCEPT {
29 disconnect();
30}
31
32bool HttpStreamServer::connect() {
33 // If already listening, return true
34 if (isConnected()) {
35 return true;
36 }
37
38 // Start listening
39 if (!mNativeServer->start()) {
40 return false;
41 }
42
43 // Mark connection as established in base class
44 mConnection.onConnected();
45
46 return true;
47}
48
49void HttpStreamServer::disconnect() {
50 if (mNativeServer) {
51 mNativeServer->stop();
52 }
53 mClientStates.clear();
54 mConnection.onDisconnected();
55}
56
57bool HttpStreamServer::isConnected() const {
58 return mNativeServer && mNativeServer->isListening();
59}
60
61u16 HttpStreamServer::port() const {
62 return mNativeServer ? mNativeServer->port() : 0;
63}
64
65void HttpStreamServer::acceptClients() {
66 if (!isConnected()) {
67 return;
68 }
69
70 // Accept new clients (non-blocking)
71 mNativeServer->acceptClients();
72
73 // Get list of all client IDs
74 fl::vector<u32> clientIds = mNativeServer->getClientIds();
75
76 // Process each client's HTTP header if not already done
77 for (u32 clientId : clientIds) {
78 ClientState* state = getOrCreateClientState(clientId);
79 if (!state) {
80 continue;
81 }
82
83 // If HTTP headers not exchanged yet, do it now
84 if (!state->httpHeaderReceived) {
85 if (readHttpRequestHeader(clientId)) {
86 // Send HTTP response header
87 if (!sendHttpResponseHeader(clientId)) {
88 // Failed to send response, disconnect client
89 disconnectClient(clientId);
90 }
91 }
92 }
93 }
94
95 // Clean up disconnected clients
96 fl::vector<u32> activeClientIds = mNativeServer->getClientIds();
97 fl::vector<u32> toRemove;
98
99 for (auto& pair : mClientStates) {
100 u32 clientId = pair.first;
101 bool found = false;
102 for (u32 activeId : activeClientIds) {
103 if (activeId == clientId) {
104 found = true;
105 break;
106 }
107 }
108 if (!found) {
109 toRemove.push_back(clientId);
110 }
111 }
112
113 for (u32 clientId : toRemove) {
114 removeClientState(clientId);
115 }
116}
117
118size_t HttpStreamServer::getClientCount() const {
119 return mNativeServer ? mNativeServer->getClientCount() : 0;
120}
121
122void HttpStreamServer::disconnectClient(u32 clientId) {
123 if (mNativeServer) {
124 mNativeServer->disconnectClient(clientId);
125 }
126 removeClientState(clientId);
127}
128
129fl::vector<u32> HttpStreamServer::getClientIds() const {
130 return mNativeServer ? mNativeServer->getClientIds() : fl::vector<u32>();
131}
132
133int HttpStreamServer::sendData(fl::span<const u8> data) {
134 if (!isConnected()) {
135 return -1;
136 }
137
138 // Broadcast to all clients
139 mNativeServer->broadcast(data);
140 return static_cast<int>(data.size());
141}
142
143int HttpStreamServer::recvData(fl::span<u8> buffer) {
144 if (!isConnected()) {
145 return -1;
146 }
147
148 // Get list of all client IDs
149 fl::vector<u32> clientIds = mNativeServer->getClientIds();
150 if (clientIds.empty()) {
151 return 0; // No clients connected
152 }
153
154 // Round-robin through clients to avoid starvation
155 // Start from the last processed client + 1
156 size_t startIdx = 0;
157 for (size_t i = 0; i < clientIds.size(); i++) {
158 if (clientIds[i] == mLastProcessedClientId) {
159 startIdx = (i + 1) % clientIds.size();
160 break;
161 }
162 }
163
164 // Try each client starting from startIdx
165 for (size_t offset = 0; offset < clientIds.size(); offset++) {
166 size_t idx = (startIdx + offset) % clientIds.size();
167 u32 clientId = clientIds[idx];
168
169 ClientState* state = getOrCreateClientState(clientId);
170 if (!state || !state->httpHeaderReceived || !state->httpHeaderSent) {
171 continue; // Skip clients that haven't completed HTTP handshake
172 }
173
174 // Try to receive data from this client
175 int received = mNativeServer->recv(clientId, buffer);
176 if (received > 0) {
177 mLastProcessedClientId = clientId;
178 return received;
179 }
180 }
181
182 return 0; // No data available from any client
183}
184
185void HttpStreamServer::triggerReconnect() {
186 // For server: disconnect all clients and restart
187 disconnect();
188 connect();
189}
190
191bool HttpStreamServer::readHttpRequestHeader(u32 clientId) {
192 ClientState* state = getOrCreateClientState(clientId);
193 if (!state) {
194 return false;
195 }
196
197 if (state->httpHeaderReceived) {
198 return true; // Already received
199 }
200
201 // Read HTTP request header in chunks, accumulating in state->headerBuffer
202 // across multiple calls (non-blocking sockets may return partial data)
203 u8 buffer[256];
204
205 // Maximum header size: 8KB (generous for HTTP headers)
206 const size_t MAX_HEADER_SIZE = 8192;
207
208 while (state->headerBuffer.size() < MAX_HEADER_SIZE) {
209 int received = mNativeServer->recv(clientId, buffer);
210 if (received < 0) {
211 return false;
212 }
213 if (received == 0) {
214 return false;
215 }
216
217 state->headerBuffer.append(reinterpret_cast<const char*>(buffer), received); // ok reinterpret cast
218
219 // Check for \r\n\r\n pattern (end of headers)
220 if (state->headerBuffer.size() >= 4) {
221 size_t pos = state->headerBuffer.find("\r\n\r\n");
222 if (pos != fl::string::npos) {
223 break;
224 }
225 }
226 }
227
228 // Validate the header directly (don't use HttpRequestParser which waits for
229 // the chunked body to complete — we only need headers for the handshake).
230 const fl::string& hdr = state->headerBuffer;
231
232 // Must start with "POST /rpc"
233 if (hdr.find("POST /rpc") != 0) {
234 return false;
235 }
236
237 // Must have Content-Type: application/json (case-insensitive check)
238 if (hdr.find("Content-Type: application/json") == fl::string::npos &&
239 hdr.find("content-type: application/json") == fl::string::npos) {
240 return false;
241 }
242
243 // Must have Transfer-Encoding: chunked (case-insensitive check)
244 if (hdr.find("Transfer-Encoding: chunked") == fl::string::npos &&
245 hdr.find("transfer-encoding: chunked") == fl::string::npos) {
246 return false;
247 }
248
249 state->httpHeaderReceived = true;
250 return true;
251}
252
253bool HttpStreamServer::sendHttpResponseHeader(u32 clientId) {
254 ClientState* state = getOrCreateClientState(clientId);
255 if (!state) {
256 return false;
257 }
258
259 if (state->httpHeaderSent) {
260 return true; // Already sent
261 }
262
263 // Build HTTP 200 OK response header
264 fl::string header;
265 header.append("HTTP/1.1 200 OK\r\n");
266 header.append("Content-Type: application/json\r\n");
267 header.append("Transfer-Encoding: chunked\r\n");
268 header.append("Connection: keep-alive\r\n");
269 header.append("\r\n");
270
271 // Send header
272 int sent = mNativeServer->send(clientId, fl::span<const u8>(reinterpret_cast<const u8*>(header.c_str()), header.size())); // ok reinterpret cast
273 if (sent != static_cast<int>(header.size())) {
274 return false;
275 }
276
277 state->httpHeaderSent = true;
278 return true;
279}
280
281HttpStreamServer::ClientState* HttpStreamServer::getOrCreateClientState(u32 clientId) {
282 // Check if state already exists
283 auto it = mClientStates.find(clientId);
284 if (it != mClientStates.end()) {
285 return &it->second;
286 }
287
288 // Create new state
289 mClientStates.insert(fl::make_pair(clientId, ClientState(clientId)));
290
291 // Find and return the newly created state
292 it = mClientStates.find(clientId);
293 if (it != mClientStates.end()) {
294 return &it->second;
295 }
296
297 return nullptr;
298}
299
300void HttpStreamServer::removeClientState(u32 clientId) {
301 auto it = mClientStates.find(clientId);
302 if (it != mClientStates.end()) {
303 mClientStates.erase(it);
304 }
305}
306
307} // namespace http
308} // namespace net
309} // namespace fl
310
311#endif // FASTLED_HAS_NETWORKING
uint8_t pos
Definition Blur.ino:11
TestState state
fl::size find(const char &value) const FL_NOEXCEPT
const char * c_str() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
Base class for HTTP streaming transport Implements RequestSource and ResponseSink for Remote class Ma...
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
string & append(const bitset_fixed< N > &bs) FL_NOEXCEPT
Definition string.h:284
static constexpr fl::size npos
Definition string.h:195
fl::size size() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
unsigned char u8
Definition stdint.h:131
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
pair< typename fl::decay< T1 >::type, typename fl::decay< T2 >::type > make_pair(T1 &&t, T2 &&u) FL_NOEXCEPT
Definition pair.h:95
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT