ESP32: HTTP/2 PUT request

The objective of this tutorial is to explain how to do a HTTP/2 PUT request using the ESP32 and the Arduino core. The tests from this tutorial were done using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

The objective of this tutorial is to explain how to do a HTTP/2 PUT request using the ESP32 and the Arduino core. We will be using the sh2lib wrapper from IDF, which offers a higher level API built on top of NGHTTP2.

Note that we have already covered how to send a HTTP/2 POST request on this previous tutorial and the code shown here will be very similar.

We are going to send the PUT request to an endpoint on this HTTP/2 server, which will return back to us the request content capitalized.

The tests from this tutorial were done using a DFRobot’s ESP32 module integrated in a ESP32 development board.

The code

Includes and global variables

We will start the code by including both the WiFi.h and the sh2lib.h libraries. These are needed to connect the ESP32 to the WiFi network and to perform the HTTP/2 PUT request, respectively.

Then, we will declare two variables to hold the credentials of the WiFi network, so we can later connect to it. As usual, we will need the network name and the password.

We will also need a Boolean variable that will act as a flag to signal when the request is finished. Naturally, we will initialize it to false and then, after receiving the response from the server, we will change it to true.

To finalize this section of the code, we will declare a string with the content to send to the server. It will be a simple testing message.

#include "WiFi.h"

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

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

bool request_finished = false;

char * toSend = "test put";

The setup

We will begin the setup function by opening a serial connection, to later output some results from our program. Following 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 the FreeRTOS task that will be responsible for handling all the HTTP/2 related functionality, pretty much like we have been doing in past tutorials.

The full code for the 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);

}

The FreeRTOS function

As usual, we will start our FreeRTOS function declaring a struct of type sh2lib_handle, which will be used on the next function calls.

After that, we will establish the connection to the HTTP/2 server by calling the sh2lib_connect function. As first input we pass the address of our previously declared handle and as second input the URL of the server.

We will do an error checking on the returned value to guarantee that we only try to perform the request in case the connection to the server succeeds.

struct sh2lib_handle hd;

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

If we pass this point without any error, it means that the connection to the server is already established. So, we will setup the HTTP/2 PUT request with a call to the sh2lib_do_put function.

This function receives as first input the address of our handle and as second input the endpoint of the server that we want to reach.

As third parameter we will pass a callback function that will be called by the HTTP/2 framework to get the data to be sent to the server. As fourth and last parameter, we need to pass another callback that will be responsible for handling the response from the server.

sh2lib_do_put(&hd, "/ECHO", send_put_data, handle_put_response);

Since this function only does the setup of the request, we still need to periodically call the sh2lib_execute function in order to do the actual exchange of data with the server. We will do it in a loop that will only break when the request_finished flag is set to true.

After the loop breaks, we know that the request was finished, so we can disconnect from the server and delete the FreeRTOS task.

The full FreeRTOS function code 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);
  }

  sh2lib_do_put(&hd, "/ECHO", send_put_data, handle_put_response);

  while (1) {

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

    if (request_finished) {
      break;
    }

    vTaskDelay(10);
  }

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

  vTaskDelete(NULL);
}

The send data callback function

As already mentioned, this function will take care of specifying the data to be sent to the server. Two of the parameters passed to this function are the following: a pointer to a data buffer and a maximum length.

Basically, we specify the data to be sent to the server by copying it to the mentioned buffer. Note however that the length of the data that we copy when the function is called cannot be greater than the mentioned maximum length parameter.

After we copy all the data to the buffer, we need to signal that there’s no more content to send to the server. To do it, we make use of another parameter that is passed to the callback function, which corresponds to a pointer to some control flags.

To signal that there is no more data to send, we simply need to set the NGHTTP2_DATA_FLAG_EOF to 1, and the HTTP/2 framework will know that there is no more data left to send.

As output of the callback function, we should return the length of the data copied to the buffer. The full callback function implementation can be seen below.

int send_put_data(struct sh2lib_handle *handle, char *buf, size_t length, uint32_t *data_flags)
{
    int copylen =  strlen(toSend);
    if (copylen < length) {
        memcpy(buf, toSend, copylen);
    } else {
        Serial.println("Cannot write to buffer");
        copylen = 0;
    }

    (*data_flags) |= NGHTTP2_DATA_FLAG_EOF;
    return copylen;
}

The server response callback function

The implementation of this function will also be very simple. First, we will look into the second and third parameters it receives, which correspond to a pointer to the buffer with the data sent by the server as response and the length of that buffer, respectively.

So, if we have a length greater than 0, it means that we have received some data and we will print it to the serial port.

Besides that, we will also check if the request is already finished by looking into the fourth parameter of this callback function. If it equals to DATA_RECV_RST_STREAM, it means the stream was closed and thus the request is finished.

We will signal this information to the FreeRTOS task by setting the request_finished flag to true. The full callback function code can be seen below.

int handle_put_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 complete code can be seen below.

#include "WiFi.h"

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

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

bool request_finished = false;

char * toSend = "test put";

int handle_put_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;
}

int send_put_data(struct sh2lib_handle *handle, char *buf, size_t length, uint32_t *data_flags)
{
    int copylen =  strlen(toSend);
    if (copylen < length) {
        memcpy(buf, toSend, copylen);
    } else {
        Serial.println("Cannot write to buffer");
        copylen = 0;
    }

    (*data_flags) |= NGHTTP2_DATA_FLAG_EOF;
    return copylen;
}

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);
  }

  sh2lib_do_put(&hd, "/ECHO", send_put_data, handle_put_response);

  while (1) {

    if (sh2lib_execute(&hd) != ESP_OK) {
      Serial.println("Error in execute");
      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 from the previous sections, simply compile it and upload it to your ESP32 using the Arduino IDE. Once the procedure finishes, open the serial monitor to check the results outputted from the device.

You should get a result similar to figure 1, which shows the data we sent to the server being echoed back capitalized, as expected.

Response to PUT HTTP/2 request sent from the ESP32, using the Arduino core

Figure 1 – Output of the program, with the capitalized response from the server.

Related Posts

3 Replies to “ESP32: HTTP/2 PUT request”

Leave a Reply