1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
5 * @file BasicExample.ino
6 * @brief Minimal example demonstrating core DetWebServer features.
9 * - WiFi connection via init_wifi_physical() + wifi_ready() polling
10 * - Checking begin() return value for heap exhaustion
11 * - CORS headers via set_cors()
12 * - Exact route: GET /api/status → JSON with HTTP version field
13 * - Wildcard route: GET /files/* → echoes the requested path
14 * - POST route with body access
15 * - Custom 404 handler via on_not_found()
16 * - Query parameter access via http_get_query()
17 * - Request header access via http_get_header()
19 * The framework handles these automatically — no application code needed:
20 * - 400 Bad Request — RFC 7230 character violation in method/path/headers
21 * - 413 Payload Too Large — Content-Length exceeds BODY_BUF_SIZE
22 * - 414 URI Too Long — path exceeds MAX_PATH_LEN
23 * - 501 Not Implemented — Transfer-Encoding header present (chunked not supported)
25 * Flash to any ESP32 board. Open the Serial Monitor at 115200 baud to
26 * see the assigned IP address, then use curl or a browser to test routes.
28 * Example curl commands:
29 * curl http://<ip>/api/status
30 * curl http://<ip>/api/status?verbose=1
31 * curl -X POST http://<ip>/api/echo -d "hello world"
32 * curl http://<ip>/files/image.png
35#include "DeterministicESPAsyncWebServer.h"
36#include "network_drivers/physical.h"
39static const char *SSID = "YOUR_SSID";
40static const char *PASSWORD = "YOUR_PASSWORD";
44static int request_count = 0;
47// Returns a JSON object with a rolling request counter and the HTTP version
48// the client used. Demonstrates req->version and query param access.
49void handle_status(uint8_t slot_id, HttpReq *req)
53 const char *version_str;
67 // ?verbose=1 includes the path in the response
68 const char *verbose = http_get_query(req, "verbose");
71 if (verbose && strcmp(verbose, "1") == 0)
72 snprintf(body, sizeof(body), "{\"status\":\"ok\",\"count\":%d,\"http\":\"%s\",\"path\":\"%s\"}", request_count,
73 version_str, req->path);
75 snprintf(body, sizeof(body), "{\"status\":\"ok\",\"count\":%d,\"http\":\"%s\"}", request_count, version_str);
77 server.send(slot_id, 200, "application/json", body);
81// Echoes the request body back as plain text.
82// req->body is always null-terminated; body_len holds the byte count.
83void handle_echo(uint8_t slot_id, HttpReq *req)
85 server.send(slot_id, 200, "text/plain", (const char *)req->body);
88// GET /files/* (wildcard)
89// A placeholder for static file serving. req->path holds the full request
90// path, not just the suffix after the wildcard prefix.
91void handle_files(uint8_t slot_id, HttpReq *req)
93 // Demonstrate header lookup — check if the client prefers gzip
94 const char *accept_enc = http_get_header(req, "Accept-Encoding");
95 bool wants_gzip = accept_enc && strstr(accept_enc, "gzip") != nullptr;
97 char msg[MAX_PATH_LEN + 64];
98 snprintf(msg, sizeof(msg), "Requested: %s%s", req->path, wants_gzip ? " (gzip accepted)" : "");
99 server.send(slot_id, 200, "text/plain", msg);
102// Fallback for any unmatched route
103void handle_not_found(uint8_t slot_id, HttpReq *req)
105 char msg[MAX_PATH_LEN + 32];
106 snprintf(msg, sizeof(msg), "Not found: %s", req->path);
107 server.send(slot_id, 404, "text/plain", msg);
112 Serial.begin(115200);
114 init_wifi_physical(SSID, PASSWORD);
115 Serial.print("Connecting to WiFi");
116 while (!wifi_ready())
121 Serial.print("\nIP: ");
122 Serial.println(WiFi.localIP());
124 // Allow cross-origin requests from any origin (swap "*" for your
125 // frontend origin in production).
126 server.set_cors("*");
128 server.on("/api/status", HTTP_GET, handle_status);
129 server.on("/api/echo", HTTP_POST, handle_echo);
130 server.on("/files/*", HTTP_GET, handle_files);
131 server.on_not_found(handle_not_found);
133 int32_t result = server.begin(80);
136 // Negative return means we ran out of heap.
137 // abs(result) is how many bytes were needed.
138 Serial.printf("begin() failed — need %d more heap bytes\n", -result);
141 Serial.println("Server started on port 80");
146 // Drives the full pipeline each iteration:
147 // 1. Timeout sweep (force-closes idle connections)
148 // 2. Event queue drain (TCP connect/data/disconnect events)
149 // 3. Route dispatch for completed requests
150 // 4. Auto-sends 400 / 413 / 414 for parser error states