Skip to content

Commit f755fa3

Browse files
committed
Network/Http
1 parent 1d99515 commit f755fa3

31 files changed

Lines changed: 1782 additions & 717 deletions

modules/Chrono/Chronometer.mpp

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,33 @@ export namespace CppUtils::Chrono
77
{
88
[[nodiscard]] inline auto durationToString(Duration auto duration) -> std::string
99
{
10-
const auto hour = std::chrono::duration_cast<std::chrono::hours>(duration);
11-
duration -= hour;
12-
const auto min = std::chrono::duration_cast<std::chrono::minutes>(duration);
13-
duration -= min;
14-
const auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration);
15-
duration -= sec;
16-
const auto millisec = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
17-
duration -= millisec;
18-
const auto microsec = std::chrono::duration_cast<std::chrono::microseconds>(duration);
19-
duration -= microsec;
20-
const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
10+
const auto hours = std::chrono::duration_cast<std::chrono::hours>(duration);
11+
duration -= hours;
12+
const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(duration);
13+
duration -= minutes;
14+
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
15+
duration -= seconds;
16+
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
17+
duration -= milliseconds;
18+
const auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration);
19+
duration -= microseconds;
20+
const auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
2121

22-
auto str = std::string{};
23-
if (hour.count() > 0)
24-
str += " " + std::to_string(hour.count()) + "h";
25-
if (min.count() > 0)
26-
str += " " + std::to_string(min.count()) + "m";
27-
if (sec.count() > 0)
28-
str += " " + std::to_string(sec.count()) + "s";
29-
if (millisec.count() > 0)
30-
str += " " + std::to_string(millisec.count()) + "ms";
31-
if (microsec.count() > 0)
32-
str += " " + std::to_string(microsec.count()) + "us";
33-
if (ns.count() > 0)
34-
str += " " + std::to_string(ns.count()) + "ns";
35-
return not std::empty(str) ? str.substr(1) : "0ns";
22+
auto string = std::string{};
23+
auto it = std::back_inserter(string);
24+
if (hours.count() > 0)
25+
std::format_to(it, " {}h", hours.count());
26+
if (minutes.count() > 0)
27+
std::format_to(it, " {}m", minutes.count());
28+
if (seconds.count() > 0)
29+
std::format_to(it, " {}s", seconds.count());
30+
if (milliseconds.count() > 0)
31+
std::format_to(it, " {}ms", milliseconds.count());
32+
if (microseconds.count() > 0)
33+
std::format_to(it, " {}us", microseconds.count());
34+
if (nanoseconds.count() > 0)
35+
std::format_to(it, " {}ns", nanoseconds.count());
36+
return not std::empty(string) ? string.substr(1) : "0ns";
3637
}
3738

3839
template<Clock Clock = std::chrono::high_resolution_clock>

modules/Chrono/Concept.mpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export namespace CppUtils::Chrono
1212
template<class T>
1313
concept Duration = Type::Specializes<std::remove_cvref_t<T>, std::chrono::duration>;
1414

15+
template<class T>
16+
concept TimePoint = Type::Specializes<std::remove_cvref_t<T>, std::chrono::time_point>;
17+
1518
template<class T>
1619
concept Clock = Stl::chrono::is_clock_v<T>;
1720
}

modules/Execution/Event.mpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
11
export module CppUtils.Execution.Event;
22

33
import std;
4+
import CppUtils.Chrono.Concept;
45

