Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpResponse.cpp
Go to the documentation of this file.
1
18#include "http/HttpResponse.h"
19#include "http/JsonResponse.h"
20#include "framework_config.h"
21#include "DebugTrace.h"
23
24#include <sstream>
25#include <cstring>
26#include <lwip/sockets.h>
27#include "utility/utility.h"
29#include "http/HttpFileserver.h"
30
31// ------------------------------------------------------------------------
32// Constructor
33// ------------------------------------------------------------------------
34
40 : tcp(tcp), status_code(200), headerSent(false)
41{
42}
43
44// ------------------------------------------------------------------------
45// Status and Header Management
46// ------------------------------------------------------------------------
47
52{
53 status_code = code;
54 return *this;
55}
56
61{
62 status_code = code;
63 return *this;
64}
65
69HttpResponse& HttpResponse::set(const std::string &field, const std::string &value)
70{
71 headers[field] = value;
72 return *this;
73}
74
78HttpResponse& HttpResponse::setHeader(const std::string &key, const std::string &value)
79{
80 headers[key] = value;
81 return *this;
82}
83
88{
89 headers["Content-Type"] = ct;
90 return *this;
91}
92
96HttpResponse& HttpResponse::setAuthorization(const std::string &jwtToken)
97{
98 if (!jwtToken.empty())
99 {
100 headers["Authorization"] = "Bearer " + jwtToken;
101 }
102 return *this;
103}
104
109{
110 auto it = headers.find("Content-Type");
111 return (it != headers.end()) ? it->second : "text/html";
112}
113
118{
119 return headerSent;
120}
121
126{
127 return tcp->getSocketFd();
128}
129
136{
137 switch (code)
138 {
139 case 200:
140 return "OK";
141 case 401:
142 return "Unauthorized";
143 case 404:
144 return "Not Found";
145 case 500:
146 return "Internal Server Error";
147 default:
148 return "";
149 }
150}
151
152// ------------------------------------------------------------------------
153// Cookie Support
154// ------------------------------------------------------------------------
155
159HttpResponse &HttpResponse::setCookie(const std::string &name, const std::string &value, const std::string &options)
160{
161 std::ostringstream cookie;
162 cookie << name << "=" << value;
163 if (!options.empty())
164 {
165 cookie << "; " << options;
166 }
167 cookies.push_back(cookie.str());
168 return *this;
169}
170
174HttpResponse &HttpResponse::clearCookie(const std::string &name, const std::string &options)
175{
176 std::ostringstream cookie;
177 cookie << name << "=; Max-Age=0";
178 if (!options.empty())
179 {
180 cookie << "; " << options;
181 }
182 cookies.push_back(cookie.str());
183 return *this;
184}
185
186// ------------------------------------------------------------------------
187// Body and Streaming
188// ------------------------------------------------------------------------
189
193void HttpResponse::send(const std::string &body)
194{
195 TRACE("HttpResponse::send()\n");
196 if (!headerSent)
197 {
198 if (headers.find("Content-Length") == headers.end())
199 {
200 headers["Content-Length"] = std::to_string(body.size());
201 }
202 if (headers.find("Content-Type") == headers.end())
203 {
204 headers["Content-Type"] = "text/html";
205 }
206
207 std::ostringstream resp;
208 resp << "HTTP/1.1 " << status_code << " " << getStatusMessage(status_code) << "\r\n";
209 for (auto &h : headers)
210 {
211 resp << h.first << ": " << h.second << "\r\n";
212 }
213 for (const auto &cookie : cookies)
214 {
215 resp << "Set-Cookie: " << cookie << "\r\n";
216 }
217 resp << "\r\n";
218
219 std::string header_str = resp.str();
220 tcp->send(header_str.c_str(), header_str.size());
221 headerSent = true;
222 }
223
224 tcp->send(body.data(), body.size());
225 TRACE("HttpResponse::send() completed\n");
226}
227
232{
233 if (!headerSent)
234 {
235 std::ostringstream resp;
236 resp << "HTTP/1.1 " << status_code << " " << getStatusMessage(status_code) << "\r\n";
237
238 for (auto &h : headers)
239 {
240 resp << h.first << ": " << h.second << "\r\n";
241 }
242 for (const auto &cookie : cookies)
243 {
244 resp << "Set-Cookie: " << cookie << "\r\n";
245 }
246 if (headers.find("Connection") == headers.end())
247 {
248 resp << "Connection: close\r\n";
249 }
250 resp << "\r\n";
251
252 std::string header_str = resp.str();
253 tcp->send(header_str.c_str(), header_str.size());
254 headerSent = true;
255 }
256}
257
261void HttpResponse::start(int code, size_t contentLength, const std::string &contentType, const std::string &contentEncoding)
262{
263 status_code = code;
264 headers["Content-Length"] = std::to_string(contentLength);
265 headers["Content-Type"] = contentType;
266 if (!contentEncoding.empty())
267 {
268 headers["Content-Encoding"] = contentEncoding;
269 }
270
271 std::ostringstream resp;
272 resp << "HTTP/1.1 " << status_code << " " << getStatusMessage(status_code) << "\r\n";
273 for (auto &h : headers)
274 {
275 resp << h.first << ": " << h.second << "\r\n";
276 }
277 for (const auto &cookie : cookies)
278 {
279 resp << "Set-Cookie: " << cookie << "\r\n";
280 }
281 resp << "\r\n";
282
283 std::string header_str = resp.str();
284 tcp->send(header_str.c_str(), header_str.size());
285 headerSent = true;
286}
287
291void HttpResponse::writeChunk(const char *data, size_t length)
292{
293 if (!headerSent)
294 {
295 printf("Error: writeChunk called before start()\n");
296 return;
297 }
298
299 int err = tcp->send(data, length);
300 if (err < 0)
301 {
302 printf("Error sending chunk: %zu\n", err);
303 printf("Error: %s\n", strerror(errno));
304 }
305}
306
311{
312 // Placeholder for future expansion (e.g., chunked transfer end).
313}
314
315// ------------------------------------------------------------------------
316// Helpers
317// ------------------------------------------------------------------------
318
323{
324 this->status(401)
325 .set("Content-Type", "application/json")
326 .send("{\"error\": \"Unauthorized\"}");
327}
328
333{
334 return status(404)
335 .setContentType("application/json")
336 .send(R"({"error": "Not Found"})");
337}
338
342void HttpResponse::endServerError(const std::string &message)
343{
344 return status(500)
345 .setContentType("application/json")
346 .send("{\"error\": \"" + message + "\"}");
347}
348
352HttpResponse &HttpResponse::json(const std::string &body)
353{
354 this->set("Content-Type", "application/json")
355 .send(body);
356 return *this;
357}
358// send a json object
359HttpResponse &HttpResponse::json(const nlohmann::json &jsonObj)
360{
361 return json(jsonObj.dump()); // dump() creates a compact string
362}
363
364HttpResponse &HttpResponse::jsonFormatted(const nlohmann::json &jsonObj)
365{
366 return json(jsonObj.dump(2)); // dump() creates a compact string
367}
368
372HttpResponse &HttpResponse::text(const std::string &body)
373{
374 this->set("Content-Type", "text/plain")
375 .send(body);
376 return *this;
377}
378
379HttpResponse &HttpResponse::redirect(const std::string &url, int code)
380{
381 this->status(code)
382 .set("Location", url)
383 .send("");
384 return *this;
385}
386
390std::string HttpResponse::renderTemplate(const std::string &tpl, const std::map<std::string, std::string> &context)
391{
392 std::string result = tpl;
393 for (const auto &[key, value] : context)
394 {
395 std::string placeholder = "{{" + key + "}}";
396 size_t pos = 0;
397 while ((pos = result.find(placeholder, pos)) != std::string::npos)
398 {
399 result.replace(pos, placeholder.length(), value);
400 pos += value.length();
401 }
402 }
403 return result;
404}
405
406
407// ------------------------------------------------------------------------
408// ───── View related ─────
409// ------------------------------------------------------------------------
410
411
412void HttpResponse::send(const std::string& body, const std::string& contentType) {
413 setHeader("Content-Type", contentType);
414 send(body);
415}
416
418
420 const std::map<std::string, std::string>& context) {
421 view.applyHeaders(*this);
423 std::string body = view.render(context);
424 send(body);
425}
426
427
428
429HttpResponse& HttpResponse::setBody(const std::string& body) {
430 this->body = body;
431 return *this;
432}
433
435 status_code = 0;
436 headers.clear();
437 body.clear();
438}
439
440bool HttpResponse::sendFile(const std::string& path)
441{
442 FileHandler fileHandler;
443 return fileHandler.serveFile(*this, path.c_str());
444}
445
446HttpResponse& HttpResponse::sendError(int statusCode, const std::string& message) {
447 JsonResponse::sendError(*this, statusCode, "error", message);
448 return *this;
449}
450
451//#if defined(PICO_HTTP_ENABLE_STORAGE)
452HttpResponse& HttpResponse::toFile(const std::string& path, StorageManager* storage)
453{
454 if (!storage || body.empty())
455 return *this;
456
457 storage->writeFile(path,
458 reinterpret_cast<const unsigned char*>(body.data()),
459 body.size());
460 return *this;
461}
462//#endif
463
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
Abstract base class for all views in the PicoFramework.
HTTP file server and file handling helpers for static content.
HTTP HttpResponse class for managing status, headers, body, and streaming support.
nlohmann::json json
Utility functions to send standard JSON responses using nlohmann::json.
Helper class for accessing the file system and serving file content.
bool serveFile(HttpResponse &res, const char *uri)
Serve a file to the client via the HttpResponse object.
virtual std::string render(const std::map< std::string, std::string > &context={}) const =0
Render the view body.
virtual std::string getContentType() const =0
Return the MIME content type for this view.
virtual void applyHeaders(HttpResponse &response) const
Optional hook to set response headers (e.g., Content-Disposition).
Represents an HTTP response object.
void sendUnauthorized()
Send a 401 Unauthorized JSON response.
int status_code
HTTP status code.
HttpResponse & setStatus(int code)
Alias for status().
std::string renderTemplate(const std::string &tpl, const std::map< std::string, std::string > &context)
Apply basic variable substitution in a template.
HttpResponse & toFile(const std::string &path, StorageManager *storage)
HttpResponse & setContentType(const std::string &content_type)
Set the Content-Type header.
void writeChunk(const char *data, size_t length)
Send a chunk of the response body.
HttpResponse()=default
HttpResponse & text(const std::string &textString)
Send a plain text string with correct content type.
int getSocket() const
Return the raw socket descriptor.
HttpResponse & set(const std::string &field, const std::string &value)
Set a generic header field.
void finish()
Finish the response (placeholder for potential finalization).
bool isHeaderSent() const
Check if the headers have already been sent.
HttpResponse & setAuthorization(const std::string &jwtToken)
Set an Authorization header with a JWT token.
void sendNotFound()
Send a 404 Not Found JSON response.
void endServerError(const std::string &msg)
Send a 500 Internal Server Error response.
HttpResponse & jsonFormatted(const nlohmann::json &jsonObj)
void sendHeaders()
Send only the headers (for chunked/streaming responses).
HttpResponse & json(const std::string &jsonString)
Send a JSON string/object with correct content type.
void send(const std::string &body)
Send a full response including headers and body.
HttpResponse & setCookie(const std::string &name, const std::string &value, const std::string &options)
Set a cookie to be sent with the response.
HttpResponse & clearCookie(const std::string &name, const std::string &options)
Clear a cookie by setting Max-Age=0.
HttpResponse & status(int code)
Set the HTTP status code.
Tcp * tcp
Pointer to the Tcp object for socket operations.
std::string getContentType() const
Get the current Content-Type header value.
std::string body
Full response body (client-side or buffered server content)
HttpResponse & setBody(const std::string &body)
Set the body of the response (string).
bool sendFile(const std::string &path)
Sends the specified file from mounted storage to the client.
std::vector< std::string > cookies
Set-Cookie headers (server only)
HttpResponse & redirect(const std::string &url, int statusCode)
Redirect the client to another URL.
std::map< std::string, std::string > headers
Response headers (server+client)
bool headerSent
Tracks whether headers have already been sent.
void reset()
Clear the response status, headers, and body.
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.
std::string getStatusMessage(int code)
Convert an HTTP status code to its standard message.
HttpResponse & sendError(int statusCode, const std::string &code, const std::string &message)
HttpResponse & setHeader(const std::string &key, const std::string &value)
Alias for set() for custom headers.
Abstract base class for storage access and file operations.
virtual bool writeFile(const std::string &path, const std::vector< uint8_t > &data)=0
Write a memory buffer to a file.
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
int send(const char *buffer, size_t size)
Send data over the connection.
Definition Tcp.cpp:279
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.
void sendError(HttpResponse &res, int statusCode, const std::string &code, const std::string &message)
System utilities for diagnostics, memory, stack usage, and tracing.