esp32_https_server icon indicating copy to clipboard operation
esp32_https_server copied to clipboard

TLS tickets and FreeRTOS workers

Open ikostoski opened this issue 6 years ago • 1 comments

Two new API functions added:

HTTPSServer->enableTLSTickets(uint32_t liftimeSeconds = 86400, bool useHardwareRNG = false) Which enables HTTPS server generating and accepting RFC5077 TLS tickets from clients for session negotiation vs. complete renegotiation for each TLS connection

HTTPServer->enableWorkers(uint8_t numWorkers = 2, size_t taskStackSize = HTTPS_CONN_TASK_STACK_SIZE, int taskPriority = HTTPS_CONN_TASK_PRIORITY); Which creates n-FreeRTOS tasks that will takeover processing of server's loop() method. More than one task will enable parallel processing of connections as ESP32 has two cores and/or if single resourceNode's handler function is busy doing some longer processing

ikostoski avatar Oct 10 '19 20:10 ikostoski

I now tried a while to compare the performance of the previous version of the server against this PR in various configurations. But using the following sketch and these scripts to measure the performance (to have more control over the number of clients and connection pool properties), I get really strange behavior when exceeding the 2 connections or workers, even on boards with an additional 4MB of RAM and even for HTTP.

main.cpp
#include <Arduino.h>

#define WIFI_SSID "..."
#define WIFI_PSK "..."

#define HTTPONLY 0
#define MAX_CONNECTIONS 2
#define WORKERS 2
#define TLSTICKETS 0

// Include certificate data (see note above)
#if (HTTPONLY!=1)
#include "cert.h"
#include "private_key.h"
#endif

// Binary data for the favicon
#include "favicon.h"

// We will use wifi
#include <WiFi.h>

#include <SPIFFS.h>

// Includes for the server
#if (HTTPONLY!=1)
#include <HTTPSServer.hpp>
#include <SSLCert.hpp>
#else
#include <HTTPServer.hpp>
#endif
#include <HTTPRequest.hpp>
#include <HTTPResponse.hpp>

// The HTTPS Server comes in a separate namespace. For easier use, include it here.
using namespace httpsserver;

// Create an SSL certificate object from the files included above
#if (HTTPONLY!=1)
SSLCert cert = SSLCert(
  example_crt_DER, example_crt_DER_len,
  example_key_DER, example_key_DER_len
);

// Create an SSL-enabled server that uses the certificate
// The contstructor takes some more parameters, but we go for default values here.
HTTPSServer server = HTTPSServer(&cert, 443, MAX_CONNECTIONS);
#else
HTTPServer server = HTTPServer(80, MAX_CONNECTIONS);
#endif

// Declare some handler functions for the various URLs on the server
// The signature is always the same for those functions. They get two parameters,
// which are pointers to the request data (read request body, headers, ...) and
// to the response data (write response, set status code, ...)
void handleRoot(HTTPRequest * req, HTTPResponse * res);
void handleCat(HTTPRequest * req, HTTPResponse * res);
void handleSmall(HTTPRequest * req, HTTPResponse * res);
void handleFavicon(HTTPRequest * req, HTTPResponse * res);
void handle404(HTTPRequest * req, HTTPResponse * res);

unsigned long lastReconnect = 0;

void setup() {
  // For logging
  Serial.begin(115200);

  if(!SPIFFS.begin()) {
    while(true) {
      Serial.println("SPIFFS failed.");
      delay(5000);
    }
  }

  // Connect to WiFi
  Serial.println("Setting up WiFi");
  WiFi.begin(WIFI_SSID, WIFI_PSK);
  while (WiFi.status() != WL_CONNECTED) {
    if (millis()-lastReconnect > 10000) {
      lastReconnect = millis();
      WiFi.disconnect();
      delay(500);
      Serial.print(":");
      WiFi.begin(WIFI_SSID, WIFI_PSK);
    } else {
      delay(500);
      Serial.print(".");
    }
  }
  Serial.print("Connected. IP=");
  Serial.println(WiFi.localIP());

  // For every resource available on the server, we need to create a ResourceNode
  // The ResourceNode links URL and HTTP method to a handler function
  ResourceNode * nodeRoot    = new ResourceNode("/", "GET", &handleRoot);
  ResourceNode * nodeCat     = new ResourceNode("/cats/*", "GET", &handleCat);
  ResourceNode * nodeSmall   = new ResourceNode("/small/*", "GET", &handleSmall);
  ResourceNode * nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
  ResourceNode * node404     = new ResourceNode("", "GET", &handle404);

  // Add the root node to the server
  server.registerNode(nodeRoot);
  server.registerNode(nodeCat);
  server.registerNode(nodeSmall);

  // Add the favicon
  server.registerNode(nodeFavicon);

  // Add the 404 not found node to the server.
  // The path is ignored for the default node.
  server.setDefaultNode(node404);

#if TLSTICKETS==1 && HTTPONLY==0
  server.enableTLSTickets();
#endif

#if WORKERS>0
  server.enableWorkers(WORKERS);
#endif

  Serial.println("Starting server...");
  server.start();
  if (server.isRunning()) {
    Serial.println("Server ready.");
  }
}

