Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpRequest.cpp
Go to the documentation of this file.
1
18#include "framework_config.h" // Must be included before DebugTrace.h to ensure framework_config.h is processed first
19#include "DebugTrace.h"
21
22#include "http/HttpRequest.h"
23#include <sstream>
24#include <algorithm>
25#include <cctype>
26#include <iostream>
27#include <cstring>
28#include <cstdio>
29#include <unordered_map>
30#include "pico/stdlib.h"
31// #include <lwip/sockets.h>
32
33#include "http/HttpServer.h"
34#include "utility/utility.h"
36#include "http/url_utils.h"
37#include "http/HttpParser.h"
38#include "network/Tcp.h"
39
40#ifdef PICO_HTTP_ENABLE_HTTP_CLIENT
41#include "http/HttpClient.h"
42#endif // PICO_HTTP_ENABLE_HTTP_CLIENT
43
44#define BUFFER_SIZE 256
45
47{
48 setMethod("GET");
49 return send();
50}
51
52HttpResponse HttpRequest::get(const std::string &url)
53{
54 setMethod("GET");
55 setUri(url);
56 return send();
57}
58
60{
61 setMethod("POST");
62 return send();
63}
64
66{
67 setMethod("PUT");
68 return send();
69}
70
72{
73 setMethod("DELETE");
74 return send();
75}
76
77HttpResponse HttpRequest::post(const std::string &url, const std::string &body)
78{
79 setMethod("POST");
80 setUri(url);
82 return send();
83}
84
85HttpResponse HttpRequest::put(const std::string &url, const std::string &body)
86{
87 setMethod("PUT");
88 setUri(url);
90 return send();
91}
92
93HttpResponse HttpRequest::del(const std::string &url)
94{
95 setMethod("DELETE");
96 setUri(url);
97 return send();
98}
99
101{
102#ifdef PICO_HTTP_ENABLE_HTTP_CLIENT
103 HttpResponse response;
104 HttpClient client;
105 client.sendRequest(*this, response);
106 return response;
107#else
108 // If HTTP client is not enabled, return an empty response
109 return HttpResponse(nullptr);
110#endif
111}
112
120HttpRequest::HttpRequest(const char *rawHeaders, const std::string &reqMethod, const std::string &reqPath)
121 : method(reqMethod), path(reqPath)
122{
123
124 uri = reqPath; // Store the original URL
125 // Parse the URL to separate path and query string
126 size_t pos = uri.find('?');
127 if (pos != std::string::npos)
128 {
129 path = uri.substr(0, pos);
130 query = uri.substr(pos + 1);
131 }
132 else
133 {
134 path = uri;
135 query = "";
136 }
137 parseHeaders(rawHeaders);
138}
139
140HttpRequest& HttpRequest::setBody(const std::string &b)
141{
142 body = b;
143 return *this;
144}
145
147{
148 setHeader("Content-Type", "application/json");
149 body = json;
150 return *this;
151}
152
153HttpRequest& HttpRequest::setJson(const nlohmann::json &json)
154{
155 setHeader("Content-Type", "application/json");
156 body = json.dump();
157 return *this;
158}
159
160void HttpRequest::parseHeaders(const char *raw)
161{
163}
164
174bool HttpRequest::getMethodAndPath(const std::string& rawHeaders, std::string& method, std::string& path)
175{
176 TRACE("Parsing request data: %s\n", rawHeaders.c_str());
177 std::istringstream stream(rawHeaders);
178 std::string requestLine;
179 if (!std::getline(stream, requestLine)) {
180 printf("Failed to read request line\n");
181 return false;
182 }
183
184 std::istringstream lineStream(requestLine);
185 if (!(lineStream >> method >> path)) {
186 printf("Error parsing HTTP request method and path\n");
187 return false;
188 }
189 return true;
190}
191
192constexpr size_t MAX_HEADER_BYTES = 4096; // limit to prevent runaway headers if malformed
193
194std::optional<std::pair<std::string, std::string>> HttpRequest::receiveUntilHeadersComplete(Tcp* conn) {
195 std::string requestText;
196 char buffer[HTTP_BUFFER_SIZE];
197
198 while (true) {
199 int received = conn->recv(buffer, sizeof(buffer), HTTP_RECEIVE_TIMEOUT);
200 if (received <= 0) {
201 printf("[HttpRequest] Failed to receive header - usually Safari reusing socket it shouldn't\n");
202 return std::nullopt;
203 }
204
205 requestText.append(buffer, received);
206
207 size_t headerEnd = requestText.find("\r\n\r\n");
208 if (headerEnd != std::string::npos) {
209 size_t bodyStart = headerEnd + 4;
210 std::string headers = requestText.substr(0, headerEnd);
211 std::string leftover = (bodyStart < requestText.size())
212 ? requestText.substr(bodyStart)
213 : "";
214 return std::make_pair(std::move(headers), std::move(leftover));
215 }
216
217 if (requestText.size() > MAX_HEADER_BYTES) {
218 printf("[HttpRequest] Headers exceeded %zu bytes, rejecting\n", MAX_HEADER_BYTES);
219 return std::nullopt;
220 }
221 }
222}
223
224bool HttpRequest::appendRemainingBody(int expectedLength) {
225 size_t remaining = expectedLength - body.size();
226 char buffer[HTTP_BUFFER_SIZE];
227
228 while (remaining > 0) {
229 size_t toRead = std::min(remaining, sizeof(buffer));
230 int received = tcp->recv(buffer, toRead, HTTP_RECEIVE_TIMEOUT);
231 if (received <= 0) {
232 printf("Error receiving body chunk\n");
233 return false;
234 }
235 size_t currentSize = body.size();
236 if (currentSize >= MAX_HTTP_BODY_LENGTH) {
237 TRACE("Body exceeds max length. Truncating.\n");
239 break;
240 }
241 size_t allowed = MAX_HTTP_BODY_LENGTH - currentSize;
242 size_t toAppend = std::min(static_cast<size_t>(received), allowed);
243 body.append(buffer, toAppend);
244
245 if (toAppend < static_cast<size_t>(received)) {
246 TRACE("Body chunk truncated due to size limit.\n");
248 break;
249 }
250 remaining -= received;
251 }
252 return true;
253}
254
255
256
267{
268 TRACE("Receiving request on socket %d\n", tcp->getSocketFd());
269
270 char buffer[BUFFER_SIZE]; // Declare buffer size
271 std::string body = ""; // Initialize empty body
272 std::map<std::string, std::string> headers; // Initialize empty headers
273 std::string method = {0}; // Initialize method buffer
274 std::string path = {0}; // Initialize path buffer
275
276 auto result = receiveUntilHeadersComplete(tcp);
277 if (!result) {
278 return HttpRequest("", "", "");
279 }
280
281 const auto& [rawHeaders, initialBody] = *result;
282
283 if (!getMethodAndPath(rawHeaders, method, path)) {
284 return HttpRequest("", "", "");
285 }
286
287 // Create the request which will parse the headers
288 TRACE("Raw headers: %s\n", rawHeaders.c_str());
289
290 HttpRequest request(tcp, rawHeaders, std::string(method), std::string(path));
291 request.setBody(initialBody); // Set the initial body if any
292
293 // NOW split path and query
294 std::string cleanPath = path;
295 std::string queryString = "";
296
297 size_t qpos = cleanPath.find('?');
298 if (qpos != std::string::npos)
299 {
300 queryString = cleanPath.substr(qpos + 1);
301 cleanPath = cleanPath.substr(0, qpos);
302 }
303 request.setPath(cleanPath); // Set the cleaned path as the URI
304 request.setQueryString(queryString); // Set the query string
305
306 // Get headers from the HttpRequest object
307 headers = request.getHeaders();
308
309 TRACE("Parsed headers:\n");
310 for (const auto &header : headers)
311 {
312 TRACE("%s: %s\n", header.first.c_str(), header.second.c_str());
313 }
314
315 int contentLength = request.getContentLength();
316 TRACE("Content-Length: %d\n", contentLength);
317
318 if (contentLength > 0)
319 {
320 TRACE("Content-Length is greater than 0\n");
321 if (request.isMultipart())
322 {
323 TRACE("Multipart request detected\n");
324 return request;
325 }
326
327 TRACE("Non-multipart request detected\n");
328
329 request.appendRemainingBody(contentLength);
330 TRACE("Final body length: %zu\n", body.length());
331 TRACE("HttpRequest object constructed\n");
332 }
333 return request;
334}
335
344{
345 MultipartParser parser;
346 return parser.handleMultipart(*this, res) ? 0 : -1;
347}
348
354const std::unordered_map<std::string, std::string> HttpRequest::getCookies() const
355{
356 std::unordered_map<std::string, std::string> cookies;
357 std::string cookieHeader = getHeader("cookie");
358 std::istringstream stream(cookieHeader);
359 std::string pair;
360 while (std::getline(stream, pair, ';'))
361 {
362 size_t eq = pair.find('=');
363 if (eq != std::string::npos)
364 {
365 std::string key = pair.substr(0, eq);
366 std::string value = pair.substr(eq + 1);
367 key.erase(0, key.find_first_not_of(" \t"));
368 value.erase(0, value.find_first_not_of(" \t"));
369 cookies[key] = value;
370 }
371 }
372 return cookies;
373}
374
381const std::string HttpRequest::getCookie(const std::string &name) const
382{
383 auto cookies = getCookies();
384 auto it = cookies.find(name);
385 return (it != cookies.end()) ? it->second : "";
386}
387
393const std::unordered_multimap<std::string, std::string> HttpRequest::getQueryParams()
394{
395 return parseUrlEncoded(query);
396}
397
403const std::unordered_multimap<std::string, std::string> HttpRequest::getFormParams()
404{
405 return parseUrlEncoded(body);
406}
407
408// ─────────────────────────────────────────────────────────────────────────────
409// Fluent Builder Methods - some shared server-side
410// ─────────────────────────────────────────────────────────────────────────────
411
413{
414 return HttpRequest("", "", "");
415}
416
423HttpRequest &HttpRequest::setUri(const std::string &uri)
424{
425 const auto proto_end = uri.find("://");
426 if (proto_end == std::string::npos)
427 {
428 // Relative URI — just set it, do not overwrite protocol or host
429 this->uri = uri;
430 return *this;
431 }
432
433 // Full URL — parse into protocol, host, and path
434 protocol = uri.substr(0, proto_end);
435 std::string rest = uri.substr(proto_end + 3); // skip "://"
436
437 const auto path_start = rest.find('/');
438 if (path_start == std::string::npos)
439 {
440 host = rest;
441 this->uri = "/";
442 }
443 else
444 {
445 host = rest.substr(0, path_start);
446 this->uri = rest.substr(path_start);
447 }
448
449 return *this;
450}
451
452HttpRequest &HttpRequest::setHost(const std::string &h)
453{
454 host = h;
455 return *this;
456}
457
459{
460 protocol = p;
461 return *this;
462}
463
464HttpRequest &HttpRequest::setHeaders(const std::map<std::string, std::string> &headers)
465{
466 for (const auto &[k, v] : headers)
467 {
468 this->headers[k] = v;
469 }
470 return *this;
471}
472
473HttpRequest &HttpRequest::setHeader(const std::string &key, const std::string &value)
474{
475 headers[key] = value;
476 return *this;
477}
478
479HttpRequest &HttpRequest::setUserAgent(const std::string &userAgent)
480{
481 headers["User-Agent"] = userAgent;
482 return *this;
483}
484
485HttpRequest &HttpRequest::setAcceptEncoding(const std::string &encoding)
486{
487 headers["Accept-Encoding"] = encoding;
488 return *this;
489}
491{
492 rootCACertificate = certData;
493 return *this;
494}
495
496#if defined(PICO_HTTP_ENABLE_STORAGE)
497#include <fstream>
498HttpRequest &HttpRequest::setBodyFromFile(const std::string &path)
499{
500 std::ifstream file(path, std::ios::in | std::ios::binary);
501 if (file)
502 {
503 body.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
504 }
505 return *this;
506}
507
508bool HttpRequest::setRootCACertificateFromFile(const char *path)
509{
510 std::string contents;
511 if (!StorageManager::instance().readFileToString(path, contents))
512 {
513 return false;
514 }
515 setRootCACertificate(contents);
516 return true;
517}
518#endif
519
520
521HttpRequest& HttpRequest::toFile(const std::string& path) {
522 this->outputFilePath = path;
523 return *this;
524}
525
527 return !outputFilePath.empty();
528}
529
531 return outputFilePath;
532}
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
constexpr size_t MAX_HEADER_BYTES
#define BUFFER_SIZE
HTTP Server class that listens for connections and dispatches requests to a Router.
nlohmann::json json
Parser for handling multipart/form-data file uploads. Part of the PicoFramework HTTP server....
General-purpose TCP socket abstraction with optional TLS support for both client and server use.
static std::map< std::string, std::string > parseHeaders(const std::string &rawHeaders)
Parses the HTTP headers from a raw header string.
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
HttpResponse get()
Send a GETPOST/PUT/DEL request.
const std::map< std::string, std::string > & getHeaders() const
Get all request headers.
HttpRequest & setUserAgent(const std::string &userAgent)
std::string getOutputFilePath() const
void setQueryString(const std::string &query)
std::string protocol
HttpRequest & setUri(const std::string &uri)
Set the URI for the request.
HttpRequest()=default
HttpResponse put()
std::string uri
static bool getMethodAndPath(const std::string &data, std::string &method, std::string &path)
Parse the HTTP method and path from the first request line.
std::string host
std::string outputFilePath
HttpRequest & setRootCACertificate(const std::string &certData)
Set the root CA certificate to use for TLS.
HttpRequest & setHost(const std::string &host)
HttpRequest & setMethod(const std::string &method)
Set the HTTP method (e.g., GET, POST).
bool wantsToFile() const
HttpRequest & setPath(const std::string &path)
Set the request path.
void parseHeaders(const char *raw)
std::string body
HttpRequest & toFile(const std::string &path)
std::string query
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.
std::string method
std::string path
int handle_multipart(HttpResponse &res)
Handle multipart/form-data uploads.
const std::unordered_map< std::string, std::string > getCookies() const
Get all parsed cookies.
static std::optional< std::pair< std::string, std::string > > receiveUntilHeadersComplete(Tcp *conn)
HttpRequest & setProtocol(const std::string &protocol)
std::map< std::string, std::string > headers
nlohmann::json json() const
Safely parse the request body as JSON (non-throwing).
const std::string getCookie(const std::string &name) const
Get a specific cookie value.
int getContentLength() const
Get the Content-Length header as integer.
HttpResponse send()
Send the request and return the response.
size_t headerEnd
HttpRequest & setHeaders(const std::map< std::string, std::string > &headers)
bool isMultipart() const
Check whether the request is multipart/form-data.
std::string rootCACertificate
HttpRequest & setAcceptEncoding(const std::string &encoding)
std::string getHeader(const std::string &field) const
Get a specific header field (case-insensitive).
HttpResponse del()
HttpRequest & setJson(const nlohmann::json &json)
Set the body of the request as JSON.
static HttpRequest create()
void markBodyTruncated()
HttpRequest & setHeader(const std::string &key, const std::string &value)
HttpResponse post()
bool appendRemainingBody(int expectedLength)
HttpRequest & setBody(const std::string &body)
Set the body of the request.
Represents an HTTP response object.
Parses and processes multipart/form-data uploads over HTTP.
bool handleMultipart(HttpRequest &req, HttpResponse &res)
Begin processing the multipart upload from the socket.
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
int recv(char *buffer, size_t size, uint32_t timeout_ms)
Receive data from the connection.
Definition Tcp.cpp:389
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 HTTP_BUFFER_SIZE
Size of the HTTP buffer for request/response data.
#define HTTP_RECEIVE_TIMEOUT
Timeout for receiving HTTP data in milliseconds.
#define MAX_HTTP_BODY_LENGTH
Maximum HTTP body size in bytes.
std::unordered_multimap< std::string, std::string > parseUrlEncoded(const std::string &data)
Definition url_utils.cpp:67
Utility functions for URL decoding, form parsing, MIME type lookup, and IP address extraction.
System utilities for diagnostics, memory, stack usage, and tracing.