Program Listing for File KPServerResponse.hpp

Return to documentation for file (src/KPServerResponse.hpp)

#pragma once

#include <ArduinoJson.h>
#include <StreamUtils.h>
#include <WiFi101.h>

#include <map>
#include <functional>

#include <KPFoundation.hpp>
#include <KPDataStoreInterface.hpp>

struct Response {
private:
    int status         = 200;
    bool headerPending = true;
    std::map<const char *, const char *> headers{
        {"Content-Type", "text/html; charset=UTF-8"},
        {"Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"},
        {"Access-Control-Allow-Origin", "*"},
        {"Connection", "Keep-Alive"},
        {"Content-Language", "en-us"}};

    std::function<void()> _notFound;

    void sendHeader() {
        client.printf("HTTP/1.1 %d %s\r\n", status, statusText(status));
        for (auto & p : headers) {
            client.printf("%s: %s\r\n", p.first, p.second);
        }

        client.print("\r\n");
        headerPending = false;
    }

    const char * statusText(int code) const {
        switch (code) {
        case 200:
            return "OK";
        default:
            return "Not Found";
        }
    }

public:
    WiFiClient & client;
    const size_t TCP_LIMIT;
    Response(WiFiClient & client, const size_t tcp_limit) : client(client), TCP_LIMIT(tcp_limit) {
        _notFound = [&]() {
            client.println("HTTP/1.1 404 Not Found");
            client.println("Connection: close");
            client.print("\r\n");
        };
    }

    void setHeader(const char * key, const char * value) {
        for (auto p : headers) {
            if (strcmpi(p.first, key) == 0) {
                headers[p.first] = value;  // update
                return;
            }
        }

        headers[key] = value;
    }

    template <typename T>
    size_t send(const T & data) {
        if (headerPending) {
            sendHeader();
        }

        return client.print(data);
    }

    template <size_t N>
    size_t send(const char (&data)[N]) {
        if (N > TCP_LIMIT) {
            println("Warning: data exeeds TCP limit. All or some of it may be lost.");
            println("Try to reduce the amount of data sent at once to be below ", TCP_LIMIT,
                    " bytes");
        }

        if (headerPending) {
            sendHeader();
        }

        return client.print(data);
    }

    size_t json(const JsonDocument & doc) {
        if (headerPending) {
            setHeader("Content-Type", "application/json");
            sendHeader();
        }

        WriteBufferingClient buffer(client, 64);
        return serializeJson(doc, buffer);
    }

    size_t sendFile(const char * filepath, KPDataStoreInterface & store) {
        if (headerPending) {
            setHeader("Transfer-Encoding", "chunked");
            sendHeader();
        }

        // Chunk encoding
        const size_t bufferSize = 1400;
        char buffer[bufferSize]{0};
        int charsRead = 0;
        size_t total  = 0;
        while ((charsRead = store.loadContentOfFile(filepath, buffer, bufferSize)) > 0) {
            client.printf("%X\r\n", charsRead);
            client.write(buffer, charsRead);
            client.flush();
            total += charsRead;
        }

        // Chunk terminator
        if (charsRead == 0) {
            client.print("0\r\n");
        }

        return total + 1;
    }

    void end() {
        if (headerPending) {
            sendHeader();
        }

        client.print("\r\n\r\n");
        client.stop();
    }

    void onNotFound(std::function<void()> callback) {
        _notFound = callback;
    }

    void notFound() {
        _notFound();
    }
};