FastLED 3.9.15
Loading...
Searching...
No Matches
stream_client.cpp.hpp
Go to the documentation of this file.
1#pragma once
2
3#ifdef FASTLED_HAS_NETWORKING
4
6#include "fl/stl/stdio.h" // fl::snprintf — avoids _svfprintf_r (#2773 item 1.1)
7#include "fl/stl/string.h"
8#include "fl/stl/stdint.h"
9#include "fl/stl/chrono.h"
10// Note: fl/stl/cstdio.h intentionally NOT included — workaround for
11// zackees/zccache#619 (Windows PCH path-spelling drift). This TU doesn't
12// reference any cstdio.h symbol directly; the include was dead.
13#include "fl/stl/thread.h"
14#include "fl/stl/noexcept.h"
15namespace fl {
16namespace net {
17namespace http {
18
19HttpStreamClient::HttpStreamClient(const fl::string& host, u16 port, u32 heartbeatIntervalMs)
20 : HttpStreamTransport(host, port, heartbeatIntervalMs)
21 , mHttpHeaderSent(false)
22 , mHttpHeaderReceived(false)
23 , mHost(host)
24 , mPort(port) {
25 // Create native client with default connection config
26 ConnectionConfig config;
27 mNativeClient = fl::make_unique<NativeHttpClient>(mHost, mPort, config);
28}
29
30HttpStreamClient::~HttpStreamClient() FL_NOEXCEPT {
31 disconnect();
32}
33
34bool HttpStreamClient::connect() {
35 // If already connected, return true
36 if (isConnected()) {
37 return true;
38 }
39
40 // Reset HTTP state
41 mHttpHeaderSent = false;
42 mHttpHeaderReceived = false;
43
44 // Connect native socket
45 if (!mNativeClient->connect()) {
46 return false;
47 }
48
49 // Send HTTP POST request header
50 if (!sendHttpRequestHeader()) {
51 mNativeClient->disconnect();
52 return false;
53 }
54
55 // Read HTTP response header
56 if (!readHttpResponseHeader()) {
57 mNativeClient->disconnect();
58 return false;
59 }
60
61 // Mark connection as established in base class
62 mConnection.onConnected();
63
64 return true;
65}
66
67void HttpStreamClient::disconnect() {
68 if (mNativeClient) {
69 mNativeClient->disconnect();
70 }
71 mHttpHeaderSent = false;
72 mHttpHeaderReceived = false;
73 mConnection.onDisconnected();
74}
75
76bool HttpStreamClient::isConnected() const {
77 return mNativeClient && mNativeClient->isConnected() && mHttpHeaderSent && mHttpHeaderReceived;
78}
79
80int HttpStreamClient::sendData(fl::span<const u8> data) {
81 if (!isConnected()) {
82 return -1;
83 }
84 return mNativeClient->send(data);
85}
86
87int HttpStreamClient::recvData(fl::span<u8> buffer) {
88 if (!isConnected()) {
89 return -1;
90 }
91 return mNativeClient->recv(buffer);
92}
93
94void HttpStreamClient::triggerReconnect() {
95 // Disconnect and let the base class reconnection logic handle it
96 disconnect();
97}
98
99bool HttpStreamClient::sendHttpRequestHeader() {
100 // Build HTTP POST request header
101 // Format:
102 // POST /rpc HTTP/1.1
103 // Host: <host>:<port>
104 // Content-Type: application/json
105 // Transfer-Encoding: chunked
106 // Connection: keep-alive
107 // \r\n
108
109 fl::string header;
110 header.append("POST /rpc HTTP/1.1\r\n");
111 header.append("Host: ");
112 header.append(mHost);
113 if (mPort != 80) {
114 header.append(":");
115 char portStr[8];
116 fl::snprintf(portStr, sizeof(portStr), "%u", mPort);
117 header.append(portStr);
118 }
119 header.append("\r\n");
120 header.append("Content-Type: application/json\r\n");
121 header.append("Transfer-Encoding: chunked\r\n");
122 header.append("Connection: keep-alive\r\n");
123 header.append("\r\n");
124
125 // Send header
126 int sent = mNativeClient->send(fl::span<const u8>(reinterpret_cast<const u8*>(header.c_str()), header.size())); // ok reinterpret cast
127 if (sent != static_cast<int>(header.size())) {
128 return false;
129 }
130
131 mHttpHeaderSent = true;
132 return true;
133}
134
135bool HttpStreamClient::readHttpResponseHeader() {
136 // Read HTTP response header
137 // Expected format:
138 // HTTP/1.1 200 OK
139 // Content-Type: application/json
140 // Transfer-Encoding: chunked
141 // Connection: keep-alive
142 // \r\n
143
144 // Read until we find \r\n\r\n (end of headers)
145 fl::string headerBuffer;
146 u8 buffer[256]; // Read in chunks instead of byte-by-byte
147
148 // Maximum header size: 4KB, max retries when no data (yield to server thread)
149 const size_t MAX_HEADER_SIZE = 4096;
150 const int MAX_READ_ATTEMPTS = 500; // 500 yields before giving up
151 int readAttempts = 0;
152
153 while (headerBuffer.size() < MAX_HEADER_SIZE) {
154 int received = mNativeClient->recv(buffer);
155
156 if (received < 0) {
157 return false;
158 }
159
160 if (received == 0) {
161 // No data yet - server may still be processing the request
162 // Yield to allow other threads (e.g. server thread) to run
163 readAttempts++;
164 if (readAttempts >= MAX_READ_ATTEMPTS) {
165 return false;
166 }
167 // Sleep briefly to allow server thread to run.
168 // The server thread sleeps 10ms between iterations, so we need
169 // a real sleep (not just yield) to give it time to process.
170 fl::this_thread::sleep_for(fl::chrono::milliseconds(10)); // ok sleep for
171 continue;
172 }
173
174 headerBuffer.append(reinterpret_cast<const char*>(buffer), received); // ok reinterpret cast
175
176 // Check for \r\n\r\n pattern
177 if (headerBuffer.size() >= 4) {
178 size_t pos = headerBuffer.find("\r\n\r\n");
179 if (pos != fl::string::npos) {
180 break;
181 }
182 }
183 }
184
185 // Validate response
186 // Must start with "HTTP/1.1 200"
187 if (headerBuffer.size() < 12) {
188 return false;
189 }
190
191 if (headerBuffer.substr(0, 12) != "HTTP/1.1 200") {
192 return false;
193 }
194
195 // Check for required headers (case-insensitive)
196 // We're being lenient here - just check that the connection is valid
197 // The chunked encoding will handle the actual data parsing
198
199 // Look for Transfer-Encoding: chunked
200 bool hasChunked = false;
201 if (headerBuffer.find("Transfer-Encoding: chunked") != fl::string::npos ||
202 headerBuffer.find("transfer-encoding: chunked") != fl::string::npos) {
203 hasChunked = true;
204 }
205
206 if (!hasChunked) {
207 return false;
208 }
209
210 mHttpHeaderReceived = true;
211 return true;
212}
213
214} // namespace http
215} // namespace net
216} // namespace fl
217
218#endif // FASTLED_HAS_NETWORKING
uint8_t pos
Definition Blur.ino:11
FastLED chrono implementation - duration types for time measurements.
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...
string substr(fl::size start, fl::size length) const FL_NOEXCEPT
string & append(const bitset_fixed< N > &bs) FL_NOEXCEPT
Definition string.h:284
static constexpr fl::size npos
Definition string.h:195
unsigned char u8
Definition stdint.h:131
duration< fl::i64, fl::milli > milliseconds
Milliseconds - duration with period of 1/1,000 seconds.
Definition chrono.h:103
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
int snprintf(char *buffer, fl::size size, const char *format, const Args &... args) FL_NOEXCEPT
Snprintf-like formatting function that writes to a buffer.
Definition stdio.h:666
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT