This commit is contained in:
2025-12-03 01:08:36 +05:30
parent bd9d75e2a2
commit 4af30facaf
5 changed files with 166 additions and 19 deletions

View File

@ -65,6 +65,6 @@ endif()
# ------------------------------------------------- # -------------------------------------------------
# Local Development Sandboxes (not included in source control) # Local Development Sandboxes (not included in source control)
# ------------------------------------------------- # -------------------------------------------------
if (EXISTS ".local") if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.local")
add_subdirectory(.local) add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/.local")
endif() endif()

View File

@ -21,6 +21,52 @@
namespace IACore namespace IACore
{ {
// FNV-1a 32-bit Constants
constexpr uint32_t FNV1A_32_PRIME = 0x01000193; // 16777619
constexpr uint32_t FNV1A_32_OFFSET = 0x811c9dc5; // 2166136261
UINT32 DataOps::Hash(IN CONST String &string)
{
uint32_t hash = FNV1A_32_OFFSET;
for (char c : string)
{
hash ^= static_cast<uint8_t>(c);
hash *= FNV1A_32_PRIME;
}
return hash;
}
UINT32 DataOps::Hash(IN Span<CONST UINT8> data)
{
uint32_t hash = FNV1A_32_OFFSET;
const uint8_t *ptr = static_cast<const uint8_t *>(data.data());
for (size_t i = 0; i < data.size(); ++i)
{
hash ^= ptr[i];
hash *= FNV1A_32_PRIME;
}
return hash;
}
DataOps::CompressionType DataOps::DetectCompression(IN Span<CONST UINT8> data)
{
if (data.size() < 2)
return CompressionType::None;
// Check for GZIP Magic Number (0x1F 0x8B)
if (data[0] == 0x1F && data[1] == 0x8B)
return CompressionType::Gzip;
// Check for ZLIB Magic Number (starts with 0x78)
// 0x78 = Deflate compression with 32k window size
// Valid second bytes: 0x01 (Fastest), 0x9C (Default), 0xDA (Best)
if (data[0] == 0x78 && (data[1] == 0x01 || data[1] == 0x9C || data[1] == 0xDA))
return CompressionType::Zlib;
return CompressionType::None;
}
Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data) Expected<Vector<UINT8>, String> DataOps::ZlibInflate(IN Span<CONST UINT8> data)
{ {
z_stream zs{}; z_stream zs{};
@ -28,6 +74,7 @@ namespace IACore
zs.zfree = Z_NULL; zs.zfree = Z_NULL;
zs.opaque = Z_NULL; zs.opaque = Z_NULL;
// 15 + 32 = Auto-detect Gzip or Zlib
if (inflateInit2(&zs, 15 + 32) != Z_OK) if (inflateInit2(&zs, 15 + 32) != Z_OK)
return MakeUnexpected("Failed to initialize zlib inflate"); return MakeUnexpected("Failed to initialize zlib inflate");
@ -35,18 +82,28 @@ namespace IACore
zs.avail_in = static_cast<uInt>(data.size()); zs.avail_in = static_cast<uInt>(data.size());
Vector<UINT8> outBuffer; Vector<UINT8> outBuffer;
outBuffer.resize(data.size() * 2); // Start with 2x input size.
// Small packets compress well, so maybe 4x for very small inputs.
size_t guessSize = data.size() < 1024 ? data.size() * 4 : data.size() * 2;
outBuffer.resize(guessSize);
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
int ret; int ret;
do do
{ {
if (zs.total_out >= outBuffer.size()) if (zs.avail_out == 0)
{ {
outBuffer.resize(outBuffer.size() * 2); size_t currentPos = zs.total_out;
}
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + zs.total_out); size_t newSize = outBuffer.size() * 2;
zs.avail_out = static_cast<uInt>(outBuffer.size() - zs.total_out); outBuffer.resize(newSize);
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data() + currentPos);
zs.avail_out = static_cast<uInt>(newSize - currentPos);
}
ret = inflate(&zs, Z_NO_FLUSH); ret = inflate(&zs, Z_NO_FLUSH);
@ -55,9 +112,10 @@ namespace IACore
inflateEnd(&zs); inflateEnd(&zs);
if (ret != Z_STREAM_END) if (ret != Z_STREAM_END)
return MakeUnexpected("Failed to inflate, corrupt or incomplete data"); return MakeUnexpected("Failed to inflate: corrupt data or stream error");
outBuffer.resize(zs.total_out); outBuffer.resize(zs.total_out);
return outBuffer; return outBuffer;
} }
@ -165,4 +223,46 @@ namespace IACore
outBuffer.resize(compressedSize); outBuffer.resize(compressedSize);
return outBuffer; return outBuffer;
} }
Expected<Vector<UINT8>, String> DataOps::GZipDeflate(IN Span<CONST UINT8> data)
{
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// WindowBits = 15 + 16 (31) -> This forces GZIP encoding
// MemLevel = 8 (default)
// Strategy = Z_DEFAULT_STRATEGY
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
return MakeUnexpected("Failed to initialize gzip deflate");
zs.next_in = const_cast<Bytef *>(data.data());
zs.avail_in = static_cast<uInt>(data.size());
Vector<UINT8> outBuffer;
outBuffer.resize(deflateBound(&zs, data.size()) + 1024); // Additional 1KB buffer for safety
zs.next_out = reinterpret_cast<Bytef *>(outBuffer.data());
zs.avail_out = static_cast<uInt>(outBuffer.size());
int ret = deflate(&zs, Z_FINISH);
if (ret != Z_STREAM_END)
{
deflateEnd(&zs);
return MakeUnexpected("Failed to deflate");
}
outBuffer.resize(zs.total_out);
deflateEnd(&zs);
return outBuffer;
}
Expected<Vector<UINT8>, String> DataOps::GZipInflate(IN Span<CONST UINT8> data)
{
return ZlibInflate(data);
}
} // namespace IACore } // namespace IACore

