ESP32: HTTP/2 GET request to Node.js server

In this tutorial we will check how to make a HTTP/2 GET request from the ESP32 to a Node.js server. The tests shown on this tutorial were performed using an ESP32 board from DFRobot.

Introduction

In this tutorial we will check how to make a HTTP/2 GET request from the ESP32 to a Node.js server. The ESP32 will be running the Arduino core and we will be using the sh2lib wrapper from IDF for the HTTP/2 related functionalities.

If you haven’t yet installed the sh2lib as an Arduino library for the ESP32, please check this previous post for a detailed explanation on how to do it.

For an introductory tutorial on how to make a HTTP/2 GET request with the ESP32, please check here. For an explanation on how to setup a HTTP/2 server with node.js, please consult this post.

Note that this tutorial assumes that both the ESP32 and the machine that will be running the Node.js HTTP/2 server will be connected to the same network.

The tests shown on this tutorial were performed using an ESP32 board from DFRobot.

The node.js server code

In order for us to be able to setup the server, we will need to both generate a private key and a certificate. The procedure to do it was explained in the already mentioned Node.js tutorial.

So, for this section, I’m assuming you have already followed the tutorial and generated the private key and certificate files with the following names: localhost-privkey.pem and localhost-cert.pem.

Moving on to the code, we will first import the required Node.js modules. We will need the HTTP/2 module, so we can setup the HTTP/2 server, and the file system module, so we can read the content of the localhost-privkey.pem and localhost-cert.pem files.

const http2 = require('http2');
const fs = require('fs');

After this, we will define a callback function that will be executed when a request is received by the server. When this callback is called by the HTTP/2 module, it will receive  as parameters an object of class Http2ServerRequest and an object of class Http2ServerResponse.

function onRequest (req, resp) {
// callback function implementation
}

We can then make use of the second object so send back a response to the client. In our case, it will be a very simple plain text sentence, just for testing purposes.

So, we just need to call the end method on the Http2ServerResponse object, passing as input the content we want to return to the client.

function onRequest (req, resp) {
	resp.end("Hello World from Node.js");
}

Next, to create the server, we need to call the createSecureServer function. As first input, we will pass an object containing some server options. In our case, this object will simply contain two attributes with the content of the key and certificate files.

We will read the content of these files with a call to the readFileSync function from the file system module. This function receives as input the path to the file we want to read.

For this tutorial I’m assuming that both the certificate and private key files are on the same folder of the Node.js code, which means we only need to specify the file names as input of the readFileSync function.

As second argument of the createSecureServer function, we will pass our previously defined callback.

const server = http2.createSecureServer({
  key: fs.readFileSync('localhost-privkey.pem'),
  cert: fs.readFileSync('localhost-cert.pem')
}, onRequest);

To finalize, we will call the listen method on the server object returned by the previous call to the createSecureServer function. We will pass as input the port where the server should listen to incoming requests. After this, the server should be up and ready to serve clients.

server.listen(8443);

The full code can be seen below.

const http2 = require('http2');
const fs = require('fs');

function onRequest (req, resp) {
	resp.end("Hello World from Node.js");
}

const server = http2.createSecureServer({
  key: fs.readFileSync('localhost-privkey.pem'),
  cert: fs.readFileSync('localhost-cert.pem')
}, onRequest);

server.listen(8443);

The ESP32 Arduino code

Includes and global variables

We will start the code by importing the needed libraries, more precisely the WiFi.h and the sh2lib.h. The first one is needed for connecting the device to a WiFi network and the second one exposes the API with the HTTP/2 functionalities.

#include "WiFi.h" 

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

Then we will declare the credentials of the WiFi network as global variables. We will also need an additional Boolean variable that will be used as a flag to signal when the request is finished.

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

bool request_finished = false;

The setup function

Moving on to the setup function, we will start by opening a serial connection and then we will connect the ESP32 to the WiFi network, using the previously declared credentials.

To finalize the setup, we will launch a FreeRTOS task that will be responsible for handling the HTTP/2 function calls. We will check its implementation 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);

}

