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
85{
87 return xTaskCreateStatic(startServerTask, "HttpServer", HTTP_STACK_SIZE, this, 5, xStack, &xTaskBuffer);
88}
89
91void HttpServer::startServerTask(void *pvParameters)
92{
93 HttpServer *server = static_cast<HttpServer *>(pvParameters);
94 server->run();
95 vTaskDelete(NULL);
96}
97
98// ----------------------------------------------------------------------------
99// Server Core
100// ----------------------------------------------------------------------------
101
104{
105
106 printf("[HttpServer] Starting HTTP Server on port %d\n", port);
107
108 if (!initNetwork())
109 {
110 return;
111 }
112
114 if (!listener)
115 {
116 printf("[HttpServer] Failed to initialize listener\n");
117 return;
118 }
119
121
122 // Optional: store listener as a class member if needed later
123 while (true)
124 {
125 TRACE("[HttpServer] Waiting for client connection...\n");
126 Tcp* conn = listener->accept();
127 if (conn)
128 {
129 QUIET_PRINTF("\n===== HTTP CLIENT ACCEPTED ====\n");
130 QUIET_PRINTF("[HttpServer] Accepted client connection\n");
132 vTaskDelay(pdMS_TO_TICKS(10));
133
134 QUIET_PRINTF("[HttpServer] Client connection handled\n");
135 QUIET_PRINTF("===============================\n\n");
136 #if !defined(HTTP_SERVER_USE_TASK_PER_CLIENT)
137 delete conn;
138 #endif
139 }
140 else
141 {
142 warning("[HttpServer] Failed to accept client connection\n");
143 vTaskDelay(pdMS_TO_TICKS(10)); // Wait before retrying
144 }
145
146 }
147
148 // Note: we never reach here in this model
149 delete listener;
150}
151
152
155{
156 struct netif *netif;
157
158 //TRACE("Waiting for DHCP lease...\n");
159
160 while (true)
161 {
162 netif = netif_list;
163 if (netif && netif->ip_addr.addr != 0)
164 {
165 break;
166 }
167 vTaskDelay(pdMS_TO_TICKS(10));
168 }
169
170 //TRACE("Assigned IP Address: %s\n", ip4addr_ntoa(&netif->ip_addr));
171 return true;
172}
173
175
177{
179 return nullptr;
180 }
181 // Once server is listening
183 AppContext::get<EventManager>()->postEvent(e); // tell others that we are up
184 return &listener;
185}
186
188{
189#ifdef HTTP_SERVER_USE_TASK_PER_CLIENT
190 if (xSemaphoreTake(clientSemaphore, pdMS_TO_TICKS(100)) == pdPASS)
191 {
192 TaskParams *params = new TaskParams{this, conn};
193
194 if (xTaskCreate(handleClientTask, "HttpClient", 4096, params, 4, NULL) == pdPASS)
195 {
196 TRACE("Client task created successfully");
197 }
198 else
199 {
200 TRACE("Failed to create client task");
201 conn->close();
202 delete params;
203 xSemaphoreGive(clientSemaphore);
204 }
205 }
206 else
207 {
208 TRACE("Max concurrent clients reached, closing connection");
209 conn->close();
210 // Optional: return a "503 Service Unavailable" if desired
211 }
212#else
213 handleClient(conn);
214
215#endif
216}
217
220{
221 int64_t start = to_ms_since_boot(get_absolute_time());
222 int64_t lastActivity = start;
223 const int64_t idleTimeoutMs = HTTP_IDLE_TIMEOUT; // idle timeout - kill connection if no data received
224
225
227 if (req.getMethod().empty())
228 {
229 TRACE("[HttpServer] Empty HTTP method — client either closed connection or it is Safari trying to reuse closed socket\n");
230 return;
231 }
232 TRACE("HttpRequest received: %s, %s\n", req.getMethod().c_str(), req.getPath().c_str());
233 TRACE("HttpRequest content length: %s\n", req.getContentLength());
234 TRACE("HttpRequest content type: %s\n", req.getContentType());
235 TRACE("HttpRequest boundary: %s\n", req.getBoundary());
236 TRACE("HttpRequest is multipart: %s\n", (req.isMultipart() ? "true" : "false"));
237 TRACE("HttpRequest header count: %d\n", req.getHeaders().size());
238 TRACE("HttpRequest url: %s\n", req.getUri().c_str());
239 TRACE("HttpRequest path: %s\n", req.getPath().c_str());
240 TRACE("HttpRequest query: %s\n", req.getQuery().c_str());
241
242 for (const auto& param : req.getQueryParams())
243 {
244 TRACE("HttpRequest query parameter %s : %s\n", param.first.c_str(), param.second.c_str());
245 }
246
247 for (const auto& param : req.getFormParams())
248 {
249 TRACE("HttpRequest form parameter %s : %s\n", param.first.c_str(), param.second.c_str());
250 }
251
252 for (const auto& cookie : req.getCookies())
253 {
254 TRACE("HttpRequest cookie %s : %s\n", cookie.first.c_str(), cookie.second.c_str());
255 }
256
257 if (req.getContentLength() > 0)
258 {
259 TRACE("HttpRequest body length: %d\n", req.getBody().length());
260 TRACE("HttpRequest start of body index: %d\n", req.getHeaderEnd());
261 }
262
263 TRACE("HttpRequest headers:\n");
264 for (const auto& headr : req.getHeaders())
265 {
266 TRACE("%s : %s\n", headr.first.c_str(), headr.second.c_str());
267 }
268
269 TRACE("HttpRequest body: %s\n", req.getBody().c_str());
270
271 QUIET_PRINTF("[HttpServer] Client request received: %s, path: %s\n", req.getMethod().c_str(), req.getPath().c_str());
272
273 HttpResponse res(conn);
274 TRACE("HttpResponse created\n");
275 res.setHeader("Connection", "close"); // Close the connection
276
277 bool ok = router.handleRequest(req, res);
278 TRACE("HttpRequest handled: %s\n", ok ? "true" : "false");
279
280 if (!ok)
281 {
282 JsonResponse::sendError(res, 404, "NOT_FOUND", "route: " + std::string(req.getUri()));
283 }
284
285 lastActivity = to_ms_since_boot(get_absolute_time());
286
287 if (req.getHeader("Connection") == "close")
288 {
289 TRACE("[HttpServer] Client requested Connection: close, closing connection\n");
290 return;
291 }
292
293 if (to_ms_since_boot(get_absolute_time()) - lastActivity > idleTimeoutMs)
294 {
295 printf("[HttpServer] Idle timeout reached, closing connection\n");
296 return;
297 }
298
299 conn->close();
300 int64_t end = to_ms_since_boot(get_absolute_time());
301 TRACE("[HttpServer] Client handled in %lld ms\n", end - start);
302}
303
304
305#ifdef HTTP_SERVER_USE_TASK_PER_CLIENT
306void HttpServer::handleClientTask(void *pvParameters)
307{
308 TaskParams *params = static_cast<TaskParams *>(pvParameters);
309 HttpServer *server = params->server;
310 Tcp* tcp = params->tcp;
311
312 TRACE("Handling client in task for socket %d\n", tcp->getSocketFd());
313
314 server->handleClient(tcp);
315
316 tcp->close();
317 delete tcp;
318 delete params;
319
320 xSemaphoreGive(clientSemaphore);
321 vTaskDelete(NULL);
322}
323#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 handleClient(Tcp *conn)
Accept a client connection and handle it directly (not task-based).
static StackType_t xStack[HTTP_STACK_SIZE]
Stack for static FreeRTOS task.
Definition HttpServer.h:117
Tcp * initListener()
Create, bind, and listen on the server.
int port
Accept client connections in a blocking loop and spawn handlers.
Definition HttpServer.h:110
void startHandlingClient(Tcp *conn)
Spawn a task to handle the client connection.
Router & router
Reference to router for dispatching requests.
Definition HttpServer.h:111
static void handleClientTask(void *pvParameters)
Handle client logic inside a FreeRTOS task.
HttpServer(int port, Router &router)
Construct a new HttpServer instance.
static StaticTask_t xTaskBuffer
Task control block buffer.
Definition HttpServer.h:118
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
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.