Hi Cesium community,
I’m trying to programmatically access a Cesium Ion tileset using cpp-httplib (a lightweight C++ HTTP library), but I keep getting a connection error(testHttps2 failed). The same request works perfectly with curl, so I suspect the issue lies in how I’m configuring the HTTPS client in C++.
Target URL & Token
-
Tileset URL:
https://assets.ion.cesium.com/ap-northeast-1/asset_depot/3644333/Mars/v2/tileset.json?v=2 -
Access Token: Valid short-lived JWT (expires in 1 hour)
What Works: curl Command
curl "https://assets.ion.cesium.com/ap-northeast-1/asset_depot/3644333/Mars/v2/tileset.json?v=2" \
-H "Authorization: Bearer <MY_TOKEN>" \
-H "Accept: application/json"
→ Returns valid tileset.json (HTTP 200).
What Fails: cpp-httplib (v0.20.0)
I’m using cpp-httplib 0.20.0 with OpenSSL 1.1.1w on Windows (MSVC). Here’s my simplified code:
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include <iostream>
#include <string>
#include <map>
#include <chrono>
#include <thread>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
void testHttps1();
void testHttps2(const std::string &targetUrl, const std::string &accessToken);
void testHttps2Alternative(const std::string &targetUrl, const std::string &accessToken);
bool parseCesiumResponse(const std::string &responseBody, std::string &outUrl, std::string &outToken);
bool parseFullUrl(const std::string &url, std::string &outProtocol, std::string &outHost, int &outPort, std::string &outPath);
int main()
{
std::cout << "=== Step 1: Get Cesium Asset Endpoint ===" << std::endl;
testHttps1();
return 0;
}
void testHttps1()
{
const std::string host = "api.cesium.com";
const int port = 443;
const std::string path = "/v1/assets/3644333/endpoint?access_token=<MY_TOKEN>";
const std::map<std::string, std::string> headers = {
{"User-Agent", "CesiumConnectionTester/1.0"},
{"Accept", "application/json"}};
std::cout << "Requesting: https://" << host << path << std::endl
<< std::endl;
httplib::SSLClient client(host, port);
client.enable_server_certificate_verification(false);
client.set_connection_timeout(30);
client.set_read_timeout(120);
client.set_write_timeout(30);
httplib::Headers httplibHeaders;
for (const auto &[key, value] : headers)
{
httplibHeaders.emplace(key, value);
}
auto start = std::chrono::steady_clock::now();
auto response = client.Get(path, httplibHeaders);
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if (response)
{
std::cout << "=== Step 1 Response ===" << std::endl;
std::cout << "Status code: " << response->status << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl
<< std::endl;
std::cout << "Response body:" << std::endl;
std::cout << response->body << std::endl
<< std::endl;
std::string nestedUrl, tempToken;
if (parseCesiumResponse(response->body, nestedUrl, tempToken))
{
std::cout << "=== Step 1 Parse Success ===" << std::endl;
std::cout << "Extracted nested URL: " << nestedUrl << std::endl;
std::cout << "Extracted temp access token: " << tempToken << std::endl
<< std::endl;
std::cout << "=== Step 2: Request Nested Cesium Asset URL ===" << std::endl;
testHttps2(nestedUrl, tempToken);
// testHttps2Alternative(nestedUrl, tempToken);
}
else
{
std::cout << "=== Step 1 Parse Failed: Cannot extract URL or token ===" << std::endl;
}
}
else
{
std::cout << "=== Step 1 Request Failed ===" << std::endl;
std::cout << "Error: " << httplib::to_string(response.error()) << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl;
}
}
void testHttps2(const std::string &targetUrl, const std::string &accessToken)
{
if (targetUrl.empty() || accessToken.empty())
{
std::cout << "Invalid parameters: URL or token is empty" << std::endl;
return;
}
std::string protocol, host;
int port;
std::string path;
if (!parseFullUrl(targetUrl, protocol, host, port, path))
{
std::cout << "Invalid URL format: " << targetUrl << std::endl;
return;
}
if (protocol != "https")
{
std::cout << "Unsupported protocol: " << protocol << " (only HTTPS is supported)" << std::endl;
return;
}
httplib::SSLClient client(host, port);
client.enable_server_certificate_verification(false);
client.set_connection_timeout(30);
client.set_read_timeout(120);
client.set_write_timeout(30);
client.set_keep_alive(false);
std::map<std::string, std::string> headers = {
{"User-Agent", "curl/7.88.1"},
{"Accept", "application/json"},
{"Authorization", "Bearer " + accessToken}};
std::cout << "Requesting nested URL: " << targetUrl << std::endl;
httplib::Headers httplibHeaders;
for (const auto &[key, value] : headers)
{
httplibHeaders.emplace(key, value);
}
auto start = std::chrono::steady_clock::now();
auto response = client.Get(path, httplibHeaders);
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if (response)
{
std::cout << "=== Step 2 Response ===" << std::endl;
std::cout << "Status code: " << response->status << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl
<< std::endl;
std::cout << "Response headers:" << std::endl;
for (const auto &[key, value] : response->headers)
{
std::cout << key << ": " << value << std::endl;
}
std::cout << std::endl
<< "Response body (tileset.json):" << std::endl;
if (response->body.size() > 2000)
{
std::cout << response->body.substr(0, 2000) << "..." << std::endl;
std::cout << "(Response body truncated, total size: " << response->body.size() << " bytes)" << std::endl;
}
else
{
std::cout << response->body << std::endl;
}
}
else
{
std::cout << "=== Step 2 Request Failed ===" << std::endl;
std::cout << "Error type: " << httplib::to_string(response.error()) << std::endl;
std::cout << "Possible reasons:" << std::endl;
std::cout << " 1. SSL handshake failed (check OpenSSL version)" << std::endl;
std::cout << " 2. Connection closed by server (check keep-alive config)" << std::endl;
std::cout << " 3. Invalid token or URL (verify with curl first)" << std::endl;
std::cout << "Time taken: " << duration.count() << " ms" << std::endl;
}
}
void testHttps2Alternative(const std::string &targetUrl, const std::string &accessToken)
{
std::cout << "=== Trying alternative method using system curl ===" << std::endl;
std::string cmd = "curl -s -H \"Authorization: Bearer " + accessToken + "\" ";
cmd += "-H \"User-Agent: curl/8.14.1\" ";
cmd += "-H \"Accept: */*\" ";
cmd += "\"" + targetUrl + "\"";
std::cout << "Executing: " << cmd << std::endl;
int result = system(cmd.c_str());
if (result == 0)
{
std::cout << "Curl command executed successfully" << std::endl;
}
else
{
std::cout << "Curl command failed with code: " << result << std::endl;
}
}
bool parseCesiumResponse(const std::string &responseBody, std::string &outUrl, std::string &outToken)
{
try
{
json responseJson = json::parse(responseBody);
outUrl = responseJson["url"];
outToken = responseJson["accessToken"];
return !outUrl.empty() && !outToken.empty();
}
catch (const json::exception &e)
{
std::cout << "JSON parse error: " << e.what() << std::endl;
return false;
}
}
bool parseFullUrl(const std::string &url, std::string &outProtocol, std::string &outHost, int &outPort, std::string &outPath)
{
size_t protocolEnd = url.find("://");
if (protocolEnd == std::string::npos)
{
outProtocol = "https";
protocolEnd = 0;
}
else
{
outProtocol = url.substr(0, protocolEnd);
protocolEnd += 3;
}
size_t pathStart = url.find('/', protocolEnd);
std::string hostPortStr;
if (pathStart == std::string::npos)
{
hostPortStr = url.substr(protocolEnd);
outPath = "/";
}
else
{
hostPortStr = url.substr(protocolEnd, pathStart - protocolEnd);
outPath = url.substr(pathStart);
}
outPort = (outProtocol == "https") ? 443 : 80;
size_t portSep = hostPortStr.find(':');
if (hostPortStr.front() == '[')
{
size_t ipv6End = hostPortStr.find(']');
if (ipv6End == std::string::npos)
return false;
outHost = hostPortStr.substr(1, ipv6End - 1);
if (ipv6End + 1 < hostPortStr.size() && hostPortStr[ipv6End + 1] == ':')
{
try
{
outPort = std::stoi(hostPortStr.substr(ipv6End + 2));
}
catch (...)
{
return false;
}
}
}
else if (portSep != std::string::npos)
{
outHost = hostPortStr.substr(0, portSep);
try
{
outPort = std::stoi(hostPortStr.substr(portSep + 1));
}
catch (...)
{
return false;
}
}
else
{
outHost = hostPortStr;
}
return !outHost.empty() && outPort > 0 && outPort < 65536;
}
Observed Error
-
res == nullptr -
Error message:
"Failed to read connection" -
Possible causes from logs:
-
SSL handshake failed
-
Connection closed by server immediately after handshake
-
-
return contents:
=== Step 1 Parse Success ===
Extracted nested URL: https://assets.ion.cesium.com/us-east-1/asset_depot/3644333/Mars/v2/tileset.json?v=2
Extracted temp access token: <TOKEN>
=== Step 2: Request Nested Cesium Asset URL ===
Requesting nested URL: https://assets.ion.cesium.com/us-east-1/asset_depot/3644333/Mars/v2/tileset.json?v=2
=== Step 2 Request Failed ===
Error type: Failed to read connection
Possible reasons:
1. SSL handshake failed (check OpenSSL version)
2. Connection closed by server (check keep-alive config)
3. Invalid token or URL (verify with curl first)
Time taken: 3945 ms
Questions
-
Does Cesium Ion have specific TLS or HTTP header requirements that differ from standard servers?
-
Are there known compatibility issues with cpp-httplib or certain OpenSSL configurations?
-
Has anyone successfully fetched Cesium Ion assets using C++ (not curl/Node.js/etc.)?
Any insights or working examples would be greatly appreciated!
Thanks in advance!
—
Environment: Windows 10, MSVC 2022, cpp-httplib v0.20.0, OpenSSL 1.1.1w
Feel free to copy and paste this directly into the Cesium Community Forum. It includes all necessary technical details while being concise and respectful of others’ time. Good luck!