ESP32 Arduino HTTP2: Using query parameters

In this tutorial we will check how to add query parameters to a HTTP2 GET request sent from the ESP32, using the Arduino core. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we will check how to add query parameters to a HTTP2 GET request sent from the ESP32, using the Arduino core.

We will be using the sh2lib wrapper from IDF, which offers a higher level abstraction on top of the NGHTTP2 library. For an explanation on how to setup the sh2lib on the Arduino environment, please check here.

We are going to contact this testing endpoint which echoes back to us the query string arguments of the request, as shown in figure 1.

Testing the query string by making a HTTP2 request to a test server

Figure 1 – Testing the query string in a HTTP2 GET request.

As can be seen, the query parameters should appear after the server path and they start with a question mark. They are composed by name-value pairs separated by the “&” character, following the familiar structure we use on HTTP1.

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

The code

We will start our code by including the WiFi.h library, so we can connect the ESP32 to a WiFi network, and the sh2lib.h library, which offers some higher level functions to perform the HTTP2 requests without having to worry about many of the NGHTT2 lower level details.

In order to be able to connect the ESP32 to the WiFi network, we will need to store its credentials, more precisely, the network name and password.

To finalize, we will also declare a Boolean value that will be used as a flag to signalize to our main code that the request is finished. Naturally, we will initialize its value as false and later, when the HTTP2 request is finished, set it to true.

#include "WiFi.h"

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

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

bool request_finished = false;

Moving on to the setup function, we will do the same procedure we have been covering in previous tutorials.

So, we will first open a serial connection to output the results of running our code. Then, we will connect the ESP32 to the WiFi network, using the previously declared credentials.

To finalize, we will create the FreeRTOS task that will contain the HTTP2 related function calls. The full setup function 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);

}

We will start the implementation of the FreeRTOS function by declaring a struct of type sh2lib_handle, which will be used in the next sh2lib function calls.

struct sh2lib_handle hd;

Then, we will establish the connection to the HTTP2 server by calling the sh2lib_connect function. As first input we will pass the address of our sh2lib_handle and as second argument the URI of the server.

Since this function returns ESP_OK on success, we will perform an error check on the return value. In case the connection fails, we will finish the execution of the task, since it doesn’t make sense to try to send the request to the server.

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

In this tutorial we will consider the more complex scenario where, besides the query parameters, we want to specify the HTTP headers of the request. So, before we setup the GET request, we need to declare the date structure with the name-value pairs representing the headers, like we did on this post.

For simplicity, we will simply set the necessary HTTP2 pseudo headers:

  • :method
  • :scheme
  • :authority
  • :path

As stated in the HTTP2 RFC, the :path pseudo header contains the path of the server to which we want to send the request and the query parameters [1].

So, after the path, we will add the query string when specifying the :path pseudo header. We will have two parameters, called param1 and param2, which will have the values “x” and “y“, respectively. These are just for testing purposes, so you can try with other values.

const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
                           SH2LIB_MAKE_NV(":scheme", "https"),
                           SH2LIB_MAKE_NV(":authority", hd.hostname),
                           SH2LIB_MAKE_NV(":path", "/httpbin/get?param1=x&param2=y"),
                           };

Since we are explicitly setting the headers of the request, we will need to use the sh2lib_do_get_with_nv to perform its setup, since this function is the one that allows to pass the headers array.

Note that if we used the sh2lib_do_get function, which is a simplified version that receives the path of the server but not the headers structure, we could still specify the query parameters, precisely by adding the query string at the end of the path.

This would work because the path parameter that the sh2lib_do_get function receives is directly mapped to the :path pseudo header in the implementation of the function.

Going back to the sh2lib_do_get_with_nv function we will use, we need to take in consideration that it receives as first input the address of our sh2lib_handle, as second the array of name values containing the headers, as third the length of that array and as fourth and final input a handling function that will be called to process the response from the server.

sh2lib_do_get_with_nv(&hd, nva, sizeof(nva) / sizeof(nva[0]), handle_get_response);

Since this function only setups the request, then we will need to periodically call the sh2lib_execute function to perform the actual exchange of data with the server. This function also receives as input the address of our handle.

We will call this function on an infinite loop, which will break when the response from the server is received and the stream is closed. This means that the infinite loop will break when the previously declared request_finished flag is set to true on the request handling callback function.

while (1) {

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

    if (request_finished) {
      break;
    }

    vTaskDelay(10);
}

When the request is finished and we break from the previous loop, we will call the sh2lib_free function to disconnect from the server and free the resources. Once again, this function will receive the address of our handle.

Finally, we will delete the FreeRTOS task with a call to the vTaskDelete function, passing as input the value NULL, which indicates the task is deleting itself.

The full FreeRTOS task function can be seen below and it already includes these function calls.

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

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

  Serial.println("Connected");

  const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
                             SH2LIB_MAKE_NV(":scheme", "https"),
                             SH2LIB_MAKE_NV(":authority", hd.hostname),
                             SH2LIB_MAKE_NV(":path", "/httpbin/get?param1=x&param2=y"),
                           };

  sh2lib_do_get_with_nv(&hd, nva, sizeof(nva) / sizeof(nva[0]), 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);
}

Regarding the implementation of the response handling function, it will be similar to the one we have been covering in past HTTP2 tutorials. So, when we receive data from the server, we will print its content to the serial port. Also, when we receive the stream closed flag, we will set the request_finished to true, thus indicating to the FreeRTOS task that the request is finished.

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 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://nghttp2.org") != ESP_OK) {
    Serial.println("Error connecting to HTTP2 server");
    vTaskDelete(NULL);
  }

  Serial.println("Connected");

  const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
                             SH2LIB_MAKE_NV(":scheme", "https"),
                             SH2LIB_MAKE_NV(":authority", hd.hostname),
                             SH2LIB_MAKE_NV(":path", "/httpbin/get?param1=x&param2=y"),
                           };

  sh2lib_do_get_with_nv(&hd, nva, sizeof(nva) / sizeof(nva[0]), 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 code, simply compile it and upload it to your device using the Arduino IDE. After the procedure finishes, open the Serial Monitor tool of the Arduino IDE. You should get an output similar to figure 2, which shows the value of the headers we sent to the server being echoed back in the response.

Answer to a ESP32 HTTP2 GET request containing query parameters, printed to the Arduino IDE serial monitor

Figure 2 – Printing the server response, with the query parameters echoed back.

Related Posts

References

[1] https://tools.ietf.org/html/rfc7540#section-8.1.2.3

Advertisements

One Reply to “ESP32 Arduino HTTP2: Using query parameters”

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