1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
6 * @brief Expert example demonstrating connection-pool inspection, request profiling, rate-limiting, and memory-safe
10 * 1. Direct low-level query of the Layer 4 connection pool (conn_pool[MAX_CONNS])
11 * to monitor TCP states, activity timestamps, and ring buffer fill-levels.
12 * 2. Token Bucket Rate Limiting: zero-allocation, time-based threshold checks
13 * returning "429 Too Many Requests".
14 * 3. Execution Profiling: measuring route execution times in microseconds.
15 * 4. Dynamic Stack-Allocated Response Templating: generating format-sensitive
16 * error pages (JSON vs. Plaintext) in the 404 handler based on request headers.
17 * 5. Real-time stack watermarking (uxTaskGetStackHighWaterMark) to audit stack headroom.
19 * To run this example:
20 * - Configure SSID/PASSWORD, and upload to an ESP32.
21 * - Trigger high-frequency requests to see the rate limiter (429 status) in action.
22 * - Monitor connection state diagnostics printed on the Serial interface.
25#include "DeterministicESPAsyncWebServer.h"
26#include "network_drivers/physical.h"
27#include "network_drivers/transport.h" // Needed to access conn_pool and ConnState
30static const char *SSID = "YOUR_SSID";
31static const char *PASSWORD = "YOUR_PASSWORD";
35// Uptime tracker and execution counters
36static unsigned long total_routed_requests = 0;
37static unsigned long total_rate_limited = 0;
39// --- Token Bucket Rate Limiter State ---
40static float bucket_tokens = 5.0f; // Max burst size
41static const float bucket_capacity = 5.0f;
42static const float refill_rate_per_sec = 2.0f; // Tokens added per second
43static unsigned long last_refill_time_ms = 0;
46 * @brief Zero-heap Token Bucket Rate Limiter
47 * Refills tokens based on time elapsed and consumes one token per request.
48 * @return true if allowed, false if rate-limited (bucket empty).
50bool acquire_rate_limit_token()
52 unsigned long now = millis();
53 unsigned long elapsed_ms = now - last_refill_time_ms;
54 last_refill_time_ms = now;
57 bucket_tokens += (elapsed_ms / 1000.0f) * refill_rate_per_sec;
58 if (bucket_tokens > bucket_capacity)
60 bucket_tokens = bucket_capacity;
63 // Check if token is available
64 if (bucket_tokens >= 1.0f)
66 bucket_tokens -= 1.0f;
72// --- Connection Pool Monitoring ---
75 * @brief Helper function to log current TCP connection pool statistics to Serial.
76 * Iterates through the static Layer 4 pool to analyze resource usage.
78void print_connection_pool_stats()
80 Serial.println("\n--- Connection Pool Snapshot ---");
81 for (int i = 0; i < MAX_CONNS; i++)
83 TcpConn *conn = &conn_pool[i];
84 const char *state_str = "UNKNOWN";
94 state_str = "CLOSING";
99 if (conn->state == CONN_ACTIVE)
101 // Calculate fill occupancy of the circular ring buffer
102 rx_unread = (conn->rx_head >= conn->rx_tail) ? (conn->rx_head - conn->rx_tail)
103 : (RX_BUF_SIZE - (conn->rx_tail - conn->rx_head));
106 Serial.printf("Slot [%d]: State=%-7s | UnreadBytes=%4zu | LastActivity=%6lu ms ago | PCB_Addr=%p\n", i,
107 state_str, rx_unread, (conn->state == CONN_ACTIVE) ? (millis() - conn->last_activity_ms) : 0,
110 Serial.println("---------------------------------");
113// --- Route Handlers with microsecond profiling ---
116 * @brief GET /api/diagnostics
117 * Returns detailed telemetry. Profiler measures how fast this handler runs.
119void handle_diagnostics(uint8_t slot_id, HttpReq *req)
121 unsigned long start_us = micros();
122 total_routed_requests++;
124 // Apply Rate Limiting first
125 if (!acquire_rate_limit_token())
127 total_rate_limited++;
128 server.send(slot_id, 429, "application/json", "{\"error\":\"Too Many Requests. Rate limit exceeded.\"}");
132 // Audit local task stack headroom
133 UBaseType_t stack_high_water = uxTaskGetStackHighWaterMark(NULL);
135 char response_buf[384];
136 snprintf(response_buf, sizeof(response_buf),
139 "\"routed_requests\":%lu,"
140 "\"rate_limited_count\":%lu,"
141 "\"free_heap_bytes\":%u,"
142 "\"task_stack_headroom_words\":%u,"
143 "\"bucket_tokens_left\":%.2f"
145 millis(), total_routed_requests, total_rate_limited, ESP.getFreeHeap(), (unsigned int)stack_high_water,
148 unsigned long duration_us = micros() - start_us;
150 // We send the reply first.
151 server.send(slot_id, 200, "application/json", response_buf);
153 Serial.printf("[Profile] Route GET %s handled in %lu us\n", req->path, duration_us);
157 * @brief GET /api/compute
158 * Demonstrates a heavier compute-bound route with timing checks.
160void handle_compute(uint8_t slot_id, HttpReq *req)
162 unsigned long start_us = micros();
163 total_routed_requests++;
165 if (!acquire_rate_limit_token())
167 total_rate_limited++;
168 server.send(slot_id, 429, "application/json", "{\"error\":\"Too Many Requests\"}");
172 // Perform a mock deterministic heavy calculation (e.g. integer math)
173 volatile uint32_t val = 12345;
174 for (int i = 0; i < 500; i++)
176 val = (val ^ 37821) * 31;
179 char response_buf[64];
180 snprintf(response_buf, sizeof(response_buf), "{\"result\":%u}", val);
182 server.send(slot_id, 200, "application/json", response_buf);
184 unsigned long duration_us = micros() - start_us;
185 Serial.printf("[Profile] Route GET %s (heavy compute) handled in %lu us\n", req->path, duration_us);
189 * @brief Dynamic template fallback handler
190 * Chooses response type based on 'Accept' or 'Content-Type' headers.
192void handle_expert_not_found(uint8_t slot_id, HttpReq *req)
194 unsigned long start_us = micros();
195 total_routed_requests++;
197 const char *accept_header = http_get_header(req, "Accept");
198 bool wants_json = (accept_header && strstr(accept_header, "application/json") != nullptr);
203 snprintf(error_buf, sizeof(error_buf), "{\"error\":\"not_found\",\"requested_path\":\"%s\",\"uptime\":%lu}",
204 req->path, millis());
205 server.send(slot_id, 404, "application/json", error_buf);
209 snprintf(error_buf, sizeof(error_buf),
210 "--- Error 404 ---\nPath: %s\nUptime: %lu ms\nESP32 High-Reliability Node", req->path, millis());
211 server.send(slot_id, 404, "text/plain", error_buf);
214 unsigned long duration_us = micros() - start_us;
215 Serial.printf("[Profile] Fallback 404 matched in %lu us (JSON: %d)\n", duration_us, wants_json);
220 Serial.begin(115200);
222 Serial.println("\n--- DetWebServer Expert Performance Example ---");
224 init_wifi_physical(SSID, PASSWORD);
225 while (!wifi_ready())
230 Serial.println("\nWiFi online!");
231 Serial.print("Local IP Address: ");
232 Serial.println(WiFi.localIP());
234 last_refill_time_ms = millis();
236 // Map optimized endpoints
237 server.on("/api/diagnostics", HTTP_GET, handle_diagnostics);
238 server.on("/api/compute", HTTP_GET, handle_compute);
239 server.on_not_found(handle_expert_not_found);
241 if (server.begin(80))
243 Serial.println("Telemetry server started on port 80");
251 // Periodically display active pool diagnostics every 5 seconds
252 static unsigned long last_snapshot = 0;
253 if (millis() - last_snapshot >= 5000)
255 last_snapshot = millis();
256 print_connection_pool_stats();