9#include "platforms/esp/is_esp.h"
19#include "platforms/esp/esp_version.h"
20#if defined(FL_IS_ESP32) && !defined(FASTLED_HAS_NETWORKING) && ESP_IDF_VERSION_4_OR_HIGHER
22#include <esp_http_server.h>
26#ifdef FASTLED_HAS_NETWORKING
28#include "platforms/time_platform.h"
32 #include "platforms/win/socket_win.h"
33 #define SOCKET_ERROR_WOULD_BLOCK WSAEWOULDBLOCK
34 #define SOCKET_ERROR_IN_PROGRESS WSAEINPROGRESS
36 #include "platforms/posix/socket_posix.h"
38 #define SOCKET_ERROR_WOULD_BLOCK EWOULDBLOCK
39 #define SOCKET_ERROR_IN_PROGRESS EINPROGRESS
61 bool has_active_tasks()
const override {
62 return mServer && mServer->is_running();
65 size_t active_task_count()
const override {
66 return (mServer && mServer->is_running()) ? mServer->mClientSockets.size() : 0;
76constexpr u32 CONNECTION_TIMEOUT_MS = 30000;
79bool iequals(
const string& a,
const string& b) {
80 if (a.size() != b.size())
return false;
81 for (
size_t i = 0; i < a.size(); ++i) {
88string trim(
const string& s) {
90 while (start < s.size() &&
fl::isspace(s[start])) ++start;
91 size_t end = s.size();
93 return s.substr(start,
end - start);
97vector<string> split(
const string& s,
char delimiter) {
98 vector<string> tokens;
100 size_t end = s.find(delimiter);
102 tokens.push_back(s.substr(start,
end - start));
104 end = s.find(delimiter, start);
106 tokens.push_back(s.substr(start));
113 if (query.empty() || query[0] !=
'?')
return params;
115 vector<string> pairs = split(query.substr(1),
'&');
116 for (
const auto& pair : pairs) {
117 size_t eq_pos = pair.find(
'=');
119 string key = pair.substr(0, eq_pos);
120 string value = pair.substr(eq_pos + 1);
128const char* status_text(
int code) {
130 case 200:
return "OK";
131 case 400:
return "Bad Request";
132 case 404:
return "Not Found";
133 case 500:
return "Internal Server Error";
134 default:
return "Unknown";
139bool set_nonblocking(
int fd) {
142 return ioctlsocket(fd, FIONBIO, &mode) == 0;
144 int flags = fcntl(fd, F_GETFL, 0);
145 if (flags == -1)
return false;
146 return fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1;
158 if (iequals(it->first, name)) {
166 auto it =
mQuery.find(param);
178 mHeaders[
"Content-Type"] =
"text/plain";
197 mBody = data.to_string();
198 mHeaders[
"Content-Type"] =
"application/json";
204 resp.status(200).body(
body);
210 resp.status(404).body(
"Not Found\n");
216 resp.status(400).body(message +
"\n");
222 resp.status(500).body(message +
"\n");
238 result += it->first +
": " + it->second +
"\r\n";
307 if (client.fd != -1) {
327 mRoutes.push_back({method, path, handler});
331 route(
"GET", path, handler);
335 route(
"POST", path, handler);
339 route(
"PUT", path, handler);
343 route(
"DELETE", path, handler);
365 (
const char*)(&opt),
sizeof(opt)) < 0) {
376 mLastError =
"Failed to set non-blocking mode";
382 addr.sin_family = AF_INET;
383 addr.sin_addr.s_addr = INADDR_ANY;
384 addr.sin_port = htons(
static_cast<u16
>(
port));
386 if (bind(
mListenSocket, (sockaddr*)(&addr),
sizeof(addr)) < 0) {
405 sockaddr_in client_addr{};
406 socklen_t addr_len =
sizeof(client_addr);
411 (sockaddr*)(&client_addr),
416 if (WSAGetLastError() == SOCKET_ERROR_WOULD_BLOCK)
break;
418 if (errno == EWOULDBLOCK || errno == EAGAIN)
break;
425 if (!set_nonblocking(client_fd)) {
432 client.
fd = client_fd;
433 client.connect_time = fl::platforms::millis();
439 size_t requests_processed = 0;
443 size_t index = i - 1;
458 resp = (*handler)(*req);
470 requests_processed++;
473 return requests_processed;
481 int bytes = recv(client.fd, buffer,
sizeof(buffer), 0);
483 if (WSAGetLastError() != SOCKET_ERROR_WOULD_BLOCK) {
489 ssize_t bytes = recv(client.fd, buffer,
sizeof(buffer), MSG_DONTWAIT);
491 if (errno != EWOULDBLOCK && errno != EAGAIN) {
498 client.buffer.append(buffer,
static_cast<size_t>(bytes));
501 size_t header_end = client.buffer.find(
"\r\n\r\n");
510 string header_section = client.buffer.substr(0, header_end);
511 vector<string> lines = split(header_section,
'\n');
518 string request_line = trim(lines[0]);
519 vector<string> parts = split(request_line,
' ');
520 if (parts.size() < 3) {
524 req.mMethod = parts[0];
527 string full_path = parts[1];
528 size_t query_pos = full_path.find(
'?');
530 req.mPath = full_path.substr(0, query_pos);
531 req.mQuery = parse_query_string(full_path.substr(query_pos));
533 req.mPath = full_path;
536 req.mHttpVersion = parts[2];
539 for (
size_t i = 1; i < lines.size(); ++i) {
540 string line = trim(lines[i]);
541 if (line.empty())
continue;
543 size_t colon_pos = line.find(
':');
545 string name = trim(line.substr(0, colon_pos));
546 string value = trim(line.substr(colon_pos + 1));
547 req.mHeaders[name] =
value;
553 if (content_length) {
554 int body_len =
atoi(content_length->c_str());
556 size_t body_start = header_end + 4;
557 size_t total_needed = body_start +
static_cast<size_t>(body_len);
559 if (client.buffer.size() < total_needed) {
563 req.mBody = client.buffer.substr(body_start,
static_cast<size_t>(body_len));
572 const char* ptr = data.c_str();
573 size_t remaining = data.size();
575 while (remaining > 0) {
577 int sent = send(client_fd, ptr,
static_cast<int>(remaining), 0);
579 ssize_t sent = send(client_fd, ptr, remaining, 0);
587 remaining -=
static_cast<size_t>(sent);
594 for (
const auto& entry :
mRoutes) {
595 if (entry.method == method && entry.path == path) {
596 return entry.handler;
610 u32 now = fl::platforms::millis();
613 size_t index = i - 1;
616 if (now - client.connect_time > CONNECTION_TIMEOUT_MS) {
626#elif defined(FL_IS_ESP32) && ESP_IDF_VERSION_4_OR_HIGHER
645httpd_method_t to_httpd_method(
const string& method) {
646 if (method ==
"GET")
return HTTP_GET;
647 if (method ==
"POST")
return HTTP_POST;
648 if (method ==
"PUT")
return HTTP_PUT;
649 if (method ==
"DELETE")
return HTTP_DELETE;
654struct EspRouteContext {
660esp_err_t esp_route_handler(httpd_req_t* req);
667int Server::handle_esp_request(
void* raw_req) {
668 auto* req =
static_cast<httpd_req_t*
>(raw_req);
669 auto* ctx =
static_cast<EspRouteContext*
>(req->user_ctx);
670 if (!ctx || !ctx->server) {
671 httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"No context");
677 fl_req.mPath = req->uri;
678 switch (req->method) {
679 case HTTP_GET: fl_req.mMethod =
"GET";
break;
680 case HTTP_POST: fl_req.mMethod =
"POST";
break;
681 case HTTP_PUT: fl_req.mMethod =
"PUT";
break;
682 case HTTP_DELETE: fl_req.mMethod =
"DELETE";
break;
683 default: fl_req.mMethod =
"GET";
break;
685 fl_req.mHttpVersion =
"HTTP/1.1";
688 size_t q = fl_req.mPath.find(
'?');
690 fl_req.mPath = fl_req.mPath.substr(0, q);
694 if (req->content_len > 0 && req->content_len <= 8192) {
695 int total_len = req->content_len;
696 char* buf =
static_cast<char*
>(
fl::malloc(total_len + 1));
699 while (received < total_len) {
700 int ret = httpd_req_recv(req, buf + received, total_len - received);
706 buf[received] =
'\0';
707 fl_req.mBody = string(buf, received);
713 if (ctx->route_index >= ctx->server->mRoutes.size()) {
714 httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Invalid route");
718 Response resp = ctx->server->mRoutes[ctx->route_index].handler(fl_req);
723 fl::snprintf(status_str,
sizeof(status_str),
"%d", resp.mStatusCode);
724 httpd_resp_set_status(req, status_str);
726 for (
auto it = resp.mHeaders.begin(); it != resp.mHeaders.end(); ++it) {
727 httpd_resp_set_hdr(req, it->first.c_str(), it->second.c_str());
730 httpd_resp_send(req, resp.mBody.c_str(), resp.mBody.size());
737esp_err_t esp_route_handler(httpd_req_t* req) {
738 return static_cast<esp_err_t
>(Server::handle_esp_request(
static_cast<void*
>(req)));
747 if (it->first == name) {
755 auto it =
mQuery.find(param);
765 mHeaders[
"Content-Type"] =
"text/plain";
784 mBody = data.to_string();
785 mHeaders[
"Content-Type"] =
"application/json";
791 resp.status(200).body(
body);
797 resp.status(404).body(
"Not Found\n");
803 resp.status(400).body(message +
"\n");
809 resp.status(500).body(message +
"\n");
825static httpd_handle_t s_esp_httpd =
nullptr;
826static fl::vector<fl::unique_ptr<EspRouteContext>> s_esp_route_contexts;
847 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
848 config.server_port =
static_cast<u16
>(
port);
850 esp_err_t err = httpd_start(&s_esp_httpd, &config);
853 FL_WARN(
"[HTTP] httpd_start failed: " << esp_err_to_name(err));
858 for (
size_t i = 0; i <
mRoutes.size(); ++i) {
861 httpd_uri_t uri_handler = {};
862 uri_handler.uri =
mRoutes[i].path.c_str();
863 uri_handler.method = to_httpd_method(
mRoutes[i].method);
864 uri_handler.handler = esp_route_handler;
865 uri_handler.user_ctx = ctx.get();
867 esp_err_t reg_err = httpd_register_uri_handler(s_esp_httpd, &uri_handler);
868 if (reg_err != ESP_OK) {
869 FL_WARN(
"[HTTP] Failed to register route " <<
mRoutes[i].path.c_str());
886 httpd_stop(s_esp_httpd);
887 s_esp_httpd =
nullptr;
891 s_esp_route_contexts.
clear();
897 FL_WARN(
"[HTTP] Server stopped");
901 mRoutes.push_back({method, path, handler});
905 size_t idx =
mRoutes.size() - 1;
908 httpd_uri_t uri_handler = {};
909 uri_handler.uri =
mRoutes[idx].path.c_str();
910 uri_handler.method = to_httpd_method(method);
911 uri_handler.handler = esp_route_handler;
912 uri_handler.user_ctx = ctx.get();
914 httpd_register_uri_handler(s_esp_httpd, &uri_handler);
920 route(
"GET", path, handler);
924 route(
"POST", path, handler);
928 route(
"PUT", path, handler);
932 route(
"DELETE", path, handler);
static void removeListener(Listener *listener) FL_NOEXCEPT
static void addListener(Listener *listener, int priority=0) FL_NOEXCEPT
optional< string > header(const string &name) const
Get header value by name (case-insensitive)
map< string, string > mHeaders
map< string, string > mQuery
optional< string > query(const string ¶m) const
Get query parameter value by name.
static Response not_found()
Factory method for 404 Not Found response.
Response & json(const class json &data)
Set JSON response body with automatic Content-Type header.
static Response internal_error(const string &message)
Factory method for 500 Internal Server Error response.
Response & status(int code)
Set HTTP status code.
Response & body(const string &content)
Set response body.
static Response ok(const string &body="")
Factory method for 200 OK response.
Response & header(const string &name, const string &value)
Add HTTP header.
map< string, string > mHeaders
static Response bad_request(const string &message)
Factory method for 400 Bad Request response.
HTTP response builder (fluent interface)
void del(const string &path, RouteHandler handler)
Convenience method for DELETE routes.
optional< Request > read_request(ClientConnection &client)
void get(const string &path, RouteHandler handler)
Convenience method for GET routes.
vector< ClientConnection > mClientSockets
void put(const string &path, RouteHandler handler)
Convenience method for PUT routes.
~Server() FL_NOEXCEPT
Destructor (stops server if running)
void accept_connections()
vector< RouteEntry > mRoutes
int port() const
Get server port.
void stop()
Stop server and close all connections.
bool send_response(int client_fd, const Response &response)
void post(const string &path, RouteHandler handler)
Convenience method for POST routes.
void cleanup_stale_connections()
bool start(int port=8080)
Start server on specified port.
Server() FL_NOEXCEPT
Constructor.
size_t process_requests()
size_t update()
Update server (process pending requests non-blocking)
fl::unique_ptr< ServerAsyncRunner > mAsyncRunner
optional< RouteHandler > find_handler(const string &method, const string &path) const
void route(const string &method, const string &path, RouteHandler handler)
Register route handler using fl::function.
bool setup_listen_socket(int port)
void close_client(size_t index)
static constexpr fl::size npos
void register_runner(Runner *r)
Register a runner.
static Executor & instance()
void unregister_runner(Runner *r)
Unregister a runner.
void push_back(const T &value) FL_NOEXCEPT
Task executor — runs registered task runners and manages the run loop.
Centralized logging categories for FastLED hardware interfaces and subsystems.
function< Response(const Request &)> RouteHandler
Route handler function signature Takes const reference to Request, returns Response by value.
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
string to_string(T value) FL_NOEXCEPT
char tolower(char c) FL_NOEXCEPT
Convert character to lowercase.
constexpr int type_rank< T >::value
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > map
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
int snprintf(char *buffer, fl::size size, const char *format, const Args &... args) FL_NOEXCEPT
Snprintf-like formatting function that writes to a buffer.
void * malloc(size_t size)
expected< T, E > result
Alias for expected (Rust-style naming)
constexpr nullopt_t nullopt
int atoi(const char *str)
bool isspace(char c) FL_NOEXCEPT
Check if character is whitespace (space, tab, newline, carriage return)
Base definition for an LED controller.