Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
HttpClient.cpp
Go to the documentation of this file.
1#include "http/HttpClient.h"
2#include "http/HttpRequest.h"
3#include "http/HttpResponse.h"
4#include "http/HttpParser.h"
6#include "network/Tcp.h"
7
8#include <sstream>
9#include <cstring>
10
11#include "framework_config.h"
12#include "DebugTrace.h"
13TRACE_INIT(HttpClient)
14
15bool HttpClient::sendRequest(const HttpRequest &request, HttpResponse &response)
16{
17
18 response.reset(); // Clear any prior response data
19
20 const std::string &protocol = request.getProtocol();
21 const std::string &host = request.getHost();
22 const std::string &path = request.getUri();
23 const std::string &method = request.getMethod();
24 const std::string &body = request.getBody();
25 const auto &headers = request.getHeaders();
26 const std::string &cert = request.getRootCACertificate();
27
28 const bool useTls = (protocol == "https");
29 const uint16_t port = useTls ? 443 : 80;
30
31 Tcp socket;
32
33 TRACE("Connecting to %s:%d\n", host.c_str(), port);
34 TRACE("Using TLS: %s\n", useTls ? "true" : "false");
35 TRACE("Root CA: %s\n", cert.empty() ? "none" : "set");
36 TRACE("Method: %s\n", method.c_str());
37 TRACE("Path: %s\n", path.c_str());
38 TRACE("Body: %s\n", body.c_str());
39 TRACE("Headers:\n");
40 for (const auto &[key, value] : headers)
41 {
42 TRACE(" %s: %s\n", key.c_str(), value.c_str());
43 }
44
45 if (useTls)
46 {
47 if (!cert.empty())
48 {
49 socket.setRootCACertificate(cert);
50 }
51 socket.setHostname(host.c_str()); // required for SNI
52 }
53
54 if (!socket.connect(host.c_str(), port, useTls))
55 {
56 return false;
57 }
58
59 std::ostringstream req;
60 req << method << " " << path << " HTTP/1.1\r\n";
61 req << "Host: " << host << "\r\n";
62
63 for (const auto &[key, value] : headers)
64 {
65 req << key << ": " << value << "\r\n";
66 }
67
68 if (!body.empty())
69 {
70 req << "Content-Length: " << body.length() << "\r\n";
71 }
72
73 req << "\r\n";
74 if (!body.empty())
75 {
76 req << body;
77 }
78
79 const std::string requestStr = req.str();
80 if (!socket.send(requestStr.c_str(), requestStr.length()))
81 {
82 return false;
83 }
84
85 auto [rawHeader, leftover] = HttpParser::receiveHeaderAndLeftover(socket);
86 if (rawHeader.empty())
87 {
88 return false;
89 }
90
91 TRACE("Raw header: %s\n", rawHeader.c_str());
92 TRACE("Leftover: %s\n", leftover.c_str());
93
94 response.setStatus(HttpParser::parseStatusCode(rawHeader));
95 const auto parsedHeaders = HttpParser::parseHeaders(rawHeader);
96 for (const auto &[key, value] : parsedHeaders)
97 {
98 response.setHeader(key, value);
99 }
100
101 bool truncated = false;
102
103 if (request.wantsToFile()) {
105 const std::string& path = request.getOutputFilePath();
106
107 bool ok = false;
108 if (HttpParser::isChunkedEncoding(parsedHeaders)) {
109 ok = HttpParser::receiveChunkedBodyToFile(socket, leftover,
110 [&](const char* data, size_t len) {
111 return storage->appendToFile(path, reinterpret_cast<const uint8_t*>(data), len);
112 },
114 &truncated);
115 } else {
116 ok = HttpParser::receiveFixedLengthBodyToFile(socket, parsedHeaders, leftover,
117 [&](const char* data, size_t len) {
118 return storage->appendToFile(path, reinterpret_cast<const uint8_t*>(data), len);
119 },
121 &truncated);
122 }
123
124 if (!ok)
125 return false;
126
127 if (truncated)
128 response.markBodyTruncated();
129
130 response.setHeader("X-Body-Saved", path);
131 }
132
133 else
134 {
135 std::string bodyData;
136 if (!HttpParser::receiveBody(socket, parsedHeaders, leftover, bodyData, MAX_HTTP_BODY_LENGTH, &truncated))
137 {
138 return false;
139 }
140 response.setBody(bodyData);
141 if (truncated)
142 response.markBodyTruncated();
143 }
144
145 return true;
146}
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
Defines the HttpRequest class for handling HTTP requests: headers, method, path, query string,...
HTTP HttpResponse class for managing status, headers, body, and streaming support.
General-purpose TCP socket abstraction with optional TLS support for both client and server use.
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
static std::pair< std::string, std::string > receiveHeaderAndLeftover(Tcp &socket)
Receive raw headers from the socket until \r \r .
static bool receiveFixedLengthBodyToFile(Tcp &socket, const std::map< std::string, std::string > &headers, const std::string &leftover, std::function< bool(const char *, size_t)> writeFn, size_t maxLength, bool *wasTruncated)
static bool isChunkedEncoding(const std::map< std::string, std::string > &headers)
static int parseStatusCode(const std::string &statusLine)
Parses the HTTP status line to extract the status code.
static bool receiveBody(Tcp &socket, const std::map< std::string, std::string > &headers, const std::string &leftoverBody, std::string &outBody, size_t maxLength, bool *wasTruncated)
Receive the HTTP body from socket, using Content-Length or connection close.
static std::map< std::string, std::string > parseHeaders(const std::string &rawHeaders)
Parses the HTTP headers from a raw header string.
static bool receiveChunkedBodyToFile(Tcp &socket, const std::string &leftover, std::function< bool(const char *, size_t)> writeFn, size_t maxLength, bool *wasTruncated)
Forward declaration for potential routing needs.
Definition HttpRequest.h:32
const std::string & getBody() const
Get the request body (copy).
const std::map< std::string, std::string > & getHeaders() const
Get all request headers.
std::string getOutputFilePath() const
const std::string & getMethod() const
Get the HTTP method.
bool wantsToFile() const
const std::string & getRootCACertificate() const
Get the root CA certificate string, if set.
const std::string & getHost() const
Get the Host header value.
const std::string & getUri() const
Get the original URL from the request line.
const std::string & getProtocol() const
Represents an HTTP response object.
HttpResponse & setStatus(int code)
Alias for status().
void markBodyTruncated()
Mark the response body as truncated.
HttpResponse & setBody(const std::string &body)
Set the body of the response (string).
void reset()
Clear the response status, headers, and body.
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 appendToFile(const std::string &path, const uint8_t *data, size_t size)=0
Append data to a file.
General-purpose TCP socket wrapper with optional TLS support via mbedTLS (altcp).
Definition Tcp.h:39
bool connect(const char *host, int port, bool useTls=false)
Connect to a remote host.
Definition Tcp.cpp:116
void setRootCACertificate(const std::string &pem)
Set the Root CA certificate to be used for client TLS connections (PEM format).
void setHostname(const char *name)
Definition Tcp.h:126
int send(const char *buffer, size_t size)
Send data over the connection.
Definition Tcp.cpp:279
Delegates to user or system configuration.
#define MAX_HTTP_BODY_LENGTH
Maximum HTTP body size in bytes.