Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
Router.cpp
Go to the documentation of this file.
1
28#include "framework_config.h" // Must be included before DebugTrace.h to ensure framework_config.h is processed first
29#include "DebugTrace.h"
31
32#include <regex>
33
34#include "http/Router.h"
35#include "http/HttpRequest.h"
36#include "http/HttpResponse.h"
38#include "http/Middleware.h"
39#include "utility/utility.h"
40#include "http/url_utils.h"
41#include "http/JsonResponse.h"
43
44SemaphoreHandle_t lock_;
45StaticSemaphore_t Router::lockBuffer_;
46
47// -----------------------------------------------------------------------------
48// Helper function to extract a bearer token from an Authorization header.
49static std::string extractBearerToken(const std::string &auth_header)
50{
51 const std::string bearerPrefix = "Bearer ";
52 if (auth_header.compare(0, bearerPrefix.size(), bearerPrefix) == 0)
53 {
54 return auth_header.substr(bearerPrefix.size());
55 }
56 return "";
57}
58
59// ----- Router constructor
62{
63 lock_ = xSemaphoreCreateRecursiveMutex();
65}
66
67// Returns the bearer token from the request's Authorization header.
70{
71 std::string auth_header = req.getHeader("Authorization");
72 std::string token = extractBearerToken(auth_header);
73 if (token.empty())
74 {
75 TRACE("Error: Missing or invalid Authorization header format\n");
76 }
77 return token;
78}
79
80// Handles /auth route: checks for the Authorization header and returns token info.
81// If the header is missing, it responds with a 401 Unauthorized status.
82// If the header is present, it extracts the token and returns it in the response.
83// This is useful for testing JWT token handling.
84// Returns true if the request was successfully handled, false otherwise.
85// /auth route is typically used for testing JWT token handling and will need to be in the routes.
87#ifdef PICO_HTTP_ENABLE_JWT
88bool Router::handle_auth_req(HttpRequest &req, HttpResponse &res, const std::vector<std::string> &params)
89{
90 TRACE("Handling /auth\n");
91 std::string auth_header = req.getHeader("Authorization");
92 if (auth_header.empty())
93 {
94 TRACE("Missing Authorization header\n");
95 JsonResponse::sendError(res, 401, "UNAUTHORIZED", "Missing authorization header");
96 return false;
97 }
98 else
99 {
100 std::string token = getAuthorizationToken(req);
101 TRACE("Token: %s\n", token.c_str());
102 JsonResponse::sendSuccess(res, {{"token", token}}, "Authorization successful");
103 return true;
104 }
105}
106#endif
107
108// Adds a middleware to be executed for all routes.
110void Router::use(Middleware middleware)
111{
112 globalMiddleware.push_back(middleware);
113}
114
115// Checks if the request is authorized for the given route.
117#ifdef PICO_HTTP_ENABLE_JWT
118bool Router::isAuthorizedForRoute(const Route &route, HttpRequest &req, HttpResponse &res)
119{
120
121 if (route.requiresAuth)
122 {
123 TRACE("Authorization required for route: %s\n", route.path.c_str());
124 std::string token = getAuthorizationToken(req);
125 TRACE("Token: %s\n", token.c_str());
126 // Fixed the conditional check: removed the erroneous comma operator.
127 if (token.empty() || !AppContext::get<JwtAuthenticator>()->validateJWT(token))
128 {
129 JsonResponse::sendError(res, 401, "UNAUTHORIZED", "Missing authorization header");
130 TRACE("Authorization failed\n");
131 return false;
132 }
133 }
134 else
135 {
136 TRACE("No authorization required for route: %s\n", route.path.c_str());
137 }
138
139 return true;
140}
141#endif
142
144void Router::addRoute(const std::string &method,
145 const std::string &path,
146 RouteHandler handler,
147 std::vector<Middleware> middleware)
148{
149 TRACE("Adding route: %s %s\n", method.c_str(), path.c_str());
150
151 std::string regex_pattern = "^" + path;
152 bool is_dynamic = false;
153 std::vector<std::string> paramNames;
154 size_t pos = 0;
155
156 while ((pos = regex_pattern.find("{", pos)) != std::string::npos)
157 {
158 size_t end = regex_pattern.find("}", pos);
159 if (end != std::string::npos)
160 {
161 std::string paramName = regex_pattern.substr(pos + 1, end - pos - 1);
162 paramNames.push_back(paramName);
163 regex_pattern.replace(pos, end - pos + 1, "([^/]+)");
164 is_dynamic = true;
165 pos += std::string("([^/]+)").size();
166 }
167 else
168 {
169 break;
170 }
171 }
172
173 if (regex_pattern == "/.*")
174 {
175 regex_pattern = "^/(.*)$";
176 is_dynamic = true;
177 }
178 regex_pattern += "$";
179
180 RouteHandler finalHandler = [handler, paramNames, this, middleware](HttpRequest &req, HttpResponse &res, const RouteMatch &match)
181 {
182 for (const auto &mw : this->globalMiddleware)
183 {
184 if (!mw(req, res, match))
185 return;
186 }
187
188 for (const auto &mw : middleware)
189 {
190 if (!mw(req, res, match))
191 return;
192 }
193
194 handler(req, res, match);
195 };
196
197 withRoutes([&](auto &r)
198 {
199 r[method].emplace_back(method, regex_pattern, finalHandler, is_dynamic, !middleware.empty(), paramNames);
200 });
201}
202
203void Router::addCatchAllGetRoute(RouteHandler handler, std::vector<Middleware> middleware)
204{
205 TRACE("Adding catch-all GET route\n");
206
207 RouteHandler finalHandler = [handler, this, middleware](HttpRequest &req, HttpResponse &res, const RouteMatch &match) {
208 for (const auto &mw : globalMiddleware)
209 if (!mw(req, res, match)) return;
210 for (const auto &mw : middleware)
211 if (!mw(req, res, match)) return;
212 handler(req, res, match);
213 };
214
215 withRoutes([&](auto &) {
217 "GET",
218 "/(.*)",
219 finalHandler,
220 true, // authRequired
221 !middleware.empty(), // hasMiddleware
222 {} // vector<string>
223 );
224 hasCatchallGetRoute = true;
225 });
226}
227
229{
230 TRACE("Router::handleRequest: %s %s\n", req.getMethod().c_str(), req.getPath().c_str());
231 bool matched = false;
232 const Route *matchedRoute = nullptr;
233 std::vector<std::string> params;
234
235 withRoutes([&](auto &r)
236 {
237 auto it = r.find(req.getMethod());
238 if (it == r.end())
239 return;
240
241 for (const auto &route : it->second)
242 {
243 TRACE("Checking route: %s\n", route.path.c_str());
244
245 std::smatch match;
246 const std::string &path = req.getPath();
247 if (std::regex_match(path, match, route.compiledRegex)) // <--- USE precompiled
248 {
249 for (size_t i = 1; i < match.size(); ++i)
250 {
251 TRACE("Matched param %zu: %s\n", i, match[i].str().c_str());
252 params.push_back(urlDecode(match[i].str()));
253 }
254 matchedRoute = &route;
255 matched = true;
256 TRACE("Matched route: %s\n", route.path.c_str());
257 return;
258 }
259 }
260 });
261 TRACE("Matched: %s\n", matched ? "true" : "false");
262 if (matched && matchedRoute)
263 {
264 RouteMatch match;
265 match.ordered = params;
266 const auto &names = matchedRoute->paramNames;
267
268 for (size_t i = 0; i < names.size() && i < params.size(); ++i)
269 {
270 match.named[names[i]] = params[i];
271 }
272
273 TRACE("Matched route: %s\n", matchedRoute->path.c_str());
274 matchedRoute->handler(req, res, match);
275 return true;
276 }
277
278 TRACE("No matching regular route found\n");
279
280 if (req.getMethod() == "GET" && hasCatchallGetRoute)
281 {
282 TRACE("Falling back to catch-all GET route\n");
283 RouteMatch dummyMatch; // Dummy match for catch-all route
284 catchallGetRoute.handler(req, res, dummyMatch);
285 return true;
286 }
287
288 printRoutes();
289 return false;
290}
291
292// Prints all registered routes (for debugging).
295{
296 TRACE("Routes:\n");
297 withRoutes([&](auto &r)
298 {
299 for (const auto &method_pair : routes)
300 {
301 TRACE("Method: %s\n", method_pair.first.c_str());
302 for (const auto &route : method_pair.second)
303 {
304 TRACE(" Route: %s, Dynamic: %s, Requires Auth: %s\n",
305 route.path.c_str(),
306 route.isDynamic ? "true" : "false",
307 route.requiresAuth ? "true" : "false");
308 }
309 } });
310}
311
312// Serve static files using the HttpFileserver instance.
315{
316 fileServer.handle_static_request(req, res, match);
317}
318
319// Handle list directory using HttpFileserver instance.
322{
323
324 fileServer.handle_list_directory(req, res, match);
325 ;
326}
327
328void Router::withRoutes(const std::function<void(std::unordered_map<std::string, std::vector<Route>> &)> &fn)
329{
330 const char* taskName = pcTaskGetName(nullptr);
331
332 if (xSemaphoreTakeRecursive(lock_, pdMS_TO_TICKS(5000)) != pdTRUE)
333 {
334 printf("[Router] [%s] ERROR - failed to acquire lock within timeout\n", taskName);
335 return;
336 }
337
338 fn(routes);
339
340 xSemaphoreGiveRecursive(lock_);
341 vTaskDelay(pdMS_TO_TICKS(1)); // Give any pending route-registration task a chance to run
342}
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
#define configASSERT(x)
Defines the HttpRequest class for handling HTTP requests: headers, method, path, query string,...
HTTP HttpResponse class for managing status, headers, body, and streaming support.
Utility functions to send standard JSON responses using nlohmann::json.
Stateless singleton class for creating and validating JWTs using HMAC-SHA256.
Middleware definitions for HTTP routing (e.g., logging, auth). Part of the PicoFramework HTTP server....
static std::string extractBearerToken(const std::string &auth_header)
Definition Router.cpp:49
SemaphoreHandle_t lock_
Definition Router.cpp:44
HTTP routing with middleware and optional JWT-based authorization. Part of the PicoFramework HTTP ser...
std::function< void(HttpRequest &, HttpResponse &, const RouteMatch &)> RouteHandler
Function signature for HTTP route handlers.
Definition Router.h:41
std::function< bool(HttpRequest &, HttpResponse &, const RouteMatch &)> Middleware
Function signature for middleware.
Definition Router.h:48
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
void handle_list_directory(HttpRequest &req, HttpResponse &res, const RouteMatch &match)
Handle requests to list directory contents.
void handle_static_request(HttpRequest &req, HttpResponse &res, const RouteMatch &match)
Handle requests for static file content.
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
const std::string & getMethod() const
Get the HTTP method.
const std::string & getPath() const
Get the parsed request path (without query string).
std::string getHeader(const std::string &field) const
Get a specific header field (case-insensitive).
Represents an HTTP response object.
The central router for handling HTTP requests and middleware.
Definition Router.h:60
std::unordered_map< std::string, std::vector< Route > > routes
Definition Router.h:176
void withRoutes(const std::function< void(std::unordered_map< std::string, std::vector< Route > > &)> &fn)
Definition Router.cpp:328
bool isAuthorizedForRoute(const Route &route, HttpRequest &req, HttpResponse &res)
Check if a route requires and is granted JWT authorization.
Definition Router.h:151
void addCatchAllGetRoute(RouteHandler handler, std::vector< Middleware > middleware={})
Register a catch-all route with optional middleware.
Definition Router.cpp:203
SemaphoreHandle_t lock_
Definition Router.h:183
static StaticSemaphore_t lockBuffer_
Definition Router.h:182
HttpFileserver fileServer
Internal file server instance.
Definition Router.h:175
void serveStatic(HttpRequest &req, HttpResponse &res, const RouteMatch &match)
Serve static files from the internal HttpFileserver.
Definition Router.cpp:314
Route catchallGetRoute
Catch-all route for unmatched requests.
Definition Router.h:177
Router()
Construct the router instance.
Definition Router.cpp:61
void printRoutes()
Print all registered routes to stdout.
Definition Router.cpp:294
std::string getAuthorizationToken(const HttpRequest &req)
Returns the cached Authorization token, or extracts it from the request.
Definition Router.cpp:69
bool handleRequest(HttpRequest &req, HttpResponse &res)
Handle an incoming HTTP request.
Definition Router.cpp:228
bool hasCatchallGetRoute
Flag to indicate if a catch-all route exists.
Definition Router.h:178
void listDirectory(HttpRequest &req, HttpResponse &res, const RouteMatch &match)
Convenience method to list directory from the internal HttpFileserver.
Definition Router.cpp:321
std::vector< Middleware > globalMiddleware
Definition Router.h:180
void addRoute(const std::string &method, const std::string &path, RouteHandler handler, std::vector< Middleware > middleware={})
Register a route with optional middleware.
Definition Router.cpp:144
void use(Middleware middleware)
Register a global middleware function.
Definition Router.cpp:110
Delegates to user or system configuration.
void sendError(HttpResponse &res, int statusCode, const std::string &code, const std::string &message)
void sendSuccess(HttpResponse &res, const nlohmann::json &data, const std::string &message)
Represents a match of a route against an incoming HTTP request.
Definition RouteTypes.h:18
std::vector< std::string > ordered
Definition RouteTypes.h:19
std::unordered_map< std::string, std::string > named
Definition RouteTypes.h:20
Represents a single HTTP route.
Definition RouteTypes.h:42
std::vector< std::string > paramNames
Definition RouteTypes.h:49
bool isDynamic
Definition RouteTypes.h:47
RouteHandler handler
Definition RouteTypes.h:46
bool requiresAuth
Definition RouteTypes.h:48
std::string path
Definition RouteTypes.h:44
std::regex compiledRegex
Definition RouteTypes.h:45
std::string urlDecode(const std::string &src)
Definition url_utils.cpp:28
Utility functions for URL decoding, form parsing, MIME type lookup, and IP address extraction.
System utilities for diagnostics, memory, stack usage, and tracing.