ESP32 HTTPS server: Handling route not found

In this tutorial we will check how the handle the case when a client sends a request to a non existing route, on a HTTPS web server hosted on the ESP32. The tests shown on this tutorial were performed using an ESP32 board from DFRobot.

Introduction

In this tutorial we will check how the handle the case when a client sends a request to a non existing route, on a HTTPS web server hosted on the ESP32. We will be using the Arduino core to program the ESP32.

For an introduction on how to get started setting up a HTTPS web server on the ESP32, please check this tutorial.

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

The code

We will start the code by doing all the library includes we need to be able to connect the ESP32 to a WiFi network and then to setup the HTTPS web server.

#include <WiFi.h>
#include <HTTPSServer.hpp>
#include <SSLCert.hpp>
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>

We will also declare the using of the httpsserver namespace, so we avoid the need of using the c++ scope resolution operator.

using namespace httpsserver;

Then, we will declare the WiFi network credentials we need for the ESP32 to be able to establish the connection. We will need the network name and password.

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

We will also need a pointer to an object of class SSLCert, which will store the certificate information, and a pointer to an object of class HTTPSServer, which we will use to configure the server routes.

SSLCert * cert;
HTTPSServer * secureServer;

Moving on to the Arduino setup function, we will start by opening a serial connection, to be able to output content from our program.

Serial.begin(115200);

Then we will handle the generation of a self signed certificate, so we can easily test our server without having to manually generate the certificate ourselves. Note that, when running the code, this step will take a while.

//----Generating the certificate----
Serial.println("Creating certificate...");
    
cert = new SSLCert();
  
int createCertResult = createSelfSignedCert(
    *cert,
    KEYSIZE_2048,
    "CN=myesp.local,O=acme,C=US");
    
if (createCertResult != 0) {
    Serial.printf("Error generating certificate");
    return; 
}
  
Serial.println("Certificate created with success");

Next we will handle the connection of the ESP32 to the WiFi network, using the credentials we have declared before.

//----Connect to WiFi----
WiFi.begin(ssid, password);
   
while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
}  
   
Serial.println(WiFi.localIP());

Next we will create an instance of the HTTPSServer class. This will be used to configure the server routes.

secureServer = new HTTPSServer(cert);

Then we will create a ResourceNode for the index route, so we have at least one working route in our server. The implementation of the route handling function will consist on returning a textual message back to the client.

Note that a 200 status code will be returned, since this is the default value when we don’t explicitly specify it.

ResourceNode * indexRoute = new ResourceNode("/", "GET", [](HTTPRequest * req, HTTPResponse * res){
    res->println("IndexRoute");
});

After this, we will create an additional ResourceNode for handling the route not found case. In this case, since this node will be handling the route not found, we can pass empty strings as first and second parameters of the constructor. These parameters correspond to the route name and HTTP method, respectively.

As third parameter we still pass an handling function, which we will implement using the C++ lambda syntax.

ResourceNode * notFoundRoute = new ResourceNode("", "", [](HTTPRequest * req, HTTPResponse * res){
    // Handling function implementation
});

For the implementation of the handling function, the first thing we will do is discarding any body that might have been sent with the request, since we won’t be parsing it or doing any computation using it.

We do this by calling the discardRequestBody method on the HTTPRequest object pointer we receive as input of the handling function.

req->discardRequestBody();

Then we will set the HTTP response status code to 404 (Not Found). We do this by calling the setStatusCode method on the HTTPResponse object pointer, also received as input of the handling function. This method receives as parameter the status code to be returned back to the client.

If you need a more detailed explanation on how to set the HTTP return code, please check this previous post.

res->setStatusCode(404);

Finally, we return back a generic “Route not found” message to the client. We do this by calling the println method on the same HTTPResponse object pointer, passing as input the string to return back to the client.

res->println("Route Not Found");

The complete ResourceNode instantiation can be seen below.

ResourceNode * notFoundRoute = new ResourceNode("", "GET", [](HTTPRequest * req, HTTPResponse * res){
    req->discardRequestBody();
    res->setStatusCode(404);

    res->println("Route Not Found");
});

