Non-blocking https post request with ArduinoHttpClient

Hi, I want to send a Pushover notification, I have a Nano 33 IoT device.
With the following code the notification is sent successfully:

// Libraries
#include <ArduinoHttpClient.h>
#include <ArduinoJson.h>
#include <WiFiNINA.h>

// Objects
WiFiSSLClient wifiSSL;
HttpClient client = HttpClient(wifiSSL, "api.pushover.net", 443);

// Setup
void setup() {
  int wifi_status = WL_IDLE_STATUS;
  wifi_status = WiFi.begin("ssid", "passphrase");
  delay(10000); // wait 10 seconds for connection

  if (wifi_status != WL_CONNECTED) {
    while (true); // not continue
  }

  // Send notification
  StaticJsonDocument<512> notification;
  notification["token"] = "pushoverToken";
  notification["user"] = "pushoverUser";
  notification["message"] = "Notification";
  
  String jsonStringNotification;
  serializeJson(notification, jsonStringNotification);

  client.post("/1/messages.json", "application/json", jsonStringNotification);
 
  int statusCode = client.responseStatusCode();
  String response = client.responseBody();
} 

// Loop
void loop() {
}

The "client.post" comand is blocking for about 1,5 seconds, while the "client.responseBody()" takes about 1 second. These delays are important for a microcontroller (but still acceptable for my application), but the real issue is what happens if something goes wrong, for example if the server doesn't respond: in this scenario "client.post" takes about 10 seconds, and "client.responseBody()" takes about 30 seconds.
I attemped using the "Fetch" library, but it uses the transport WiFiClientSecure and not the WiFiSSLClient, so I can't compile the sketch.
I would rather use the ArduinoHttpClient library, I wonder if there is a way to use it in a non-blocking mode.
Any help or suggestion are welcome, thank you.

First, the ArduinoHttpClient timeout defaults to 30 seconds

    // Number of milliseconds that we'll wait in total without receiving any
    // data before returning HTTP_ERROR_TIMED_OUT (during status code and header
    // processing)
    static const int kHttpResponseTimeout = 30*1000;

Second, there's no point calling responseBody if the responseStatusCode is not successful, likely 200 (but could be 201, especially for a POST); i.e. not

// Spent too long waiting for a reply
static const int HTTP_ERROR_TIMED_OUT =-3;

So after sending the request, you could call

    virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; };

to a low number, like 100 milliseconds. Then handle it in a non-blocking manner: any response code?

  • no: skip and try again on the next loop
  • yes: was it successful?
    • no: certainly print that status code to show what's going wrong. Maybe try to read the error body and print that too
    • yes: read the responseBody

This relies on the usual behavior of web servers: it may take a few seconds to come up with a response, but once the response is ready, you get the status code and the body all at once. For very large responses or streams, you could get more pauses, which makes things more complicated.

Third, taking another step back/up, there's no point looking for any kind of response of the request did not work. client.post also returns a value; be sure it is

static const int HTTP_SUCCESS =0;

Any time spent in that function is likely trying to get a connection through the WiFiSSLClient. Unfortunately, this looks to be hard-coded to 10 seconds

      ServerDrv::startClient(uint32_t(ip), port, _sock, TLS_MODE);

      unsigned long start = millis();

      // wait 4 second for the connection to close
      while (!connected() && millis() - start < 10000)
        delay(1);

(not sure what that comment means). So maybe try to connect in setup, and then check if it's still connected before trying to post?

1 Like