8#ifdef FASTLED_HAS_NETWORKING
15 #ifndef WIN32_LEAN_AND_MEAN
16 #define WIN32_LEAN_AND_MEAN
34 #define boolean _FL_BOOLEAN_WORKAROUND
35 #define CRGB _FL_CRGB_WORKAROUND
46 #define SOCKET_ERROR_WOULD_BLOCK WSAEWOULDBLOCK
47 #define GET_SOCKET_ERROR() WSAGetLastError()
48 #define CLOSE_SOCKET(s) closesocket(s)
51 #include <sys/socket.h>
52 #include <sys/select.h>
54 #include <netinet/in.h>
61 #define SOCKET_ERROR_WOULD_BLOCK EWOULDBLOCK
62 #define GET_SOCKET_ERROR() errno
63 #define CLOSE_SOCKET(s) close(s)
74constexpr fl::uptr kInvalidSocketHandle =
static_cast<fl::uptr
>(INVALID_SOCKET);
76constexpr int kInvalidSocketHandle = -1;
80bool header_name_equals(
const fl::string& name,
const char* reserved)
FL_NO_EXCEPT {
82 for (; i < name.size() && reserved[i] !=
'\0'; ++i) {
85 if (a >=
'A' && a <=
'Z') a =
static_cast<char>(a -
'A' +
'a');
86 if (b >=
'A' && b <=
'Z') b =
static_cast<char>(b -
'A' +
'a');
91 return i == name.size() && reserved[i] ==
'\0';
96bool has_line_break(
const fl::string& field)
FL_NO_EXCEPT {
97 for (fl::size i = 0; i < field.size(); ++i) {
98 if (field[i] ==
'\r' || field[i] ==
'\n') {
106bool is_reserved_request_header(
const fl::string& name)
FL_NO_EXCEPT {
107 return header_name_equals(name,
"host") ||
108 header_name_equals(name,
"connection") ||
109 header_name_equals(name,
"content-length") ||
110 header_name_equals(name,
"transfer-encoding");
119 if (has_line_break(options.method) || has_line_break(path) || has_line_break(host)) {
122 for (
const auto& header : options.headers) {
123 if (has_line_break(header.first) || has_line_break(header.second)) {
127 fl::string request = options.method.empty() ? fl::string(
"GET") : options.method;
128 request +=
" " + path +
" HTTP/1.1\r\n";
129 request +=
"Host: " + host +
"\r\n";
130 for (
const auto& header : options.headers) {
131 if (is_reserved_request_header(header.first)) {
134 request += header.first +
": " + header.second +
"\r\n";
136 if (!options.body.empty()) {
137 request +=
"Content-Length: ";
138 request.
append(
static_cast<u32
>(options.body.size()));
141 request +=
"Connection: close\r\n\r\n";
142 request += options.body;
153 , mOptions(opts.options())
158 , mSocketFd(kInvalidSocketHandle)
159 , mDnsResult(
nullptr)
165 if (!mParsedUrl.isValid() || mParsedUrl.host().empty()) {
166 complete_error(
"Invalid URL");
169 mHostname = fl::string(mParsedUrl.host().data(), mParsedUrl.host().size());
170 mPort = mParsedUrl.port();
171 fl::string_view p = mParsedUrl.path();
172 mPath = p.
empty() ? fl::string(
"/") : fl::string(p.data(), p.size());
219 mSocketFd = socket(AF_INET, SOCK_STREAM, 0);
230 int flags = fcntl(
mSocketFd, F_GETFL, 0);
231 fcntl(
mSocketFd, F_SETFL, flags | O_NONBLOCK);
235 sockaddr_in server_addr{};
236 server_addr.sin_family = AF_INET;
237 server_addr.sin_port = htons(
static_cast<u16
>(
mPort));
240 char *addr_ptr =
nullptr;
246 connect(
mSocketFd, (sockaddr*)&server_addr,
sizeof(server_addr));
263 int result = select(
static_cast<int>(
mSocketFd + 1),
nullptr, &write_fds,
nullptr, &timeout);
268 socklen_t len =
sizeof(sock_err);
269 getsockopt(
mSocketFd, SOL_SOCKET, SO_ERROR, (
char*)&sock_err, &len);
280 complete_error(
"Invalid request: CR or LF in the method, path, host or a header");
307 FL_WARN(
"[FETCH] Waiting for HTTP response...");
311 }
else if (sent < 0) {
312 int err = GET_SOCKET_ERROR();
313 if (err != SOCKET_ERROR_WOULD_BLOCK) {
322 ssize_t bytes = recv(
mSocketFd, buffer,
sizeof(buffer), 0);
327 }
else if (bytes == 0) {
332 int err = GET_SOCKET_ERROR();
333 if (err == SOCKET_ERROR_WOULD_BLOCK) {
346 const char* data = raw.c_str();
347 const size_t len = raw.size();
350 size_t header_end = raw.find(
"\r\n\r\n");
352 return Response(500,
"Internal Server Error");
356 size_t first_space = raw.find(
' ');
357 size_t second_space = raw.find(
' ', first_space + 1);
359 first_space >= header_end || second_space >= header_end) {
360 return Response(500,
"Internal Server Error");
365 for (
size_t i = first_space + 1; i < second_space; ++i) {
366 status_code = status_code * 10 + (data[i] -
'0');
370 size_t status_line_end = raw.find(
"\r\n");
371 size_t st_len = (status_line_end !=
fl::string::npos ? status_line_end : header_end) - (second_space + 1);
373 Response resp(status_code, fl::string(data + second_space + 1, st_len));
374 resp.set_body(fl::string(data + header_end + 4, len - header_end - 4));
377 size_t pos = status_line_end + 2;
378 while (
pos < header_end) {
379 size_t line_end = raw.find(
"\r\n",
pos);
381 line_end = header_end;
386 for (
size_t i =
pos; i < line_end; ++i) {
387 if (data[i] ==
':') {
395 fl::string name(data +
pos, colon -
pos);
396 for (
size_t i = 0; i < name.size(); ++i) {
397 if (name[i] >=
'A' && name[i] <=
'Z') {
398 name[i] += (
'a' -
'A');
403 size_t val_start = colon + 1;
404 if (val_start < line_end && data[val_start] ==
' ') {
407 resp.set_header(name, fl::string(data + val_start, line_end - val_start));
420 if (mPromise.valid() && !mPromise.is_completed()) {
421 mPromise.complete_with_value(resp);
429 if (mPromise.valid() && !mPromise.is_completed()) {
430 mPromise.complete_with_error(fl::task::Error(message));
Fetch options builder (fluent interface)
fl::string mResponseBuffer
void handle_dns_lookup() FL_NO_EXCEPT
void close_socket() FL_NO_EXCEPT
void update() FL_NO_EXCEPT
Pump the state machine (called by fl::task every update interval)
void handle_sending() FL_NO_EXCEPT
void handle_connecting() FL_NO_EXCEPT
Response parse_http_response(const fl::string &raw) FL_NO_EXCEPT
~FetchRequest() FL_NO_EXCEPT
Destructor - closes socket if still open.
FetchRequest(const fl::string &url, const FetchOptions &opts, fl::task::Promise< Response > promise) FL_NO_EXCEPT
Construct a new fetch request.
void handle_receiving() FL_NO_EXCEPT
@ DNS_LOOKUP
Resolving hostname (brief blocking ~10-100ms)
@ RECEIVING
Receiving HTTP response (non-blocking)
@ SENDING
Sending HTTP request (non-blocking)
@ CONNECTING
Waiting for socket connection (non-blocking)
@ COMPLETED
Successfully completed.
void complete_error(const char *message) FL_NO_EXCEPT
void complete_success(const Response &resp) FL_NO_EXCEPT
fl::string mRequestBuffer
HTTP response class (unified interface)
constexpr bool empty() const FL_NO_EXCEPT
string & append(const bitset_fixed< N > &bs) FL_NO_EXCEPT
static constexpr fl::size npos
Task executor — runs registered task runners and manages the run loop.
Centralized logging categories for FastLED hardware interfaces and subsystems.
bool build_http_request(const RequestOptions &options, const fl::string &path, const fl::string &host, fl::string *out) FL_NO_EXCEPT
The HTTP/1.1 request FetchRequest sends: options.method (GET when empty) on the request line,...
void run(fl::u32 microseconds, ExecFlags flags) FL_NO_EXCEPT
Run selected task subsystems.
u8 u8 fract8 blur_amount FL_NO_EXCEPT
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
FASTLED_FORCE_INLINE void * memcpy(void *dest, const void *src, size_t n) FL_NO_EXCEPT
expected< T, E > result
Alias for expected (Rust-style naming)
Base definition for an LED controller.