Juce URL withPostData failing to open WebInputStream only on Windows

Hello, I have some juce code to login to my woocommerce website.
It runs flawlessly on Mac, but I was testing it on Windows and it always fails to open the WebInputStream.

I cannot for the life of me figure out why it is failing either.

On windows it always fails to create the WebInputStream with status code 0.

I have some oddities in the debugger specifically in the juce::WebInputStream::connect(Listener* listener) method. For some reason the first time that the breakpoint is hit, hasCalledConnect is already set to true. I have a feeling that’s just a side effect of the threading though because it doesn’t do that if I try to create an input stream on the main thread.

If the code also didn’t work on Mac I’d believe I’m doing something wrong but it works fine on Mac. So I think there’s something wrong with the Windows networking implementation?

void AuthService::login(const juce::String& usernameOrEmail, 
                       const juce::String& password,
                       std::function<void(LoginResponse)> callback)
{
    auto loginUrl = "https://my-website.com";
    
    // Create the form data
    juce::StringPairArray postData;
    postData.set("username", usernameOrEmail);
    postData.set("password", password);
    
    // Add WooCommerce authentication
    postData.set("consumer_key", consumerKey);
    postData.set("consumer_secret", consumerSecret);

    // Start the async request in a separate thread
    std::thread([callback, loginUrl, postData, this]() mutable {
        LoginResponse result;
        bool success = false;
        int retryCount = 0;
        const int maxRetries = 3;

        while (!success && retryCount < maxRetries)
        {
            try
            {
                DBG("AuthService: Login attempt " + juce::String(retryCount + 1) + " of " + juce::String(maxRetries));
                
                // Setup headers for form POST request
                juce::StringPairArray headers;
                headers.set("Content-Type", "application/x-www-form-urlencoded");
                headers.set("User-Agent", "NNAudioHub/1.0");
                
                int statusCode = 0;
                auto options = juce::URL::InputStreamOptions(juce::URL::ParameterHandling::inPostData)
                    .withHttpRequestCmd("POST")
                    .withExtraHeaders(headers.getDescription())
                    .withConnectionTimeoutMs(60000)  // Increased timeout to 60 seconds
                    .withStatusCode(&statusCode)
                    .withNumRedirectsToFollow(5);    // Allow redirects

                // Create the POST data string
                j                const auto& postKeys = postData.getAllKeys();
                const auto& postValues = postData.getAllValues();
                for (auto i = 0; i < postData.size(); ++i)
                {
                    if (i > 0)
                        postDataString += "&";
                    postDataString += juce::URL::addEscapeChars(postKeys[i], true);
                    postDataString += "=";
                    postDataString += juce::URL::addEscapeChars(postValues[i], true);
                }
                
                // Create input stream with POST method
                if (auto stream = juce::URL(loginUrl)
                        .withPOSTData(postDataString)
                        .createInputStream(options))
                {
                    DBG("AuthService: Got response with status code: " + juce::String(statusCode));

                   // do stuff with the data
                }
                else
                {
                    DBG("AuthService: Failed to create input stream, status code: " + juce::String(statusCode));
                }
            }
            catch (const std::exception& e)
            {
                DBG("AuthService: Exception during login attempt: " + juce::String(e.what()));
            }

            retryCount++;
            if (!success && retryCount < maxRetries)
            {
                auto delayMs = (1000 * (1 << retryCount));  // Exponential backoff
                DBG("AuthService: Retrying in " + juce::String(delayMs) + "ms...");
                juce::Thread::sleep(delayMs);
            }
        }
}
1 Like

+1

I have the same exact problem, but on IOS (no error, status code 200) vs Android (error, status code 0)

(doesn’t work with JUCE 8.0.3 or 8.0.4)…

A HTTPS POST works in our case with Windows 10 and macOS with JUCE 8.0.4. But we call it from the main thread for the start. Maybe change this later.
I recommend testing with a synchronous version for a start to see if that works.

I always use a juce::ThreadPool or juce::Thread instance instead of the std::thread. This allows you to have some control over the lifetime and number of threads you create. I’m not sure what happens when someone closes your application while the thread is running.

I also think 60 seconds is way too long for a timeout. I would keep this shorter. It’s easy to add a cancellation token to your code between retries, but a network connection probably hangs / blocks until it runs into the timeout.

The problem (in my case), is that you can’t have https calls in the main thread with Android :face_holding_back_tears:

Yes, I know. You should create a new topic about that. This thread is Windows related.

I created a new blank project and tried to make the call from the main thread and it had the same issue on Windows.

That is good information about threading though, so thanks for that :+1:

So, apparently this was the problem.

withExtraHeaders() was failing the connection, but only on Windows. It was fine on Mac.
Turns out we didn’t need those headers for the request to work anyway, so all I did was remove the .withExtraHeaders() line on the InputStreamOptions and then it worked just fine.

            // Setup headers for form POST request
            juce::StringPairArray headers;
            headers.set("Content-Type", "application/x-www-form-urlencoded");
            headers.set("User-Agent", "NNAudioHub/1.0");
            
            int statusCode = 0;
            auto options = juce::URL::InputStreamOptions(juce::URL::ParameterHandling::inPostData)
                .withHttpRequestCmd("POST")
                .withExtraHeaders(headers.getDescription())
                .withConnectionTimeoutMs(60000)
1 Like