void loop() {
  // This call will let the server do its work
#if WORKERS==0
  server.loop();
#else
  delay(1000);
#endif

  if (WiFi.status() != WL_CONNECTED) {
    if (millis()-lastReconnect > 10000) {
      Serial.println("Connection lost, reconnecting...");
      WiFi.disconnect();
      delay(500);
      WiFi.begin(WIFI_SSID, WIFI_PSK);
      lastReconnect = millis();
    }
  }
}

void handleRoot(HTTPRequest * req, HTTPResponse * res) {
  // Status code is 200 OK by default.
  // We want to deliver a simple HTML page, so we send a corresponding content type:
  res->setHeader("Content-Type", "text/html");

  // The response implements the Print interface, so you can use it just like
  // you would write to Serial etc.
  res->println("<!DOCTYPE html>");
  res->println("<html>");
  res->println("<head><title>Hello World!</title></head>");
  res->println("<body>");
  res->println("<h1>Hello World!</h1>");
  res->print("<p>Your server is running for ");
  // A bit of dynamic data: Show the uptime
  res->print((int)(millis()/1000), DEC);
  res->println(" seconds.</p>");
  res->println("</body>");
  res->println("</html>");
}

void handleSmall(HTTPRequest * req, HTTPResponse * res) {
  /* The files in that directory are all 128 byte sized text files */
  std::string smallfile = "/smallfiles/" + req->getParams()->getUrlParameter(0);
  if (SPIFFS.exists(smallfile.c_str())) {
    File f = SPIFFS.open(smallfile.c_str(), "r");
    if (f) {
      res->setHeader("Content-Length", intToString(f.size()));
      res->setHeader("Content-Type", "text/plain");
      uint8_t buffer[512];
      size_t buflen;
      size_t offset = 0;
      while((buflen = f.read(buffer, sizeof(buffer)))>0) {
        offset = 0;
        while (buflen > offset) {
          offset += res->write(buffer+offset, buflen-offset);
        }
      }
      f.close();
    } else {
      res->setStatusCode(500);
      res->setStatusText("Internal server error");
      res->println("Error reading a small file.");
    }
  } else {
    res->setStatusCode(404);
    res->setStatusText("Not found");
    res->println("This file does not exist on this server.");
  }
}

void handleCat(HTTPRequest * req, HTTPResponse * res) {
  std::string catfile = "/cats/" + req->getParams()->getUrlParameter(0);
  if (SPIFFS.exists(catfile.c_str())) {
    File f = SPIFFS.open(catfile.c_str(), "r");
    if (f) {
      res->setHeader("Content-Length", intToString(f.size()));
      res->setHeader("Content-Type", "image/jpeg");
      uint8_t buffer[512];
      size_t buflen;
      size_t offset = 0;
      while((buflen = f.read(buffer, sizeof(buffer)))>0) {
        offset = 0;
        while (buflen > offset) {
          offset += res->write(buffer+offset, buflen-offset);
        }
      }
      f.close();
    } else {
      res->setStatusCode(500);
      res->setStatusText("Internal server error");
      res->println("Cat is currently catting around.");
    }
  } else {
    res->setStatusCode(404);
    res->setStatusText("Not found");
    res->println("This cat does not exist on this server.");
  }
}

void handleFavicon(HTTPRequest * req, HTTPResponse * res) {
	// Set Content-Type
	res->setHeader("Content-Type", "image/vnd.microsoft.icon");
	// Write data from header file
	res->write(FAVICON_DATA, FAVICON_LENGTH);
}

void handle404(HTTPRequest * req, HTTPResponse * res) {
  // Discard request body, if we received any
  // We do this, as this is the default node and may also server POST/PUT requests
  req->discardRequestBody();

  // Set the response status
  res->setStatusCode(404);
  res->setStatusText("Not Found");

  // Set content type of the response
  res->setHeader("Content-Type", "text/html");

  // Write a tiny HTTP page
  res->println("<!DOCTYPE html>");
  res->println("<html>");
  res->println("<head><title>Not Found</title></head>");
  res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>");
  res->println("</html>");
}

This sketch runs without problems on the current master, for arbitrary numbers of concurrent connections (as long as it doesn't hit the RAM boundary). But for more connections, it seems to get stuck before sending out packets (e.g. the server log says the connection has been finished, but the response in Wireshark arrives 7 seconds later, it doesn't accept connections directly) etc. And without enabling workers (which would be the backward-compatibility scenario), some resources aren't delivered even for a single client with a single connection. That would be a problem for everyone updating the library.

Do you want to do further debugging on this or should I try to fix these issues and add to this PR?

fhessel avatar Nov 28 '19 11:11 fhessel