56
export namespace CppUtils::Execution
67
{
78
class Event final
89
{
910
public:
10-
void wait()
11+
inline auto wait() -> void
1112
{
1213
auto lock = std::unique_lock{m_mutex};
13-
m_conditionVariable.wait(lock, [this]() { return m_triggered; });
14-
m_triggered = false;
14+
m_conditionVariable.wait(lock, [this] { return m_triggered; });
15+
}
16+
17+
template<Chrono::Duration DurationType>
18+
[[nodiscard]] inline auto waitFor(const DurationType& duration) -> bool
19+
{
20+
auto lock = std::unique_lock{m_mutex};
21+
return m_conditionVariable.wait_for(lock, duration, [this] { return m_triggered; });
1522
}
1623

17-
void notify()
24+
template<Chrono::TimePoint TimePointType>
25+
[[nodiscard]] inline auto waitUntil(const TimePointType& timePoint) -> bool
26+
{
27+
auto lock = std::unique_lock{m_mutex};
28+
return m_conditionVariable.wait_until(lock, timePoint, [this] { return m_triggered; });
29+
}
30+
31+
inline auto notify() -> void
1832
{
1933
{
20-
auto lock = std::unique_lock{m_mutex};
34+
auto lock = std::lock_guard{m_mutex};
2135
m_triggered = true;
2236
}
2337
m_conditionVariable.notify_all();
2438
}
2539

26-
void reset()
40+
inline auto reset() -> void
2741
{
28-
auto lock = std::unique_lock{m_mutex};
42+
auto lock = std::lock_guard{m_mutex};
2943
m_triggered = false;
3044
}
3145

modules/Math/Binary.mpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ export namespace CppUtils::Math
2424
const auto shift = [&]() constexpr noexcept -> std::size_t {
2525
switch (alignment)
2626
{
27-
case HorizontalAlignment::Left: return totalBits - length;
28-
case HorizontalAlignment::Center: return (totalBits - length + 1) / 2;
29-
case HorizontalAlignment::Right: return 0;
27+
using enum HorizontalAlignment;
28+
case Left: return totalBits - length;
29+
case Center: return (totalBits - length + 1) / 2;
30+
case Right: return 0;
3031
default: std::unreachable();
3132
}
3233
}();

modules/Network/Client.mpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module;
1212
export module CppUtils.Network.Client;
1313

1414
import std;
15-
import CppUtils.Network.Socket;
15+
export import CppUtils.Network.Socket;
1616

1717
export namespace CppUtils::Network
1818
{
@@ -23,26 +23,29 @@ export namespace CppUtils::Network
2323
Socket{domain, type}
2424
{}
2525

26-
inline auto connect(std::string_view ip, std::uint16_t port) const -> void
26+
[[nodiscard]] inline auto connect(std::string_view ip, std::uint16_t port) -> std::expected<void, std::error_code>
2727
{
28+
if (const auto detectedDomain = ip.find(':') != std::string_view::npos ? Domain::IPV6 : Domain::IPV4;
29+
detectedDomain != getDomain())
30+
*this = Client{detectedDomain, getType()};
31+
2832
auto address = makeAddress(std::empty(ip) ? "127.0.0.1" : ip, port);
2933
if (address.length == 0)
30-
throw std::runtime_error{"CppUtils::Network::Client::connect: Invalid address length"};
31-
#if defined(OS_WINDOWS)
32-
if (nativeHandle() == INVALID_SOCKET)
33-
#else
34-
if (nativeHandle() == -1)
35-
#endif
36-
throw std::runtime_error{"CppUtils::Network::Client::connect: Invalid socket handle"};
34+
return std::unexpected{std::make_error_code(std::errc::invalid_argument)};
35+
36+
if (not isValid())
37+
return std::unexpected{std::make_error_code(std::errc::bad_file_descriptor)};
38+
3739
if (::connect(nativeHandle(), reinterpret_cast<sockaddr*>(std::addressof(address.storage)), address.length) < 0)
3840
{
3941
#if defined(OS_WINDOWS)
4042
if (WSAGetLastError() != WSAEWOULDBLOCK)
4143
#elif defined(OS_LINUX) or defined(OS_MACOS)
4244
if (errno != EINPROGRESS)
4345
#endif
44-
throwErrno(-1, "CppUtils::Network::Client::connect");
46+
return std::unexpected{std::error_code(errno, std::generic_category())};
4547
}
48+
return {};
4649
}
4750
};
4851
}

modules/Network/DNS.mpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module;
2+
3+
#include <CppUtils/System/OS.hpp>
4+
5+
#if defined(OS_WINDOWS)
6+
# include <winsock2.h>
7+
# include <ws2tcpip.h>
8+
#elif defined(OS_LINUX) or defined(OS_MACOS)
9+
# include <netdb.h>
10+
# include <unistd.h>
11+
# include <arpa/inet.h>
12+
#endif
13+
14+
export module CppUtils.Network.DNS;
15+
16+
import std;
17+
import CppUtils.Memory;
18+
import CppUtils.Network.Socket;
19+
20+
export namespace CppUtils::Network::DNS
21+
{
22+
constexpr auto MaxHostNameLength = 256uz;
23+
24+
[[nodiscard]] inline auto getHostName() -> std::string
25+
{
26+
#if defined(OS_WINDOWS)
27+
Socket::WindowsSocketApi::ensureIsinitialized();
28+
#endif
29+
auto hostname = std::string(MaxHostNameLength, '\0');
30+
if (::gethostname(std::data(hostname), std::size(hostname)) != 0)
31+
return "unknown";
32+
33+
if (const auto position = hostname.find('\0'); position != std::string::npos)
34+
hostname.resize(position);
35+
return hostname;
36+
}
37+
38+
[[nodiscard]] inline auto resolve(std::string_view hostname) -> std::vector<std::string>
39+
{
40+
#if defined(OS_WINDOWS)
41+
Socket::WindowsSocketApi::ensureIsinitialized();
42+
#endif
43+
auto hints = addrinfo{};
44+
hints.ai_family = AF_UNSPEC; // IPv4 or IPv6
45+
hints.ai_socktype = SOCK_STREAM;
46+
hints.ai_protocol = IPPROTO_TCP;
47+
48+
auto* resultPointer = static_cast<addrinfo*>(nullptr);
49+
if (::getaddrinfo(std::data(hostname), nullptr, std::addressof(hints), std::addressof(resultPointer)) != 0)
50+
return {};
51+
52+
auto result = Memory::UniquePtrWithDestructor<addrinfo>{resultPointer, ::freeaddrinfo};
53+
auto ips = std::vector<std::string>{};
54+
auto ipString = std::string(INET6_ADDRSTRLEN, '\0');
55+
for (auto* entry = result.get(); entry; entry = entry->ai_next)
56+
{
57+
const void* address = nullptr;
58+
if (entry->ai_family == AF_INET)
59+
address = std::addressof(reinterpret_cast<sockaddr_in*>(entry->ai_addr)->sin_addr);
60+
else
61+
address = std::addressof(reinterpret_cast<sockaddr_in6*>(entry->ai_addr)->sin6_addr);
62+
63+
::inet_ntop(entry->ai_family, address, std::data(ipString), static_cast<socklen_t>(std::size(ipString)));
64+
ips.emplace_back(std::data(ipString));
65+
}
66+
67+
return ips;
68+
}
69+
}

modules/Network/Http.mpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export module CppUtils.Network.Http;
2+
3+
export import CppUtils.Network.Http.Common;
4+
export import CppUtils.Network.Http.Client;
5+
export import CppUtils.Network.Http.Server;

modules/Network/Http/Client.mpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
export module CppUtils.Network.Http.Client;
2+
3+
import std;
4+
import CppUtils.Network.Socket;
5+
import CppUtils.Network.Client;
6+
import CppUtils.Network.DNS;
7+
import CppUtils.Network.Http.Common;
8+
9+
export namespace CppUtils::Network::Http
10+
{
11+
class Client final
12+
{
13+
public:
14+
inline auto setHostname(std::string hostname) -> void
15+
{
16+
m_hostname = std::move(hostname);
17+
}
18+
19+
[[nodiscard]] inline auto connect(std::string_view hostname, std::uint16_t port = 80) -> std::expected<void, std::error_code>
20+
{
21+
setHostname(std::string{hostname});
22+
m_port = port;
23+
const auto ipAddresses = DNS::resolve(hostname);
24+
if (std::empty(ipAddresses))
25+
return std::unexpected{std::make_error_code(std::errc::host_unreachable)};
26+
return connect(ipAddresses, port);
27+
}
28+
29+
[[nodiscard]] inline auto connect(const std::vector<std::string>& ipAddresses, std::uint16_t port) -> std::expected<void, std::error_code>
30+
{
31+
auto lastError = std::error_code{};
32+
for (const auto& ip : ipAddresses)
33+
if (auto result = connectIP(ip, port); result.has_value())
34+
return {};
35+
else
36+
lastError = result.error();
37+
return std::unexpected{lastError};
38+
}
39+
40+
[[nodiscard]] inline auto connectIP(std::string_view ip, std::uint16_t port) -> std::expected<void, std::error_code>
41+
{
42+
const auto domain = ip.find(':') != std::string_view::npos ? Socket::Domain::IPV6 : Socket::Domain::IPV4;
43+
m_connection.emplace(domain, Socket::Type::TCP);
44+
if (auto result = m_connection->connect(ip, port); not result)
45+
{
46+
m_connection.reset();
47+
return std::unexpected{result.error()};
48+
}
49+
return {};
50+
}
51+
52+
[[nodiscard]] inline auto send(Request request) -> std::expected<Response, std::error_code>
53+
{
54+
if (not m_connection.has_value())
55+
if (auto result = connect(m_hostname, m_port); not result)
56+
return std::unexpected{result.error()};
57+
58+
if (std::ranges::none_of(request.headers, [](const auto& header) { return header.name == "Host"; }))
59+
request.headers.push_back(Header{"Host", m_hostname});
60+
61+
if (not m_connection.value().send(static_cast<std::string>(request)))
62+
{
63+
if (auto reconnectResult = connect(m_hostname, m_port); not reconnectResult)
64+
return std::unexpected{reconnectResult.error()};
65+
66+
if (auto retryResult = m_connection.value().send(static_cast<std::string>(request)); not retryResult)
67+
return std::unexpected{retryResult.error()};
68+
}
69+
return readResponse();
70+
}
71+
72+
[[nodiscard]] inline auto get(std::string_view path) -> std::expected<Response, std::error_code>
73+
{
74+
auto request = Request{};
75+
request.method = Method::Get;
76+
request.path = std::string{path};
77+
return send(std::move(request));
78+
}
79+
80+
private:
81+
[[nodiscard]] inline auto readResponse() -> std::expected<Response, std::error_code>
82+
{
83+
return readMessage<Response>(
84+
[this](std::size_t size) { return m_connection.value().receive<std::string>(size); },
85+
[this](std::string_view buffer) { return parseHeaders(buffer); });
86+
}
87+
88+
[[nodiscard]] inline auto parseHeaders(std::string_view serializedHeaders) -> Response
89+
{
90+
return parseMessage(serializedHeaders, [](std::string_view statusLine) {
91+
auto response = Response{};
92+
if (auto firstSpace = statusLine.find(' '); firstSpace != std::string_view::npos)
93+
{
94+
if (auto secondSpace = statusLine.find(' ', firstSpace + 1); secondSpace != std::string_view::npos)
95+
{
96+
auto codeString = statusLine.substr(firstSpace + 1, secondSpace - firstSpace - 1);
97+
auto code = 0uz;
98+
std::from_chars(std::data(codeString), std::data(codeString) + std::size(codeString), code);
99+
response.status = static_cast<Status>(code);
100+
}
101+
}
102+
return response;
103+
});
104+
}
105+
106+
std::string m_hostname;
107+
std::uint16_t m_port = 80;
108+
std::optional<CppUtils::Network::Client> m_connection;
109+
};
110+
}

0 commit comments

Comments
 (0)