View File

@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IACore/HttpClient.hpp> #include <IACore/HttpClient.hpp>
#include <IACore/DataOps.hpp>
#include <httplib.h> #include <httplib.h>
@ -48,19 +49,47 @@ namespace IACore
{ {
} }
String HttpClient::PreprocessResponse(IN CONST String &response)
{
const auto responseBytes = Span<CONST UINT8>{(PCUINT8) response.data(), response.size()};
const auto compression = DataOps::DetectCompression(responseBytes);
switch (compression)
{
case DataOps::CompressionType::Gzip: {
const auto data = DataOps::GZipInflate(responseBytes);
if (!data)
return response;
return String((PCCHAR) data->data(), data->size());
}
case DataOps::CompressionType::Zlib: {
const auto data = DataOps::ZlibInflate(responseBytes);
if (!data)
return response;
return String((PCCHAR) data->data(), data->size());
}
case DataOps::CompressionType::None:
default:
break;
}
return response;
}
Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers, Expected<String, String> HttpClient::RawGet(IN CONST String &path, IN Span<CONST Header> headers,
IN PCCHAR defaultContentType) IN PCCHAR defaultContentType)
{ {
auto httpHeaders = BuildHeaders(headers, defaultContentType); auto httpHeaders = BuildHeaders(headers, defaultContentType);
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false); static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
auto res = static_cast<httplib::Client *>(m_client)->Get(path.c_str(), httpHeaders); auto res = static_cast<httplib::Client *>(m_client)->Get(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders);
if (res) if (res)
{ {
m_lastResponseCode = static_cast<EResponseCode>(res->status); m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) if (res->status >= 200 && res->status < 300)
return res->body; return PreprocessResponse(res->body);
else else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body)); return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
} }
@ -81,14 +110,17 @@ namespace IACore
httpHeaders.erase(t); httpHeaders.erase(t);
} }
static_cast<httplib::Client *>(m_client)->set_keep_alive(true);
static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false); static_cast<httplib::Client *>(m_client)->enable_server_certificate_verification(false);
auto res = static_cast<httplib::Client *>(m_client)->Post(path.c_str(), httpHeaders, body, contentType.c_str()); auto res = static_cast<httplib::Client *>(m_client)->Post(
(!path.empty() && path[0] != '/') ? ('/' + path).c_str() : path.c_str(), httpHeaders, body,
contentType.c_str());
if (res) if (res)
{ {
m_lastResponseCode = static_cast<EResponseCode>(res->status); m_lastResponseCode = static_cast<EResponseCode>(res->status);
if (res->status >= 200 && res->status < 300) if (res->status >= 200 && res->status < 300)
return res->body; return PreprocessResponse(res->body);
else else
return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body)); return MakeUnexpected(std::format("HTTP Error {} : {}", res->status, res->body));
} }

View File

@ -22,11 +22,27 @@ namespace IACore
{ {
class DataOps class DataOps
{ {
public: public:
enum class CompressionType
{
None,
Gzip,
Zlib
};
public:
STATIC UINT32 Hash(IN CONST String &string);
STATIC UINT32 Hash(IN Span<CONST UINT8> data);
STATIC CompressionType DetectCompression(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> GZipInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> GZipDeflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data); STATIC Expected<Vector<UINT8>, String> ZlibInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data); STATIC Expected<Vector<UINT8>, String> ZlibDeflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data); STATIC Expected<Vector<UINT8>, String> ZstdInflate(IN Span<CONST UINT8> data);
STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data); STATIC Expected<Vector<UINT8>, String> ZstdDeflate(IN Span<CONST UINT8> data);
}; };
} } // namespace IACore

View File

@ -135,12 +135,8 @@ namespace IACore
~HttpClient(); ~HttpClient();
public: public:
// Automatically adds the following headers, if not present:
// 1) EHeaderType::CONTENT_TYPE = defaultContentType
Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers, Expected<String, String> RawGet(IN CONST String &path, IN Span<CONST Header> headers,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded"); IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
// Automatically adds the following headers, if not present:
// 1) EHeaderType::CONTENT_TYPE = defaultContentType
Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body, Expected<String, String> RawPost(IN CONST String &path, IN Span<CONST Header> headers, IN CONST String &body,
IN PCCHAR defaultContentType = "application/x-www-form-urlencoded"); IN PCCHAR defaultContentType = "application/x-www-form-urlencoded");
@ -169,6 +165,9 @@ namespace IACore
private: private:
PVOID m_client{}; PVOID m_client{};
EResponseCode m_lastResponseCode; EResponseCode m_lastResponseCode;
private:
String PreprocessResponse(IN CONST String& response);
}; };
template<typename _response_type> template<typename _response_type>