The FreeRTOS task

On the FreeRTOS task, we will start by declaring a struct of type sh2lib_handle. This struct will be used by the sh2lib function calls we will do below.

struct sh2lib_handle hd;

Then, we will call the sh2lib_connect function to establish a connection to our HTTP/2 server. The function receives as first input the address of the sh2lib handle and as second input the URL of the server.

In order to be able to reach the Node.js server, you will need to find the local IP address of the machine that is running it. If you are on Windows, you can run the ipconfig command on the command line. If you are on Linux, you can run the ifconfig command, also on the command line.

After finding the IP address of the machine, the final format of the URL to pass to the sh2lib_connect function should be like the one shown below, where you should change #yourMachineIp# by the IP address you found.

https://#yourMachineIp#:8443

You can check the full function call below, which also includes an error validation, to confirm that we have succeeded to connect to the HTTP/2 server before trying to send the request.

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

After this, we will setup the request by calling the sh2lib_do_get function. As first input we pass the address of our handle and as second input the endpoint we want to reach. As third input we need to pass a callback function to handle the response from the server, which we will define later.

Regarding the endpoint parameter, we can choose any route because our server is not doing any check for which routes are valid. Thus, it will always answer. For testing purposes, we will reach a route called “/test“.

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

As mentioned, the previous function call only does the setup of the request. After this, we need to call the sh2lib_execute function periodically, which will handle the actual exchange of data with the server.

We will do this in a loop with a small delay between each iteration. The loop will only break when the global flag that signals the end of the request is set to true.

while (1) {

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

    if (request_finished) {
      break;
    }

    vTaskDelay(10);
}

After the loop breaks, we know the response was received and thus we can now disconnect from the server and free the resources. This is done with a call to the sh2lib_free function.

sh2lib_free(&hd);

To finalize, we will delete the FreeRTOS task, which is no longer needed. The full function code can be seen below.

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

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

  Serial.println("Connected");

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

  while (1) {

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

    if (request_finished) {
      break;
    }

    vTaskDelay(10);
  }

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

  vTaskDelete(NULL);
}

The server response callback

The implementation of the response callback function will be similar to what we have been covering on the previous posts. Recall that, when called by the HTTP/2 framework, this function will receive as input four parameters:

  • A pointer to a sh2lib_handle struct
  • A pointer to a buffer that will hold the received data
  • The length of the received data
  • An integer that corresponds to the flags indicating if the stream is closed or if a particular received frame is finished

First we will check if the length of the data is greater than zero. If it is, then we will print the content of the data buffer.

Then, we will also check if the fourth parameter is equal to the DATA_RECV_RST_STREAM constant. If it is, then we know the stream was closed and thus we set the request finished flag to true.

You can check the full callback function implementation below. Note that the function should return 0, as indicated in the header file of the sh2lib wrapper.

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) {
        request_finished = true;
        Serial.println("STREAM CLOSED");
    }
    return 0;
}

The final code

The final source code can be seen below.

#include "WiFi.h"

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

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

bool request_finished = false;

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) {
        request_finished = true;
        Serial.println("STREAM CLOSED");
    }
    return 0;
}

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

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

  Serial.println("Connected");

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

  while (1) {

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

    if (request_finished) {
      break;
    }

    vTaskDelay(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 whole system, first run the Node.js code on a tool of your choice. I’ll be using Visual Studio Code with the Code Runner extension.

You can do a quick test to check if the server is responding correctly to requests by accessing, on a web browser with HTTP/2 support, to the same server URL and endpoint we have used on the Arduino code. You can check the expected result at figure 1.

Node.js server Hello World to Chrome browser client

Figure 1 – HTTP/2 GET request to Node.js using a web browser (Google Chrome).

After confirming the server is working correctly, simply compile and upload the Arduino code to your ESP32. Then open the Arduino IDE serial monitor. After the request is performed, you should get a result similar to figure 2.

ESP32 Arduino performing HTTP/2 GET request to Node.js server

Figure 2 – Output of the ESP32 program.

Leave a Reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading