ESP32 HTTP/2: GET request with continuous stream response

In this tutorial we will test making a HTTP/2 GET request to a endpoint that will leave a stream open and periodically send data back to the client with the current date and time. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we will test making a HTTP/2 GET request to a endpoint that will leave a stream open and periodically send data back to the client with the current date and time.

The endpoint we are going to contact will be this one and you can test its behavior from a web browser (I’ve tested with Firefox and Chrome and both work fine). Basically, after opening the URL, the server will stream the current time and date every second.

We are going to use the Arduino core and the sh2lib, a wrapper from IDF that is built on top of NGHTTP2 and offers a simplified API for making HTTP/2 requests. For a detailed guide on how to get make the sh2lib work as a regular Arduino library, please check this previous post.

The code shown here was based on IDF’s HTTP/2 example, which I encourage you to try. For a tutorial on how to make a simple HTTP/2 GET request from the ESP32 using the Arduino core, please check here.

The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

The code

We start our code by including the libraries we will need to connect the ESP32 to the WiFi network and then to do the HTTP/2 request.

So, we will need the WiFi.h library, which allows to connect the ESP32 to the WiFi network, and the sh2lib.h lib. Note that we will need to enclose the sh2lib.h include in an extern “C” block so the code can be correctly compiled.

#include "WiFi.h"

extern "C" {
#include "sh2lib.h"
}

We will also need credentials of the WiFi network, more precisely the network name (SSID) and the password. We will store them in two global variables.

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

Moving on to the setup function, we will start by opening a serial connection, so we can output the results of our program. After that, we will connect the ESP32 to the WiFi network, making use of the previously declared credentials.

To finalize the setup function, we will launch a FreeRTOS task, which will contain the HTTP/2 related function calls. Our task function will be called http2_task and we will analyze its implementation below.

We will also keep the remaining task configuration parameters (stack size and priority) used in the original example from IDF, which I encourage you to test.

The complete setup function with all the previously mentioned procedures can be seen below.

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  xTaskCreate(http2_task, "http2_task", (1024 * 32), NULL, 5, NULL);

}

Moving on to the task implementation function, the first thing we will do is connecting to the HTTP/2 server. To do this, we will need a struct of type sh2lib_handle, which will be used in all the sh2lib functions we will call below.

struct sh2lib_handle hd;

Then, to establish the connection to the server, we call the sh2lib_connect function, passing as first input the address of the sh2lib_handle we have just declared and as second input the URL of the server we are going to connect to.

Since this function call returns ESP_OK on success, we will use the return value for error checking, to ensure the connection was establish before we try to send the GET request.

if (sh2lib_connect(&hd, "https://http2.golang.org") != ESP_OK) {
    Serial.println("Error connecting to HTTP2 server");
    vTaskDelete(NULL);
}

Next, we will setup the GET request by calling the sh2lib_do_get function. As first input, this function receives the address of our handle and as second input the relative path of the endpoint to which we are going to send the request.

As third and final parameter, this function receives an handling function that will be executed to process the response from the server. We will check the implementation of this callback function below.

sh2lib_do_get(&hd, "/clockstream", handle_get_response)

To start the actual exchange of data with the server, we will nee to call the sh2lib_execute function, which also receives as input the address of our handle.

We need to call this function periodically and since we are going to contact an endpoint that will leave the stream open and keep sending data continuously, then we can do it in an infinite loop, with a small delay between each iteration.

We will also include an error check for each call of the function, which also returns ESP_OK on success.

while (1) {

   if (sh2lib_execute(&hd) != ESP_OK) {
      Serial.println("Error in send/receive\n");
      break;
   } 

   delay(10);
}

To finalize the function code, we will disconnect from the server and then delete the task. However, if everything works as expected, then the infinite loop should never break and we should not hit this part of the code.

The full task function can be seen below.

void http2_task(void *args)
{
  struct sh2lib_handle hd;

  if (sh2lib_connect(&hd, "https://http2.golang.org") != ESP_OK) {
    Serial.println("Error connecting to HTTP2 server");
    vTaskDelete(NULL);
  }

  Serial.println("Connected");

  sh2lib_do_get(&hd, "/clockstream", handle_get_response);

  while (1) {

    if (sh2lib_execute(&hd) != ESP_OK) {
      Serial.println("Error in send/receive");
      break;
    } 

    delay(10);
  }

  sh2lib_free(&hd);
  Serial.println("Disconnected");

  vTaskDelete(NULL);
}

Now we are going to define the request callback function, which will handle the reception of the data from the server response. Recall from the previous tutorial that this callback function should follow a predefined signature.

int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags){
// Callback function implementation
}

As covered in the mentioned tutorial, the second argument contains a pointer to a buffer with the received data, and the third argument contains the length of that buffer. So, when this callback function is called and the length of the data is greater than zero (which means data was received), then we will print it to the serial port.

Note that we are going to use the printf method of the Serial object, which allows to use the %.*s format specifier. This format specifier allows us to use the pointer to the buffer and its length to print it as a string. You can read more about format specifiers here.

if (len > 0) {
    Serial.printf("%.*s\n", len, data);
}

As fourth parameter, we receive as input of this callback an integer that indicates if a stream is closed or a particular frame is completely received. Although, in our case, we know that the stream will never be closed and the server will continuously send data, we are going to also check for this event, to confirm that it never happens.

if (flags == DATA_RECV_RST_STREAM) {
    Serial.println("STREAM CLOSED");
}

The full callback function can be seen below.

int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
{
    if (len > 0) {
        Serial.printf("%.*s\n", len, data);
    }

    if (flags == DATA_RECV_RST_STREAM) {
        Serial.println("STREAM CLOSED");
    }
    return 0;
}

The final code can be seen below. Note that, since we are not going to perform any computation in the main loop, we are deleting the task with a call to the vTaskDelete function, passing as input the value NULL, which basically indicates to the task to delete itself.

#include "WiFi.h"

extern "C" {
#include "sh2lib.h"
}

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

int handle_get_response(struct sh2lib_handle *handle, const char *data, size_t len, int flags)
{
    if (len > 0) {
        Serial.printf("%.*s\n", len, data);
    }

    if (flags == DATA_RECV_RST_STREAM) {
        Serial.println("STREAM CLOSED");
    }
    return 0;
}

void http2_task(void *args)
{
  struct sh2lib_handle hd;

  if (sh2lib_connect(&hd, "https://http2.golang.org") != ESP_OK) {
    Serial.println("Error connecting to HTTP2 server");
    vTaskDelete(NULL);
  }

  Serial.println("Connected");

  sh2lib_do_get(&hd, "/clockstream", handle_get_response);

  while (1) {

    if (sh2lib_execute(&hd) != ESP_OK) {
      Serial.println("Error in send/receive");
      break;
    } 

    delay(10);
  }

  sh2lib_free(&hd);
  Serial.println("Disconnected");

  vTaskDelete(NULL);
}

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  xTaskCreate(http2_task, "http2_task", (1024 * 32), NULL, 5, NULL);

}

void loop() {
  vTaskDelete(NULL);
}

Testing the code

To test the code, simply compile it and upload it to your ESP32, using the Arduino IDE. When the procedure finishes, open the serial monitor. You should see a continuous stream of the current date and time being printed, with a periodicity of 1 second, as illustrated in figure 1.

Note that the firs part of junk data is actually sent by the server and, if we access the same endpoint from a web browser, we also receive it, thus confirming the response from the server is being correctly processed by the ESP32.

ESP32 HTTP2 GET request to stream endpoint.png

Figure 1 – Data sent as response from the server.

Related posts

Advertisements

3 Replies to “ESP32 HTTP/2: GET request with continuous stream response”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s