6 #ifndef HTTPREQUEST_HPP
7 #define HTTPREQUEST_HPP
18 #include <system_error>
19 #include <type_traits>
22 #include <sys/socket.h>
23 #include <netinet/in.h>
34 explicit RequestError(
const std::string& str): std::logic_error(str) {}
41 explicit ResponseError(
const std::string& str): std::runtime_error(str) {}
50 inline std::string
urlEncode(
const std::string& str)
52 constexpr
char hexChars[16] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F'};
56 for (
auto i = str.begin(); i != str.end(); ++i)
58 const std::uint8_t cp = *i & 0xFF;
60 if ((cp >= 0x30 && cp <= 0x39) ||
61 (cp >= 0x41 && cp <= 0x5A) ||
62 (cp >= 0x61 && cp <= 0x7A) ||
63 cp == 0x2D || cp == 0x2E || cp == 0x5F)
64 result +=
static_cast<char>(cp);
66 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
67 else if ((cp >> 5) == 0x06)
69 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
70 if (++i == str.end())
break;
71 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
73 else if ((cp >> 4) == 0x0E)
75 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
76 if (++i == str.end())
break;
77 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
78 if (++i == str.end())
break;
79 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
81 else if ((cp >> 3) == 0x1E)
83 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
84 if (++i == str.end())
break;
85 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
86 if (++i == str.end())
break;
87 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
88 if (++i == str.end())
break;
89 result += std::string(
"%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
170 std::vector<std::uint8_t>
body;
180 const auto schemeEndPosition = url.find(
"://");
182 if (schemeEndPosition != std::string::npos)
184 scheme = url.substr(0, schemeEndPosition);
185 path = url.substr(schemeEndPosition + 3);
193 const auto fragmentPosition =
path.find(
'#');
196 if (fragmentPosition != std::string::npos)
197 path.resize(fragmentPosition);
199 const auto pathPosition =
path.find(
'/');
201 if (pathPosition == std::string::npos)
212 const auto portPosition =
domain.find(
':');
214 if (portPosition != std::string::npos)
217 domain.resize(portPosition);
224 const std::map<std::string, std::string>& parameters,
225 const std::vector<std::string>& headers = {})
230 for (
const auto& parameter : parameters)
232 if (!
first) body +=
"&";
238 return send(method, body, headers);
242 const std::string& body =
"",
243 const std::vector<std::string>& headers = {})
246 std::vector<uint8_t>(body.begin(), body.end()),
251 const std::vector<uint8_t>& body,
252 const std::vector<std::string>& headers)
259 hints.ai_socktype = SOCK_STREAM;
262 if (getaddrinfo(
domain.c_str(),
port.c_str(), &hints, &info) != 0)
263 throw std::system_error(getLastError(), std::system_category(),
"Failed to get address info of " +
domain);
265 std::unique_ptr<addrinfo, decltype(&freeaddrinfo)> addressInfo(info, freeaddrinfo);
267 std::string headerData = method +
" " +
path +
" HTTP/1.1\r\n";
269 for (
const std::string& header : headers)
270 headerData += header +
"\r\n";
272 headerData +=
"Host: " +
domain +
"\r\n"
276 std::vector<uint8_t> requestData(headerData.begin(), headerData.end());
277 requestData.insert(requestData.end(), body.begin(), body.end());
282 socket.connect(addressInfo->ai_addr,
static_cast<socklen_t
>(addressInfo->ai_addrlen));
284 auto remaining = requestData.size();
285 auto sendData = requestData.data();
288 while (remaining > 0)
290 const auto size = socket.send(sendData, remaining, noSignal);
295 std::uint8_t tempBuffer[4096];
296 constexpr std::uint8_t crlf[] = {
'\r',
'\n'};
298 std::vector<std::uint8_t> responseData;
299 bool firstLine =
true;
300 bool parsedHeaders =
false;
301 bool contentLengthReceived =
false;
302 unsigned long contentLength = 0;
303 bool chunkedResponse =
false;
304 std::size_t expectedChunkSize = 0;
305 bool removeCrlfAfterChunk =
false;
310 const auto size = socket.recv(tempBuffer,
sizeof(tempBuffer), noSignal);
315 responseData.insert(responseData.end(), tempBuffer, tempBuffer + size);
320 const auto i = std::search(responseData.begin(), responseData.end(), std::begin(crlf), std::end(crlf));
323 if (i == responseData.end())
break;
325 const std::string line(responseData.begin(), i);
326 responseData.erase(responseData.begin(), i + 2);
331 parsedHeaders =
true;
338 std::string::size_type lastPos = 0;
339 const auto length = line.length();
340 std::vector<std::string> parts;
343 while (lastPos < length + 1)
345 auto pos = line.find(
' ', lastPos);
346 if (pos == std::string::npos) pos = length;
349 parts.emplace_back(line.data() + lastPos,
350 static_cast<std::vector<std::string>::size_type
>(pos) - lastPos);
355 if (parts.size() >= 2)
356 response.
status = std::stoi(parts[1]);
360 response.
headers.push_back(line);
362 const auto pos = line.find(
':');
364 if (pos != std::string::npos)
366 std::string headerName = line.substr(0, pos);
367 std::string headerValue = line.substr(pos + 1);
370 headerValue.erase(headerValue.begin(),
371 std::find_if(headerValue.begin(), headerValue.end(),
372 [](
int c) {return !std::isspace(c);}));
375 headerValue.erase(std::find_if(headerValue.rbegin(), headerValue.rend(),
376 [](
int c) {return !std::isspace(c);}).base(),
379 if (headerName ==
"Content-Length")
381 contentLength = std::stoul(headerValue);
382 contentLengthReceived =
true;
383 response.
body.reserve(contentLength);
385 else if (headerName ==
"Transfer-Encoding")
387 if (headerValue ==
"chunked")
388 chunkedResponse =
true;
390 throw ResponseError(
"Unsupported transfer encoding: " + headerValue);
401 bool dataReceived =
false;
404 if (expectedChunkSize > 0)
406 const auto toWrite =
std::min(expectedChunkSize, responseData.size());
407 response.
body.insert(response.
body.end(), responseData.begin(), responseData.begin() +
static_cast<ptrdiff_t
>(toWrite));
408 responseData.erase(responseData.begin(), responseData.begin() +
static_cast<ptrdiff_t
>(toWrite));
409 expectedChunkSize -= toWrite;
411 if (expectedChunkSize == 0) removeCrlfAfterChunk =
true;
412 if (responseData.empty())
break;
416 if (removeCrlfAfterChunk)
418 if (responseData.size() >= 2)
420 removeCrlfAfterChunk =
false;
421 responseData.erase(responseData.begin(), responseData.begin() + 2);
426 const auto i = std::search(responseData.begin(), responseData.end(), std::begin(crlf), std::end(crlf));
428 if (i == responseData.end())
break;
430 const std::string line(responseData.begin(), i);
431 responseData.erase(responseData.begin(), i + 2);
433 expectedChunkSize = std::stoul(line,
nullptr, 16);
435 if (expectedChunkSize == 0)
448 response.
body.insert(response.
body.end(), responseData.begin(), responseData.end());
449 responseData.clear();
452 if (contentLengthReceived && response.
body.size() >= contentLength)
RequestError(const std::string &str)
RequestError(const char *str)
Response send(const std::string &method="GET", const std::string &body="", const std::vector< std::string > &headers={})
InternetProtocol internetProtocol
Response send(const std::string &method, const std::vector< uint8_t > &body, const std::vector< std::string > &headers)
Request(const std::string &url, InternetProtocol protocol=InternetProtocol::V4)
Response send(const std::string &method, const std::map< std::string, std::string > ¶meters, const std::vector< std::string > &headers={})
ResponseError(const std::string &str)
ResponseError(const char *str)
std::string urlEncode(const std::string &str)
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
@ HttpVersionNotSupported
@ RequestHeaderFieldsTooLarge
@ ProxyAuthenticationRequired
@ NonAuthoritativeInformation
@ UnavailableForLegalReasons
@ NetworkAuthenticationRequired
std::vector< std::uint8_t > body
std::vector< std::string > headers