Next we need to register both nodes on our server. To register the node that will handle the index route, we simply call the registerNode method on our HTTPSServer object, passing as input the pointer to the index route ResourceNode.

secureServer->registerNode(indexRoute);

To register the route not found default handler, we need to call the setDefaultNode method on our server object, passing as input the pointer to the second ResourceNodewe have created.

secureServer->setDefaultNode(notFoundRoute);

After this, we simply need to call the start method on our server object, so it starts listening to incoming requests from clients. To confirm the server started correctly, we can call the isRunning method afterwards. 

secureServer->start();
    
if (secureServer->isRunning()) {
    Serial.println("Server ready.");
}else{
    Serial.println("Server could not be started.");
}

With this, we finish our setup function. To finalize the whole code, we need to periodically call the loop method on our server object, to handle incoming client requests. This can be done on the Arduino main loop.

void loop() {
   
  secureServer->loop();  
  delay(10);
}

The final complete code can be seen below.

#include <WiFi.h>
#include <HTTPSServer.hpp>
#include <SSLCert.hpp>
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>
  
using namespace httpsserver;
  
const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";
 
SSLCert * cert;
HTTPSServer * secureServer;
  
void setup() {
  
  Serial.begin(115200);
 
 
  //----Generating the certificate----
  Serial.println("Creating certificate...");
    
  cert = new SSLCert();
  
  int createCertResult = createSelfSignedCert(
    *cert,
    KEYSIZE_2048,
    "CN=myesp.local,O=acme,C=US");
    
  if (createCertResult != 0) {
    Serial.printf("Error generating certificate");
    return; 
  }
  
  Serial.println("Certificate created with success");
 
 
  //----Connect to WiFi----
  WiFi.begin(ssid, password);
   
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }  
   
  Serial.println(WiFi.localIP());
 
 
  //----Configure server----
  secureServer = new HTTPSServer(cert);
  
  ResourceNode * indexRoute = new ResourceNode("/", "GET", [](HTTPRequest * req, HTTPResponse * res){
    res->println("IndexRoute");
  });
 
  ResourceNode * notFoundRoute = new ResourceNode("", "", [](HTTPRequest * req, HTTPResponse * res){
    req->discardRequestBody();
    res->setStatusCode(404);

    res->println("Route Not Found");
  });
 
  
  secureServer->registerNode(indexRoute);
  secureServer->setDefaultNode(notFoundRoute);
    
  secureServer->start();
    
  if (secureServer->isRunning()) {
    Serial.println("Server ready.");
  }else{
    Serial.println("Server could not be started.");
  }
}
  
void loop() {
   
  secureServer->loop();  
  delay(10);
}

Testing the code

To test the code, first compile it and upload it to your device, using the Arduino IDE. Then, open the Arduino IDE serial monitor and wait for the ESP32 to generate the self signed certificate and to connect to the WiFi network.

When both procedures finish, the IP assigned to the ESP32 on the network should get printed. Copy that IP.

Then, open a web browser of your choice and type the following, changing #yourDeviceIp# by the IP you have just copied:

https://#yourDeviceIp#/

Your browser should complain about security issues related with the certificate of the server. This is expected since we are using a self signed certificate. Choose to proceed.

You should get an output similar to figure 1. As can be seen, when we access the index route, we receive the message we defined in our source code, with a success status code (200). This is the expected behavior, since we registered this route on our server.

Making a HTTPS request to the index route of a web server hosted by the ESP32.
Figure 1 – Accessing the index route.

Now, try to reach any other route, like the one shown below.

https://#yourDeviceIp#/someRoute

In this case, you should get an output similar to figure 2, since no other route was registered in our server. This means that the request was treated by the default route handler we registered, which we configured to return a generic “Route Not Found” message and a 404 code.

Sending a request to a non existing route of the HTTPS web server, which returns the default "Not Found" and 404 response.
Figure 2 – Sending request to a non existing route of the server.

Related Posts

Leave a Reply