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
146void HttpRequest::parseHeaders(const char *raw)
147{
149}
150
160bool HttpRequest::getMethodAndPath(const std::string& rawHeaders, std::string& method, std::string& path)
161{
162 TRACE("Parsing request data: %s\n", rawHeaders.c_str());
163 std::istringstream stream(rawHeaders);
164 std::string requestLine;
165 if (!std::getline(stream, requestLine)) {
166 printf("Failed to read request line\n");
167 return false;
168 }
169
170 std::istringstream lineStream(requestLine);
171 if (!(lineStream >> method >> path)) {
172 printf("Error parsing HTTP request method and path\n");
173 return false;
174 }
175 return true;
176}
177
178constexpr size_t MAX_HEADER_BYTES = 4096; // limit to prevent runaway headers if malformed
179
180std::optional<std::pair<std::string, std::string>> HttpRequest::receiveUntilHeadersComplete(Tcp* conn) {
181 std::string requestText;
182 char buffer[HTTP_BUFFER_SIZE];
183
184 while (true) {
185 int received = conn->recv(buffer, sizeof(buffer), HTTP_RECEIVE_TIMEOUT);
186 if (received <= 0) {
187 printf("[HttpRequest] Failed to receive header - usually Safari reusing socket it shouldn't\n");
188 return std::nullopt;
189 }
190
191 requestText.append(buffer, received);
192
193 size_t headerEnd = requestText.find("\r\n\r\n");
194 if (headerEnd != std::string::npos) {
195 size_t bodyStart = headerEnd + 4;
196 std::string headers = requestText.substr(0, headerEnd);
197 std::string leftover = (bodyStart < requestText.size())
198 ? requestText.substr(bodyStart)
199 : "";
200 return std::make_pair(std::move(headers), std::move(leftover));
201 }
202
203 if (requestText.size() > MAX_HEADER_BYTES) {
204 printf("[HttpRequest] Headers exceeded %zu bytes, rejecting\n", MAX_HEADER_BYTES);
205 return std::nullopt;
206 }
207 }
208}
209
210bool HttpRequest::appendRemainingBody(int expectedLength) {
211 size_t remaining = expectedLength - body.size();
212 char buffer[HTTP_BUFFER_SIZE];
213
214 while (remaining > 0) {
215 size_t toRead = std::min(remaining, sizeof(buffer));
216 int received = tcp->recv(buffer, toRead, HTTP_RECEIVE_TIMEOUT);
217 if (received <= 0) {
218 printf("Error receiving body chunk\n");
219 return false;
220 }
221 size_t currentSize = body.size();
222 if (currentSize >= MAX_HTTP_BODY_LENGTH) {
223 TRACE("Body exceeds max length. Truncating.\n");
225 break;
226 }
227 size_t allowed = MAX_HTTP_BODY_LENGTH - currentSize;
228 size_t toAppend = std::min(static_cast<size_t>(received), allowed);
229 body.append(buffer, toAppend);
230
231 if (toAppend < static_cast<size_t>(received)) {
232 TRACE("Body chunk truncated due to size limit.\n");
234 break;
235 }
236 remaining -= received;
237 }
238 return true;
239}
240
241
242
253{
254 TRACE("Receiving request on socket %d\n", tcp->getSocketFd());
255
256 char buffer[BUFFER_SIZE]; // Declare buffer size
257 std::string body = ""; // Initialize empty body
258 std::map<std::string, std::string> headers; // Initialize empty headers
259 std::string method = {0}; // Initialize method buffer
260 std::string path = {0}; // Initialize path buffer
261
262 auto result = receiveUntilHeadersComplete(tcp);
263 if (!result) {
264 return HttpRequest("", "", "");
265 }
266
267 const auto& [rawHeaders, initialBody] = *result;
268
269 if (!getMethodAndPath(rawHeaders, method, path)) {
270 return HttpRequest("", "", "");
271 }
272
273 // Create the request which will parse the headers
274 TRACE("Raw headers: %s\n", rawHeaders.c_str());
275
276 HttpRequest request(tcp, rawHeaders, std::string(method), std::string(path));
277 request.setBody(initialBody); // Set the initial body if any
278
279 // NOW split path and query
280 std::string cleanPath = path;
281 std::string queryString = "";
282
283 size_t qpos = cleanPath.find('?');
284 if (qpos != std::string::npos)
285 {
286 queryString = cleanPath.substr(qpos + 1);
287 cleanPath = cleanPath.substr(0, qpos);
288 }
289 request.setPath(cleanPath); // Set the cleaned path as the URI
290 request.setQueryString(queryString); // Set the query string
291
292 // Get headers from the HttpRequest object
293 headers = request.getHeaders();
294
295 TRACE("Parsed headers:\n");
296 for (const auto &header : headers)
297 {
298 TRACE("%s: %s\n", header.first.c_str(), header.second.c_str());
299 }
300
301 int contentLength = request.getContentLength();
302 TRACE("Content-Length: %d\n", contentLength);
303
304 if (contentLength > 0)
305 {
306 TRACE("Content-Length is greater than 0\n");
307 if (request.isMultipart())
308 {
309 TRACE("Multipart request detected\n");
310 return request;
311 }
312
313 TRACE("Non-multipart request detected\n");
314
315 request.appendRemainingBody(contentLength);
316 TRACE("Final body length: %zu\n", body.length());
317 TRACE("HttpRequest object constructed\n");
318 }
319 return request;
320}
321
330{
331 MultipartParser parser;
332 return parser.handleMultipart(*this, res) ? 0 : -1;
333}
334
340const std::unordered_map<std::string, std::string> HttpRequest::getCookies() const
341{
342 std::unordered_map<std::string, std::string> cookies;
343 std::string cookieHeader = getHeader("cookie");
344 std::istringstream stream(cookieHeader);
345 std::string pair;
346 while (std::getline(stream, pair, ';'))
347 {
348 size_t eq = pair.find('=');
349 if (eq != std::string::npos)
350 {
351 std::string key = pair.substr(0, eq);
352 std::string value = pair.substr(eq + 1);
353 key.erase(0, key.find_first_not_of(" \t"));
354 value.erase(0, value.find_first_not_of(" \t"));
355 cookies[key] = value;
356 }
357 }
358 return cookies;
359}
360
367const std::string HttpRequest::getCookie(const std::string &name) const
368{
369 auto cookies = getCookies();
370 auto it = cookies.find(name);
371 return (it != cookies.end()) ? it->second : "";
372}
373
379const std::unordered_multimap<std::string, std::string> HttpRequest::getQueryParams()
380{
381 return parseUrlEncoded(query);
382}
383
389const std::unordered_multimap<std::string, std::string> HttpRequest::getFormParams()
390{
391 return parseUrlEncoded(body);
392}
393
394// ─────────────────────────────────────────────────────────────────────────────
395// Fluent Builder Methods - some shared server-side
396// ─────────────────────────────────────────────────────────────────────────────
397
399{
400 return HttpRequest("", "", "");
401}
402
409HttpRequest &HttpRequest::setUri(const std::string &uri)
410{
411 const auto proto_end = uri.find("://");
412 if (proto_end == std::string::npos)
413 {
414 // Relative URI — just set it, do not overwrite protocol or host
415 this->uri = uri;
416 return *this;
417 }
418
419 // Full URL — parse into protocol, host, and path
420 protocol = uri.substr(0, proto_end);
421 std::string rest = uri.substr(proto_end + 3); // skip "://"
422
423 const auto path_start = rest.find('/');
424 if (path_start == std::string::npos)
425 {
426 host = rest;
427 this->uri = "/";
428 }
429 else
430 {
431 host = rest.substr(0, path_start);
432 this->uri = rest.substr(path_start);
433 }
434
435 return *this;
436}
437
438HttpRequest &HttpRequest::setHost(const std::string &h)
439{
440 host = h;
441 return *this;
442}
443
445{
446 protocol = p;
447 return *this;
448}
449
450HttpRequest &HttpRequest::setHeaders(const std::map<std::string, std::string> &headers)
451{
452 for (const auto &[k, v] : headers)
453 {
454 this->headers[k] = v;
455 }
456 return *this;
457}
458
459HttpRequest &HttpRequest::setHeader(const std::string &key, const std::string &value)
460{
461 headers[key] = value;
462 return *this;
463}
464
465HttpRequest &HttpRequest::setUserAgent(const std::string &userAgent)
466{
467 headers["User-Agent"] = userAgent;
468 return *this;
469}
470
471HttpRequest &HttpRequest::setAcceptEncoding(const std::string &encoding)
472{
473 headers["Accept-Encoding"] = encoding;
474 return *this;
475}
477{
478 rootCACertificate = certData;
479 return *this;
480}
481
482#if defined(PICO_HTTP_ENABLE_STORAGE)
483#include <fstream>
484HttpRequest &HttpRequest::setBodyFromFile(const std::string &path)
485{
486 std::ifstream file(path, std::ios::in | std::ios::binary);
487 if (file)
488 {
489 body.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
490 }
491 return *this;
492}
493
494bool HttpRequest::setRootCACertificateFromFile(const char *path)
495{
496 std::string contents;
497 if (!StorageManager::instance().readFileToString(path, contents))
498 {
499 return false;
500 }
501 setRootCACertificate(contents);
502 return true;
503}
504#endif
505
506
507HttpRequest& HttpRequest::toFile(const std::string& path) {
508 this->outputFilePath = path;
509 return *this;
510}
511
513 return !outputFilePath.empty();
514}
515
517 return outputFilePath;
518}
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.
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
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()
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.