PsychicHttp

repository·master·Indexed 41 days ago

https://github.com/hoeken/psychichttp

An asynchronous webserver library for ESP32 supporting both the Arduino framework and native ESP-IDF. It provides a high-level API for HTTP, WebSockets, SSE, and file serving. Key features include HTTPS/SSL support, Basic and Digest authentication, multipart file uploads via PsychicUploadHandler, and custom request interception using PsychicWebHandler for features like captive portals.

Tokens
18.1K
Snippets
51
Records
66
Agent score
52%

What's inside PsychicHttp

  1. PsychicHttp features overview

    master

    PsychicHttp is an asynchronous webserver for ESP32 with the following capabilities:

    • HTTP Methods: Full support for GET/POST, including parameters, headers, cookies, and session data.
    • Security: HTTPS/SSL support and Authentication (Basic and Digest modes).
    • File Handling: Static fileserving (SPIFFS, LittleFS, etc.), chunked response serving for large files, and file uploads (Basic + Multipart).
    • Real-time Communication: WebSocket support (with onOpen, onFrame, onClose callbacks) and EventSource/SSE support.
    • Advanced Routing: Request filters (Client vs AP mode) and TemplatePrinter for dynamic runtime variables.
  2. Understand the PsychicHttp Request Lifecycle

    master

    When a TCP connection is received, PsychicHttp wraps the HTTP request in a PsychicRequest object and the TCP connection in a PsychicConnection object. The server then follows these steps:

    1. Endpoint Matching: The server iterates through all PsychicEndpoints to find one matching the URL and HTTP method.
    2. Handler Verification: For a matching handler, the server calls handler->filter(), handler->canHandle(), and handler->needsAuthentication().
    3. Processing: If verified, handler->handleRequest() is executed.
    4. Fallback: If no endpoint matches, the server checks global handlers (in order of attachment) that pass the filter/canHandle/authentication checks. If still no match, server.defaultEndpoint is called.
    5. Response & Cleanup: The handler is responsible for sending the response. Once sent, the client is closed and memory is freed (except for persistent connections like WebSockets or EventSource).

    Note on Handlers:

    • PsychicHandler can be attached to specific endpoints or as global handlers.
    • Filter settings allow controlling when a handler applies based on request method, URL, host/port, or client IP (local/remote).
    • Two specific filter callbacks are available: ON_AP_FILTER (for AP interface requests) and ON_STA_FILTER (for STA interface requests).
  3. Use TemplatePrinter for Dynamic Content

    master

    The TemplatePrinter (requires #include <TemplatePrinter.h>) is a template engine that works with any Print object (e.g., PsychicStreamResponse, Serial, File). It allows replacing placeholders in a string with dynamic data.

    Parameter Rules:

    • Delimiter: Default is % (e.g., %MY_PARAM%).
    • Content: Only a-z, A-Z, 0-9, and _ allowed.
    • Length: Max 63 characters.

    Usage Pattern: You provide a templateHandler function that is called whenever a parameter is detected. The handler must return true if it handled the parameter, or false if the detected text is not a valid parameter (in which case the text is printed literally).

    Example: Templating a response with dynamic heap info:

    server.on("/template", [](PsychicRequest *request) {
      PsychicStreamResponse response(request, "text/plain");
      response.beginSend();
    
      TemplatePrinter printer(response, [](Print &output, const char *param) {
        if(strcmp(param, "FREE_HEAP") == 0) {
          output.print((double)ESP.getFreeHeap() / 1024.0, 2);
          return true;
        }
        return false;
      });
    
      printer.println("Free heap: %FREE_HEAP% Kb");
      printer.flush();
    
      return response.endSend();
    });
    //  Function to handle parameter requests.
    
    bool templateHandler(Print &output, const char *param){
    
      if(strcmp(param, "FREE_HEAP") == 0){
        output.print((double)ESP.getFreeHeap() / 1024.0, 2);
    
      }else if(strcmp(param, "MIN_FREE_HEAP") == 0){
        output.print((double)ESP.getMinFreeHeap() / 1024.0, 2);
    
      }else if(strcmp(param, "MAX_ALLOC_HEAP") == 0){
        output.print((double)ESP.getMaxAllocHeap() / 1024.0, 2);
    
      }else if(strcmp(param, "HEAP_SIZE") == 0){
        output.print((double)ESP.getHeapSize() / 1024.0, 2);
      }else{
        return false;
      }
      output.print("Kb");
      return true;
    }
    
    //  Example serving a request
    server.on("/template", [](PsychicRequest *request) {
      PsychicStreamResponse response(request, "text/plain");
    
      response.beginSend();
    
      TemplatePrinter printer(response, templateHandler);
    
      printer.println("My ESP has %FREE_HEAP% left. Its lifetime minimum heap is %MIN_FREE_HEAP%.");
      printer.println("The maximum allocation size is %MAX_ALLOC_HEAP%, and its total size is %HEAP_SIZE%.");
      printer.println("This is an unhandled parameter: %UNHANDLED_PARAM% and this is an invalid param %INVALID PARAM%.");
      printer.println("This line finished with %UNFIN");
      printer.flush();
    
      return response.endSend();
    });
  4. PsychicHttp core principles and constraints

    master

    When developing with PsychicHttp, keep the following operational principles in mind:

    • Asynchronous Execution: The server runs in its own FreeRTOS thread and does not run on the main loop thread.
    • Non-blocking Callbacks: Do not use yield(), delay(), or any function that uses them inside server callbacks.
    • Single Response: You cannot send more than one response to a single request.
    • Resource Management: The server automatically handles closing connections and freeing resources.
  5. Create and Setup a PsychicHttp Server

    master

    To set up a basic server, include PsychicHttp.h, instantiate PsychicHttpServer, register your endpoints/handlers, and finally call server.begin().

    #include <PsychicHttp.h>
    PsychicHttpServer server;
    
    void setup()
    {
       // Optional: low level setup via server.config (ESP-IDF httpd_config struct)
       
       // Connect to WiFi
    
       // Attach endpoints and handlers
       server.on("/url", HTTP_GET, request_callback);
       server.serveStatic("/", LittleFS, "/www/");
    
       // Must be called after all registrations
       server.begin();
    }
    #include <PsychicHttp.h>
    PsychicHttpServer server;
    
    void setup()
    {
       // Optional: low level setup server config stuff here.
       // server.config is an ESP-IDF httpd_config struct
       // see: https://docs.espressif.com/projects/esp-idf/en/v4.4.6/esp32/api-reference/protocols/esp_http_server.html#_CPPv412httpd_config
    
       // connect to wifi
    
       // call server methods to attach endpoints and handlers
       server.on(...);
       server.serveStatic(...);
       server.addHandler(...);
    
       // must be called after all server.on() registrations
       server.begin();
    }
  6. Implement OTA (Over The Air) updates with PsychicUploadHandler

    master

    You can implement OTA updates for both firmware (code) and data (LittleFS) using the PsychicUploadHandler in PsychicHttp. This implementation allows users to upload .bin files via a web interface to update the ESP32.

    Requirements

    • Firmware Updates: Requires a compiled .bin file (generated via Sketch -> Export bin in Arduino IDE).
    • Data Updates: Requires a LittleFS .bin file (generated using the arduino-esp32fs-plugin).
    • Manual Restart: The example implementation does not automatically restart the ESP32 after an upload; the user must trigger a manual restart via the web interface to apply changes.

    Workflow

    1. The user selects a file (e.g., code.bin or littlefs.bin) through the web UI.
    2. PsychicUploadHandler processes the multipart upload.
    3. Upon successful upload, the handler triggers the onUpload callback where the update logic (e.g., Update.begin()) is executed.
    4. After the update is verified, the user can trigger a system restart.
  7. Enable HTTPS / SSL Support

    master

    PsychicHttp supports HTTPS/SSL on both Arduino and native ESP-IDF. To use it, include PsychicHttpsServer.h and use PsychicHttpsServer instead of PsychicHttpServer.

    Setup:

    #include <PsychicHttp.h>
    #include <PsychicHttpsServer.h>
    PsychicHttpsServer server;
    server.setCertificate(server_cert, server_key);

    Generating Certificates: You can generate self-signed certificates using OpenSSL:

    openssl req -x509 -newkey rsa -4096 -nodes -keyout server.key -out server.crt -sha256 -days 365

    Conditional Compilation: Use the PSY_ENABLE_SSL macro (defined when PsychicHttpsServer.h is included) to write code that works with both HTTP and HTTPS servers:

    #ifdef PSY_ENABLE_SSL
      PsychicHttpsServer server;
    #else
      PsychicHttpServer server;
    #endif

    Limitations:

    • Due to memory constraints, it can handle approximately 2 concurrent SSL connections.
    • Each SSL connection consumes ~45KB of RAM.
    • Handshakes may be slow (~1500ms).
  8. How to write cross-platform handlers for Arduino and ESP-IDF

    master

    Because the return types of getters differ between Arduino (which uses String) and native ESP-IDF (which uses const char*), you should use the *CStr() helper methods to write code that compiles on both platforms. These methods always return const char*.

    // These work identically on both Arduino and ESP-IDF native:
    request->uriCStr()              // instead of request->uri()
    request->bodyCStr()             // instead of request->body()
    request->headerCStr(name)      // instead of request->header(name)
    request->pathCStr()             // instead of request->path()
    request->queryCStr()            // instead of request->query()
    request->methodStrCStr()       // instead of request->methodStr()
    request->getFilenameCStr()     // instead of request->getFilename()
    request->getSessionKeyCStr(key) // instead of request->getSessionKey(key)
    endpoint->uriCStr()            // instead of endpoint->uri()
    param->nameCStr()              // instead of param->name()
    param->valueCStr()             // instead of param->value()
  9. Configure Wi-Fi credentials via secrets.h

    master

    All example and benchmark projects use a secrets.h file for Wi-Fi credentials. This file should be created locally and never committed to git.

    • For standalone examples: Create src/secrets.h from the provided secrets.h.example template.
    • For PlatformIO root builds: Create secrets.h at the repository root. The build flags will include the root directory automatically.
    # standalone example (e.g. examples/pio-arduino)
    mv examples/pio-arduino/src/secrets.h.example examples/pio-arduino/src/secrets.h
    # then edit secrets.h and set WIFI_SSID / WIFI_PASS
  10. Install PsychicHttp in PlatformIO (native ESP-IDF framework)

    master

    PsychicHttp supports native ESP-IDF projects without requiring the Arduino component. Add the library to lib_deps and ensure the following configuration is set in your sdkconfig.defaults:

    1. Enable WebSocket support: CONFIG_HTTPD_WS_SUPPORT=y
    2. Ensure CONFIG_MBEDTLS_ROM_MD5 is not set.

    If you are using PsychicHttpsServer, you must also enable: CONFIG_ESP_HTTPS_SERVER_ENABLE=y.

    [env:myboard]
    platform = espressif32
    board = ...
    framework = espidf
    lib_deps = hoeken/PsychicHttp

    In sdkconfig.defaults

    CONFIG_HTTPD_WS_SUPPORT=y

    CONFIG_MBEDTLS_ROM_MD5 is not set

    Add this if you are using PsychicHttpsServer:

    CONFIG_ESP_HTTPS_SERVER_ENABLE=y