From 37a32e2c99bc57beb91a21abffbfd96e69fc67c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Sep 2025 16:58:47 +0200 Subject: [PATCH] Split off Connection from HTTPClient.cpp/h --- CMakeLists.txt | 2 + Common/Common.vcxproj | 2 + Common/Common.vcxproj.filters | 6 + Common/Net/Connection.cpp | 205 ++++++++++++++++++++++++ Common/Net/Connection.h | 41 +++++ Common/Net/HTTPClient.cpp | 191 ---------------------- Common/Net/HTTPClient.h | 34 +--- UWP/CommonUWP/CommonUWP.vcxproj | 2 + UWP/CommonUWP/CommonUWP.vcxproj.filters | 6 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 11 files changed, 267 insertions(+), 224 deletions(-) create mode 100644 Common/Net/Connection.cpp create mode 100644 Common/Net/Connection.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9d0268f6..2ebc3ce615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -804,6 +804,8 @@ add_library(Common STATIC Common/Math/math_util.h Common/Math/Statistics.h Common/Math/Statistics.cpp + Common/Net/Connection.cpp + Common/Net/Connection.h Common/Net/HTTPClient.cpp Common/Net/HTTPClient.h Common/Net/HTTPHeaders.cpp diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 7bea24a69d..c4a706bfaa 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -451,6 +451,7 @@ + @@ -906,6 +907,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 10c2cc8417..1b17924f6a 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -689,6 +689,9 @@ Audio + + Net + @@ -1290,6 +1293,9 @@ ext\imgui + + Net + diff --git a/Common/Net/Connection.cpp b/Common/Net/Connection.cpp new file mode 100644 index 0000000000..b8c587bf5b --- /dev/null +++ b/Common/Net/Connection.cpp @@ -0,0 +1,205 @@ +#include +#include +#include + +#include + +#include "Common/File/Path.h" + +#include "Common/TimeUtil.h" +#include "Common/Log.h" +#include "Common/Net/SocketCompat.h" +#include "Common/Buffer.h" +#include "Common/File/FileDescriptor.h" +#include "Common/SysError.h" +#include "Common/Net/Connection.h" + +namespace net { + +Connection::~Connection() { + Disconnect(); + if (resolved_ != nullptr) + DNSResolveFree(resolved_); +} + +// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go. + +// TODO: Fix for big-endian +inline unsigned short myhtons(unsigned short x) { + return (x >> 8) | (x << 8); +} + +const char *DNSTypeAsString(DNSType type) { + switch (type) { + case DNSType::IPV4: + return "IPV4"; + case DNSType::IPV6: + return "IPV6"; + case DNSType::ANY: + return "ANY"; + default: + return "N/A"; + } +} + +bool Connection::Resolve(const char *host, int port, DNSType type) { + if ((intptr_t)sock_ != -1) { + ERROR_LOG(Log::IO, "Resolve: Already have a socket"); + return false; + } + if (!host || port < 1 || port > 65535) { + ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port); + return false; + } + + host_ = host; + port_ = port; + + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%d", port); + + std::string processedHostname(host); + + if (customResolve_) { + processedHostname = customResolve_(host); + } + + std::string err; + if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) { + WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type)); + // Zero port so that future calls fail. + port_ = 0; + return false; + } + + return true; +} + +static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) { + switch (info->ai_family) { + case AF_INET: + case AF_INET6: + inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize); + break; + default: + snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family); + break; + } +} + +bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { + if (port_ <= 0) { + ERROR_LOG(Log::IO, "Bad port"); + return false; + } + sock_ = -1; + + for (int tries = maxTries; tries > 0; --tries) { + std::vector sockets; + fd_set fds; + int maxfd = 1; + FD_ZERO(&fds); + for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { + if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) + continue; + + int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP); + if ((intptr_t)sock == -1) { + ERROR_LOG(Log::IO, "Bad socket"); + continue; + } + // Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong. +#if !PPSSPP_PLATFORM(WINDOWS) + if (sock >= FD_SETSIZE) { + ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock); + closesocket(sock); + continue; + } +#endif + fd_util::SetNonBlocking(sock, true); + + // Start trying to connect (async with timeout.) + errno = 0; + if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) { + int errorCode = socket_errno; + std::string errorString = GetStringErrorMsg(errorCode); + bool unreachable = errorCode == ENETUNREACH; + bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK; + if (!inProgress) { + char addrStr[128]{}; + FormatAddr(addrStr, sizeof(addrStr), possible); + if (!unreachable) { + ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str()); + } else { + INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr); + } + closesocket(sock); + continue; + } + } + sockets.push_back(sock); + FD_SET(sock, &fds); + if (maxfd < sock + 1) { + maxfd = sock + 1; + } + } + + int selectResult = 0; + long timeoutHalfSeconds = floor(2 * timeout); + while (timeoutHalfSeconds >= 0 && selectResult == 0) { + struct timeval tv {}; + tv.tv_sec = 0; + if (timeoutHalfSeconds > 0) { + // Wait up to 0.5 seconds between cancel checks. + tv.tv_usec = 500000; + } else { + // Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay. + tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0; + } + --timeoutHalfSeconds; + + selectResult = select(maxfd, nullptr, &fds, nullptr, &tv); + if (cancelConnect && *cancelConnect) { + WARN_LOG(Log::HTTP, "connect: cancelled (1): %s:%d", host_.c_str(), port_); + break; + } + } + if (selectResult > 0) { + // Something connected. Pick the first one that did (if multiple.) + for (int sock : sockets) { + if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) { + sock_ = sock; + } else { + closesocket(sock); + } + } + + // Great, now we're good to go. + return true; + } else { + // Fail. Close all the sockets. + for (int sock : sockets) { + closesocket(sock); + } + } + + if (cancelConnect && *cancelConnect) { + WARN_LOG(Log::HTTP, "connect: cancelled (2): %s:%d", host_.c_str(), port_); + break; + } + + sleep_ms(1, "connect"); + } + + // Nothing connected, unfortunately. + return false; +} + +void Connection::Disconnect() { + if ((intptr_t)sock_ != -1) { + closesocket(sock_); + sock_ = -1; + } +} + +} // net diff --git a/Common/Net/Connection.h b/Common/Net/Connection.h new file mode 100644 index 0000000000..abfe9bbb08 --- /dev/null +++ b/Common/Net/Connection.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +#include "Common/Net/Resolve.h" + +namespace net { + +typedef std::function ResolveFunc; + +class Connection { +public: + virtual ~Connection(); + + explicit Connection(ResolveFunc func) : customResolve_(func) {} + + // Inits the sockaddr_in. + bool Resolve(const char *host, int port, DNSType type = DNSType::ANY); + + bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr); + void Disconnect(); + + // Only to be used for bring-up and debugging. + uintptr_t sock() const { return sock_; } + +protected: + // Store the remote host here, so we can send it along through HTTP/1.1 requests. + // TODO: Move to http::client? + std::string host_; + int port_ = -1; + + addrinfo *resolved_ = nullptr; + +private: + uintptr_t sock_ = -1; + ResolveFunc customResolve_; +}; + +} // namespace net diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index e17565f2c0..ea05fb50b7 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -3,7 +3,6 @@ #include #include "Common/Net/HTTPClient.h" - #include "Common/TimeUtil.h" #include "Common/StringUtils.h" #include "Common/System/OSD.h" @@ -19,196 +18,6 @@ #include "Common/Net/NetBuffer.h" #include "Common/Log.h" -namespace net { - -Connection::~Connection() { - Disconnect(); - if (resolved_ != nullptr) - DNSResolveFree(resolved_); -} - -// For whatever crazy reason, htons isn't available on android x86 on the build server. so here we go. - -// TODO: Fix for big-endian -inline unsigned short myhtons(unsigned short x) { - return (x >> 8) | (x << 8); -} - -const char *DNSTypeAsString(DNSType type) { - switch (type) { - case DNSType::IPV4: - return "IPV4"; - case DNSType::IPV6: - return "IPV6"; - case DNSType::ANY: - return "ANY"; - default: - return "N/A"; - } -} - -bool Connection::Resolve(const char *host, int port, DNSType type) { - if ((intptr_t)sock_ != -1) { - ERROR_LOG(Log::IO, "Resolve: Already have a socket"); - return false; - } - if (!host || port < 1 || port > 65535) { - ERROR_LOG(Log::IO, "Resolve: Invalid host or port (%d)", port); - return false; - } - - host_ = host; - port_ = port; - - char port_str[16]; - snprintf(port_str, sizeof(port_str), "%d", port); - - std::string processedHostname(host); - - if (customResolve_) { - processedHostname = customResolve_(host); - } - - std::string err; - if (!net::DNSResolve(processedHostname.c_str(), port_str, &resolved_, err, type)) { - WARN_LOG(Log::IO, "Failed to resolve host '%s': '%s' (%s)", host, err.c_str(), DNSTypeAsString(type)); - // Zero port so that future calls fail. - port_ = 0; - return false; - } - - return true; -} - -static void FormatAddr(char *addrbuf, size_t bufsize, const addrinfo *info) { - switch (info->ai_family) { - case AF_INET: - case AF_INET6: - inet_ntop(info->ai_family, &((sockaddr_in *)info->ai_addr)->sin_addr, addrbuf, bufsize); - break; - default: - snprintf(addrbuf, bufsize, "(Unknown AF %d)", info->ai_family); - break; - } -} - -bool Connection::Connect(int maxTries, double timeout, bool *cancelConnect) { - if (port_ <= 0) { - ERROR_LOG(Log::IO, "Bad port"); - return false; - } - sock_ = -1; - - for (int tries = maxTries; tries > 0; --tries) { - std::vector sockets; - fd_set fds; - int maxfd = 1; - FD_ZERO(&fds); - for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { - if (possible->ai_family != AF_INET && possible->ai_family != AF_INET6) - continue; - - int sock = socket(possible->ai_family, SOCK_STREAM, IPPROTO_TCP); - if ((intptr_t)sock == -1) { - ERROR_LOG(Log::IO, "Bad socket"); - continue; - } - // Windows sockets aren't limited by socket number, just by count, so checking FD_SETSIZE there is wrong. -#if !PPSSPP_PLATFORM(WINDOWS) - if (sock >= FD_SETSIZE) { - ERROR_LOG(Log::IO, "Socket doesn't fit in FD_SET: %d We probably have a leak.", sock); - closesocket(sock); - continue; - } -#endif - fd_util::SetNonBlocking(sock, true); - - // Start trying to connect (async with timeout.) - errno = 0; - if (connect(sock, possible->ai_addr, (int)possible->ai_addrlen) < 0) { - int errorCode = socket_errno; - std::string errorString = GetStringErrorMsg(errorCode); - bool unreachable = errorCode == ENETUNREACH; - bool inProgress = errorCode == EINPROGRESS || errorCode == EWOULDBLOCK; - if (!inProgress) { - char addrStr[128]{}; - FormatAddr(addrStr, sizeof(addrStr), possible); - if (!unreachable) { - ERROR_LOG(Log::HTTP, "connect(%d) call to %s failed (%d: %s)", sock, addrStr, errorCode, errorString.c_str()); - } else { - INFO_LOG(Log::HTTP, "connect(%d): Ignoring unreachable resolved address %s", sock, addrStr); - } - closesocket(sock); - continue; - } - } - sockets.push_back(sock); - FD_SET(sock, &fds); - if (maxfd < sock + 1) { - maxfd = sock + 1; - } - } - - int selectResult = 0; - long timeoutHalfSeconds = floor(2 * timeout); - while (timeoutHalfSeconds >= 0 && selectResult == 0) { - struct timeval tv{}; - tv.tv_sec = 0; - if (timeoutHalfSeconds > 0) { - // Wait up to 0.5 seconds between cancel checks. - tv.tv_usec = 500000; - } else { - // Wait the remaining <= 0.5 seconds. Possibly 0, but that's okay. - tv.tv_usec = (timeout - floor(2 * timeout) / 2) * 1000000.0; - } - --timeoutHalfSeconds; - - selectResult = select(maxfd, nullptr, &fds, nullptr, &tv); - if (cancelConnect && *cancelConnect) { - WARN_LOG(Log::HTTP, "connect: cancelled (1): %s:%d", host_.c_str(), port_); - break; - } - } - if (selectResult > 0) { - // Something connected. Pick the first one that did (if multiple.) - for (int sock : sockets) { - if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) { - sock_ = sock; - } else { - closesocket(sock); - } - } - - // Great, now we're good to go. - return true; - } else { - // Fail. Close all the sockets. - for (int sock : sockets) { - closesocket(sock); - } - } - - if (cancelConnect && *cancelConnect) { - WARN_LOG(Log::HTTP, "connect: cancelled (2): %s:%d", host_.c_str(), port_); - break; - } - - sleep_ms(1, "connect"); - } - - // Nothing connected, unfortunately. - return false; -} - -void Connection::Disconnect() { - if ((intptr_t)sock_ != -1) { - closesocket(sock_); - sock_ = -1; - } -} - -} // net - namespace http { // TODO: do something sane here diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index c8a147db56..397e3d1683 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -11,39 +11,7 @@ #include "Common/Net/Resolve.h" #include "Common/Net/HTTPRequest.h" -namespace net { - -typedef std::function ResolveFunc; - -class Connection { -public: - virtual ~Connection(); - - explicit Connection(ResolveFunc func) : customResolve_(func) {} - - // Inits the sockaddr_in. - bool Resolve(const char *host, int port, DNSType type = DNSType::ANY); - - bool Connect(int maxTries = 2, double timeout = 20.0f, bool *cancelConnect = nullptr); - void Disconnect(); - - // Only to be used for bring-up and debugging. - uintptr_t sock() const { return sock_; } - -protected: - // Store the remote host here, so we can send it along through HTTP/1.1 requests. - // TODO: Move to http::client? - std::string host_; - int port_ = -1; - - addrinfo *resolved_ = nullptr; - -private: - uintptr_t sock_ = -1; - ResolveFunc customResolve_; -}; - -} // namespace net +#include "Common/Net/Connection.h" namespace http { diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 46d2098b88..2d95008f9d 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -99,6 +99,7 @@ + @@ -285,6 +286,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 2da873848c..e2c9ed7cf6 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -520,6 +520,9 @@ ext\imgui + + Net + @@ -999,6 +1002,9 @@ ext\imgui + + Net + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index aa551d1152..48849ec3a6 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -336,6 +336,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/Math/expression_parser.cpp \ $(SRC)/Common/Math/lin/vec3.cpp.arm \ $(SRC)/Common/Math/lin/matrix4x4.cpp.arm \ + $(SRC)/Common/Net/Connection.cpp \ $(SRC)/Common/Net/HTTPClient.cpp \ $(SRC)/Common/Net/HTTPHeaders.cpp \ $(SRC)/Common/Net/HTTPRequest.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 28a0cacb24..68c260b285 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -441,6 +441,7 @@ SOURCES_CXX += \ $(COMMONDIR)/Math/Statistics.cpp \ $(COMMONDIR)/Math/lin/vec3.cpp \ $(COMMONDIR)/Math/lin/matrix4x4.cpp \ + $(COMMONDIR)/Net/Connection.cpp \ $(COMMONDIR)/Net/HTTPClient.cpp \ $(COMMONDIR)/Net/HTTPHeaders.cpp \ $(COMMONDIR)/Net/HTTPServer.cpp \