Failed to Fetch Cesium Ion Tileset via cpp-httplib (SSL/Connection Issue) – Works with curl but Not in C++

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++.

:link: 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)

:white_check_mark: 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).

:cross_mark: 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;
}

:hammer_and_wrench: 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

:red_question_mark: Questions

  1. Does Cesium Ion have specific TLS or HTTP header requirements that differ from standard servers?

  2. Are there known compatibility issues with cpp-httplib or certain OpenSSL configurations?

  3. 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!

Cesium ion does require any non-standard HTTP or TLS headers. We do require providing either the ion access token or the asset access token as documented for our API, which it looks like you are already doing.

Your assement that is probably something in cpp-httplib or your code seems correct since it works with with curl and other clients. I took a look through your code and nothing obvious stood out as being incorrect. However I’m not familar with the library you are using and haven’t had to write C++ in a while so it is very possible that I could have overlooked something.

Our Cesium Native library does use C++ to stream 3D tile so that is something to consider if were not aware of it.

If you do figure out what went wrong, please let us know.