Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpServer.cpp
Go to the documentation of this file.
1
20#include "framework_config.h" // Must be included before DebugTrace.h to ensure framework_config.h is processed first
21#include "DebugTrace.h"
23
24#include "http/HttpServer.h"
25#include <lwip/sockets.h>
26#include <lwip/netif.h>
27#include <lwip/ip4_addr.h>
28#include "lwip/stats.h"
29#include "lwip/memp.h"
30#include "lwip/tcp.h"
31
32#include <cstring>
33#include <cstdio>
34#include <cstdlib>
35#include <FreeRTOS.h>
36#include <task.h>
37#include <semphr.h>
38
39#include "pico/stdlib.h"
40
41#include "utility/utility.h"
42#include "http/url_utils.h"
44#include "time/TimeManager.h"
45#include "network/Tcp.h"
46#include "http/JsonResponse.h"
47#include "events/EventManager.h"
48
49// #define HTTP_SERVER_USE_TASK_PER_CLIENT // ⚠️ NOT READY FOR PRODUCTION – Known instability with task-per-client mode
50#ifdef HTTP_SERVER_USE_TASK_PER_CLIENT
51#warning "⚠️ HTTP_SERVER_USE_TASK_PER_CLIENT is experimental and not yet production-ready. Use with caution."
52#endif
53
54static constexpr int MAX_CONCURRENT_CLIENTS = 1;
55SemaphoreHandle_t clientSemaphore;
56
58StaticTask_t HttpServer::xTaskBuffer;
59
60// ----------------------------------------------------------------------------
61// Task Context Struct
62// ----------------------------------------------------------------------------
63
68{
69 HttpServer *server; // Pointer to the HttpServer instance
70 Tcp *tcp; // The client TCP connection
71};
72
73// ----------------------------------------------------------------------------
74// Constructor and Task Entry
75// ----------------------------------------------------------------------------
76
79 : port(port), router(router)
80{
81}
82
84void HttpServer::enableTLS(const std::string& certPem, const std::string& keyPem)
85{
86 serverCert = certPem;
87 serverKey = keyPem;
88 tlsEnabled = true;
89 printf("[HttpServer] TLS enabled for HTTPS support\n");
90}
91
94{
95 return tlsEnabled;
96}
97
100{
102 return xTaskCreateStatic(startServerTask, "HttpServer", HTTP_STACK_SIZE, this, 5, xStack, &xTaskBuffer);
103}
104
106void HttpServer::startServerTask(void *pvParameters)
107{
108 HttpServer *server = static_cast<HttpServer *>(pvParameters);
109 server->run();
110 vTaskDelete(NULL);
111}
112
113// ----------------------------------------------------------------------------
114// Server Core
115// ----------------------------------------------------------------------------
116
119{
120
121 printf("[HttpServer] Starting %s Server on port %d\n",
122 tlsEnabled ? "HTTPS" : "HTTP", port);
123
124 if (!initNetwork())
125 {
126 return;
127 }
128
130 if (!listener)
131 {
132 printf("[HttpServer] Failed to initialize listener\n");
133 return;
134 }
135
137
138 // Optional: store listener as a class member if needed later
139 while (true)
140 {
141 TRACE("[HttpServer] Waiting for client connection...\n");
142 Tcp* conn = listener->accept();
143 if (conn)
144 {
145 QUIET_PRINTF("\n===== HTTP CLIENT ACCEPTED ====\n");
146 QUIET_PRINTF("[HttpServer] Accepted client connection\n");
148 vTaskDelay(pdMS_TO_TICKS(10));
149
150 QUIET_PRINTF("[HttpServer] Client connection handled\n");
151 QUIET_PRINTF("===============================\n\n");
152 #if !defined(HTTP_SERVER_USE_TASK_PER_CLIENT)
153 delete conn;
154 #endif
155 }
156 else
157 {
158 warning("[HttpServer] Failed to accept client connection\n");
159 vTaskDelay(pdMS_TO_TICKS(10)); // Wait before retrying
160 }
161
162 }
163
164 // Note: we never reach here in this model
165 delete listener;
166}
167
168
171{
172 struct netif *netif;
173
174 //TRACE("Waiting for DHCP lease...\n");
175
176 while (true)
177 {
178 netif = netif_list;
179 if (netif && netif->ip_addr.addr != 0)
180 {
181 break;
182 }
183 vTaskDelay(pdMS_TO_TICKS(10));
184 }
185
186 //TRACE("Assigned IP Address: %s\n", ip4addr_ntoa(&netif->ip_addr));
187 return true;
188}
189
191
193{
194 // Configure TLS if enabled
195 if (tlsEnabled) {
196 printf("[HttpServer] Configuring TLS server with certificate and key\n");
198 }
199
201 return nullptr;
202 }
203
204 printf("[HttpServer] Server listening on port %d (%s)\n",
205 port, tlsEnabled ? "HTTPS" : "HTTP");
206
207 // Once server is listening
209 AppContext::get<EventManager>()->postEvent(e); // tell others that we are up
210 return &listener;
211}
212
214{
215#ifdef HTTP_SERVER_USE_TASK_PER_CLIENT
216 if (xSemaphoreTake(clientSemaphore, pdMS_TO_TICKS(100)) == pdPASS)
217 {
218 TaskParams *params = new TaskParams{this, conn};
219
220 if (xTaskCreate(handleClientTask, "HttpClient", 4096, params, 4, NULL) == pdPASS)
221 {
222 TRACE("Client task created successfully");
223 }
224 else
225 {
226 TRACE("Failed to create client task");
227 conn->close();
228 delete params;
229 xSemaphoreGive(clientSemaphore);
230 }
231 }
232 else
233 {
234 TRACE("Max concurrent clients reached, closing connection");
235 conn->close();
236 // Optional: return a "503 Service Unavailable" if desired
237 }
238#else
239 handleClient(conn);
240
241#endif
242}
243
246{
247 int64_t start = to_ms_since_boot(get_absolute_time());
248 int64_t lastActivity = start;
249 const int64_t idleTimeoutMs = HTTP_IDLE_TIMEOUT; // idle timeout - kill connection if no data received
250
251
253 if (req.getMethod().empty())
254 {
255 TRACE("[HttpServer] Empty HTTP method — client either closed connection or it is Safari trying to reuse closed socket\n");
256 return;
257 }
258 TRACE("HttpRequest received: %s, %s\n", req.getMethod().c_str(), req.getPath().c_str());
259 TRACE("HttpRequest content length: %s\n", req.getContentLength());
260 TRACE("HttpRequest content type: %s\n", req.getContentType());
261 TRACE("HttpRequest boundary: %s\n", req.getBoundary());
262 TRACE("HttpRequest is multipart: %s\n", (req.isMultipart() ? "true" : "false"));
263 TRACE("HttpRequest header count: %d\n", req.getHeaders().size());
264 TRACE("HttpRequest url: %s\n", req.getUri().c_str());
265 TRACE("HttpRequest path: %s\n", req.getPath().c_str());
266 TRACE("HttpRequest query: %s\n", req.getQuery().c_str());
267
268 for (const auto& param : req.getQueryParams())
269 {
270 TRACE("HttpRequest query parameter %s : %s\n", param.first.c_str(), param.second.c_str());
271 }
272
273 for (const auto& param : req.getFormParams())
274 {
275 TRACE("HttpRequest form parameter %s : %s\n", param.first.c_str(), param.second.c_str());
276 }
277
278 for (const auto& cookie : req.getCookies())
279 {
280 TRACE("HttpRequest cookie %s : %s\n", cookie.first.c_str(), cookie.second.c_str());
281 }
282
283 if (req.getContentLength() > 0)
284 {
285 TRACE("HttpRequest body length: %d\n", req.getBody().length());
286 TRACE("HttpRequest start of body index: %d\n", req.getHeaderEnd());
287 }
288
289 TRACE("HttpRequest headers:\n");
290 for (const auto& headr : req.getHeaders())
291 {
292 TRACE("%s : %s\n", headr.first.c_str(), headr.second.c_str());
293 }
294
295 TRACE("HttpRequest body: %s\n", req.getBody().c_str());
296
297 QUIET_PRINTF("[HttpServer] Client request received: %s, path: %s\n", req.getMethod().c_str(), req.getPath().c_str());
298
299 HttpResponse res(conn);
300 TRACE("HttpResponse created\n");
301 res.setHeader("Connection", "close"); // Close the connection
302
303 bool ok = router.handleRequest(req, res);
304 TRACE("HttpRequest handled: %s\n", ok ? "true" : "false");
305
306 if (!ok)
307 {
308 JsonResponse::sendError(res, 404, "NOT_FOUND", "route: " + std::string(req.getUri()));
309 }
310
311 lastActivity = to_ms_since_boot(get_absolute_time());
312
313 if (req.getHeader("Connection") == "close")
314 {
315 TRACE("[HttpServer] Client requested Connection: close, closing connection\n");
316 return;
317 }
318
319 if (to_ms_since_boot(get_absolute_time()) - lastActivity > idleTimeoutMs)
320 {
321 printf("[HttpServer] Idle timeout reached, closing connection\n");
322 return;
323 }
324
325 conn->close();
326 int64_t end = to_ms_since_boot(get_absolute_time());
327 TRACE("[HttpServer] Client handled in %lld ms\n", end - start);
328}
329
330
331#ifdef HTTP_SERVER_USE_TASK_PER_CLIENT
332void HttpServer::handleClientTask(void *pvParameters)
333{
334 TaskParams *params = static_cast<TaskParams *>(pvParameters);
335 HttpServer *server = params->server;
336 Tcp* tcp = params->tcp;
337
338 TRACE("Handling client in task for socket %d\n", tcp->getSocketFd());
339
340 server->handleClient(tcp);
341
342 tcp->close();
343 delete tcp;
344 delete params;
345
346 xSemaphoreGive(clientSemaphore);
347 vTaskDelete(NULL);
348}
349#endif
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
#define TRACE(...)
Default trace (INFO level).
Definition DebugTrace.h:187
Event pub/sub manager for embedded applications using FreeRTOS.
SemaphoreHandle_t clientSemaphore
static constexpr int MAX_CONCURRENT_CLIENTS
#define HTTP_STACK_SIZE
Definition HttpServer.h:23
Utility functions to send standard JSON responses using nlohmann::json.
General-purpose TCP socket abstraction with optional TLS support for both client and server use.
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
const std::string & getBody() const
Get the request body (copy).
const std::map< std::string, std::string > & getHeaders() const
Get all request headers.
const std::string & getMethod() const
Get the HTTP method.
const std::unordered_multimap< std::string, std::string > getFormParams()
Get parsed form fields (application/x-www-form-urlencoded).
const std::unordered_multimap< std::string, std::string > getQueryParams()
Get parsed query string parameters.
static HttpRequest receive(Tcp *tcp)
Receive and parse an HTTP request from a socket.
const std::unordered_map< std::string, std::string > getCookies() const
Get all parsed cookies.
size_t getHeaderEnd()
Get the header end offset (used for body parsing).
const std::string & getPath() const
Get the parsed request path (without query string).
const std::string getContentType() const
Get the raw Content-Type string.
const std::string & getQuery() const
Get the parsed query string from the URL.
int getContentLength() const
Get the Content-Length header as integer.
const std::string getBoundary() const
Get the boundary string (for multipart/form-data).
bool isMultipart() const
Check whether the request is multipart/form-data.
std::string getHeader(const std::string &field) const
Get a specific header field (case-insensitive).
const std::string & getUri() const
Get the original URL from the request line.
Represents an HTTP response object.
HttpResponse & setHeader(const std::string &key, const std::string &value)
Alias for set() for custom headers.
HTTP Server that listens for incoming connections and dispatches requests.
Definition HttpServer.h:29
bool initNetwork()
Initialize the network stack (wait for DHCP or static IP).
static void startServerTask(void *pvParameters)
Launch the HTTP server task (used by FreeRTOS).
void enableTLS(const std::string &certPem, const std::string &keyPem)
Enable TLS/HTTPS support with certificate and private key.
void handleClient(Tcp *conn)
Accept a client connection and handle it directly (not task-based).
bool tlsEnabled
Definition HttpServer.h:130
static StackType_t xStack[HTTP_STACK_SIZE]
Stack for static FreeRTOS task.
Definition HttpServer.h:136
Tcp * initListener()
Create, bind, and listen on the server.
bool isTLSEnabled() const
Check if TLS is enabled for this server.
int port
Accept client connections in a blocking loop and spawn handlers.
Definition HttpServer.h:124
void startHandlingClient(Tcp *conn)
Spawn a task to handle the client connection.
std::string serverCert
Definition HttpServer.h:131
Router & router
Reference to router for dispatching requests.
Definition HttpServer.h:125
static void handleClientTask(void *pvParameters)
Handle client logic inside a FreeRTOS task.
HttpServer(int port, Router &router)
Construct a new HttpServer instance.
std::string serverKey
Definition HttpServer.h:132
static StaticTask_t xTaskBuffer
Task control block buffer.
Definition HttpServer.h:137
bool start()
Start the HTTP server as a FreeRTOS task.
void run()
Main server loop: initializes, binds, and begins accepting connections.
The central router for handling HTTP requests and middleware.
Definition Router.h:60
bool handleRequest(HttpRequest &req, HttpResponse &res)
Handle an incoming HTTP request.
Definition Router.cpp:228
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
Tcp * accept()
Accept a new incoming connection (for server use).
Definition Tcp.cpp:491
void setServerTlsConfig(const std::string &certPem, const std::string &keyPem)
Set the certificate and key to use for server-side TLS (PEM format).
int close()
Close the connection and free resources.
Definition Tcp.cpp:443
bool bindAndListen(int port)
Bind and listen on a port for incoming connections (for server use).
Definition Tcp.cpp:542
int getSocketFd() const
Get the raw socket file descriptor (may be -1 for TLS-only connection).
Definition Tcp.h:124
Delegates to user or system configuration.
#define QUIET_PRINTF(...)
#define HTTP_IDLE_TIMEOUT
Timeout for idle HTTP connections in milliseconds.
void sendError(HttpResponse &res, int statusCode, const std::string &code, const std::string &message)
Represents a framework event, optionally carrying payload data.
Definition Event.h:24
Parameters passed to the per-client handler task.
HttpServer * server
Utility functions for URL decoding, form parsing, MIME type lookup, and IP address extraction.
void warning(const std::string &msg)
Definition utility.cpp:215
System utilities for diagnostics, memory, stack usage, and tracing.