Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpFileserver.cpp
Go to the documentation of this file.
1
17#include "http/HttpFileserver.h"
18
19#include "framework_config.h" // Must be included before DebugTrace.h to ensure framework_config.h is processed first
20#include "DebugTrace.h"
22
23#include <string>
24#include <vector>
25#include <cstdio>
26#include <cstring>
27#include <lwip/sockets.h>
28#include <unordered_map>
29
30#include <FreeRTOS.h>
31#include <task.h>
32
34#include "utility/utility.h"
35#include "http/url_utils.h"
38#include "framework_config.h"
39#include "http/JsonResponse.h"
40
41#define TRACE_ON
42
43// ----------------------------------------------------------------------------
44// FileHandler Implementation
45// ----------------------------------------------------------------------------
46
51
54{
56 if (storage->mount())
57 {
58 TRACE("Storage mounted successfully\n");
59 return true;
60 }
61 else
62 {
63 printf("Storage mount failed\n");
64 return false;
65 }
66}
67
69bool FileHandler::listDirectory(const std::string &path, std::vector<FileInfo> &out)
70{
71 auto *storage = AppContext::get<StorageManager>();
72 return storage->listDirectory(path, out);
73}
74
76bool FileHandler::serveFile(HttpResponse &res, const char *uri)
77{
78 std::string path = uri;
79
81 if (!storageManager->mount())
82 {
83 printf("Storage mount failed\n");
84 JsonResponse::sendError(res, 500, "MOUNT_FAILED", "Storage mount failed");
85 return false;
86 }
87
88 if (!storageManager->exists(path))
89 {
91 {
92 TRACE("Storage is not mounted — static file access failed\n");
93 JsonResponse::sendError(res, 500, "NOT_MOUNTED", "Storage not mounted");
94 return false;
95 }
96 else
97 {
98 printf("File not found: %s\n", path.c_str());
99 JsonResponse::sendError(res, 404, "NOT_FOUND", "File Not Found: " + std::string(uri));
100 return false;
101 }
102 }
103
104 size_t fileSize = storageManager->getFileSize(path);
105 if (fileSize == 0)
106 {
107 JsonResponse::sendError(res, 500, "FILESIZE_ERROR", "Error getting file size for: " + std::string(uri));
108 return false;
109 }
110
111 std::string mimeType = getMimeType(path);
112 TRACE("Serving file: %s, size: %zu bytes, MIME type: %s\n", path.c_str(), fileSize, mimeType.c_str());
113
114 if (mimeType == "text/html" || mimeType == "application/javascript" || mimeType == "text/css")
115 {
116 // Gzip magic number: 0x1F 0x8B (in little-endian)
117 const uint8_t gzip_magic_number[] = {0x1F, 0x8B};
118 std::string magic_number;
119 if (storageManager->readFileString(path, 0, 2, magic_number))
120 {
121 TRACE("Read magic number in hex: ");
122 for (size_t i = 0; i < magic_number.size(); ++i)
123 {
124 TRACE("%02X ", static_cast<unsigned char>(magic_number[i]));
125 }
126 TRACE("\n");
127 if (magic_number[0] == gzip_magic_number[0] && magic_number[1] == gzip_magic_number[1]) // GZIP magic number
128 {
129 TRACE("File is already gzipped: %s\n", path.c_str());
130 TRACE("Setting Content-Encoding to gzip\n");
131 res.set("Content-Encoding", "gzip");
132 }
133 }
134 }
135
136 res.start(200, fileSize, mimeType.c_str());
137
138 storageManager->streamFile(path, [&](const uint8_t *data, size_t len)
139 {
140 res.writeChunk(reinterpret_cast<const char*>(data), len);
141 vTaskDelay(pdMS_TO_TICKS(STREAM_SEND_DELAY_MS)); }); // allow tcpip thread to get in
142
143 res.finish();
144 return true;
145}
146
147// ----------------------------------------------------------------------------
148// HttpFileserver Implementation
149// ----------------------------------------------------------------------------
150
153{
154 // fileHandler.init(); // switched to lazy init to avoid startup issues
155}
156
159{
160 std::string directory_path = req.getPath();
161 int pos = directory_path.find("/api/v1/ls");
162 if (pos != std::string::npos)
163 {
164 directory_path = directory_path.substr(pos + strlen("/api/v1/ls"));
165 }
166
167 if (directory_path.empty())
168 {
169 directory_path = "/"; // Default to root directory if no path is specified
170 }
171
172 std::vector<FileInfo> entries;
173 if (!fileHandler.listDirectory(directory_path, entries))
174 {
175 res.sendError(404, "not_found", "Directory not found or inaccessible");
176 return;
177 }
178
179 nlohmann::json fileArray = nlohmann::json::array();
180 for (const auto &entry : entries)
181 {
182 fileArray.push_back({{"name", entry.name},
183 {"size", entry.size},
184 {"type", entry.isDirectory ? "directory" : "file"}});
185 }
186
187 nlohmann::json result = {
188 {"path", directory_path},
189 {"files", fileArray}};
190
191 res.sendSuccess(result, "Directory listed successfully.");
192}
193
194// Helper function to check if a string ends with a given suffix
195bool ends_with(const std::string &str, const std::string &suffix)
196{
197 if (str.length() < suffix.length())
198 return false;
199 return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
200}
201
202// Fallback route for serving any static file detected in router
205{
206 const std::string &uri = req.getPath();
207 printf("Serving static request for URI: %s\n", uri.c_str());
208
209 std::string filePath = urlDecode(uri);
210 printf("Decoded URI: %s\n", filePath.c_str());
211
212 if (uri.empty() || uri == "/")
213 {
214 filePath = "/index.html";
215 }
216
217
218 fileHandler.serveFile(res, filePath.c_str());
219}
220
222std::string HttpFileserver::getMimeType(const std::string &filePath)
223{
224 TRACE("Getting MIME type for file: %s\n", filePath.c_str());
225
226 static const std::unordered_map<std::string, std::string> mimeTypes = {
227 {".html", "text/html"},
228 {".css", "text/css"},
229 {".js", "application/javascript"},
230 {".json", "application/json"},
231 {".jpg", "image/jpeg"},
232 {".jpeg", "image/jpeg"},
233 {".png", "image/png"},
234 {".gif", "image/gif"},
235 {".txt", "text/plain"},
236 {".xml", "application/xml"},
237 {".pdf", "application/pdf"},
238 {".zip", "application/zip"},
239 {".gz", "application/x-gzip-compressed"},
240 {".tar", "application/x-tar"},
241 {".mp4", "video/mp4"},
242 {".webm", "video/webm"},
243 {".ogg", "audio/ogg"},
244 {".flac", "audio/flac"},
245 {".aac", "audio/aac"},
246 {".mp4", "video/mp4"},
247 {".mp3", "audio/mpeg"},
248 {".wav", "audio/wav"},
249 {".csv", "text/csv"}};
250
251 size_t extPos = filePath.find_last_of(".");
252 if (extPos != std::string::npos)
253 {
254 std::string ext = filePath.substr(extPos);
255 TRACE("Extracted extension: %s\n", ext.c_str());
256
257 auto it = mimeTypes.find(ext);
258 if (it != mimeTypes.end())
259 {
260 return it->second;
261 }
262 }
263
264 return "application/octet-stream";
265}
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
bool ends_with(const std::string &str, const std::string &suffix)
HTTP file server and file handling helpers for static content.
Utility functions to send standard JSON responses using nlohmann::json.
Abstract interface for file and directory storage backends.
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
FileHandler()
Construct a new FileHandler object.
bool serveFile(HttpResponse &res, const char *uri)
Serve a file to the client via the HttpResponse object.
StorageManager * storageManager
bool listDirectory(const std::string &path, std::vector< FileInfo > &out)
Return a list of a given directory.
bool init()
Initialize and mount the storage (e.g., SD card).
HTTP-level controller for serving static files and directory listings.
std::string getMimeType(const std::string &filePath)
Get the MIME type based on the file extension.
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.
FileHandler fileHandler
HttpFileserver()
Construct a new HttpFileserver object.
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
const std::string & getPath() const
Get the parsed request path (without query string).
Represents an HTTP response object.
void writeChunk(const char *data, size_t length)
Send a chunk of the response body.
HttpResponse & set(const std::string &field, const std::string &value)
Set a generic header field.
void finish()
Finish the response (placeholder for potential finalization).
void start(int code, size_t contentLength, const std::string &contentType="application/octet-stream", const std::string &contentEncoding="")
Begin a streaming response by sending headers.
HttpResponse & sendSuccess(const nlohmann::json &data={}, const std::string &message="")
HttpResponse & sendError(int statusCode, const std::string &code, const std::string &message)
Abstract base class for storage access and file operations.
virtual bool isMounted() const =0
Check mounted.
virtual bool readFileString(const std::string &path, uint32_t startPosition, uint32_t length, std::string &buffer)=0
Read a file string into a memory buffer.
virtual size_t getFileSize(const std::string &path)=0
Get the size of a file.
virtual bool streamFile(const std::string &path, std::function< void(const uint8_t *, size_t)> chunkCallback)=0
Stream a file in chunks via callback.
virtual bool exists(const std::string &path)=0
Check whether a file or directory exists at the given path.
virtual bool mount()=0
Mount the underlying storage.
Delegates to user or system configuration.
#define STREAM_SEND_DELAY_MS
void sendError(HttpResponse &res, int statusCode, const std::string &code, const std::string &message)
Represents a match of a route against an incoming HTTP request.
Definition RouteTypes.h:18
std::string getMimeType(const std::string &filePath)
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.