DeterministicESPAsyncWebServer 1.2.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
DetWebServerConfig.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file DetWebServerConfig.h
6 * @brief User-facing configuration for DeterministicESPAsyncWebServer.
7 *
8 * **Compile-time sizing constants**
9 * These govern static array dimensions and must be set before the first
10 * library header is included. Define any of them in your sketch or in a
11 * build flag before including this file to override the defaults:
12 * @code
13 * // platformio.ini
14 * build_flags = -DMAX_CONNS=8 -DBODY_BUF_SIZE=512
15 * @endcode
16 *
17 * **Runtime parameters — flash or RAM, your choice**
18 * `WebServerConfig` holds values that can be changed without a rebuild.
19 * On ESP32, `PROGMEM` is a no-op (const data lands in DROM automatically).
20 * On AVR it places data in flash and requires `pgm_read_*` accessors — this
21 * library targets ESP32 only, so both forms read identically via pointer:
22 * @code
23 * // Flash (PROGMEM, no RAM cost at runtime):
24 * const WebServerConfig my_cfg PROGMEM = { .conn_timeout_ms = 10000 };
25 *
26 * // RAM (can be changed at runtime):
27 * WebServerConfig my_cfg = { .conn_timeout_ms = 10000 };
28 *
29 * server.begin(80, &my_cfg);
30 * @endcode
31 * Pass `nullptr` (or omit the argument) to use `DET_DEFAULT_CONFIG`.
32 */
33
34#ifndef DETERMINISTICESPASYNCWEBSERVER_CONFIG_H
35#define DETERMINISTICESPASYNCWEBSERVER_CONFIG_H
36
37#include <stdint.h>
38
39// ---------------------------------------------------------------------------
40// Compile-time capacity constants (affect static array sizes)
41// ---------------------------------------------------------------------------
42
43/** @brief Maximum simultaneous TCP connections. */
44#ifndef MAX_CONNS
45#define MAX_CONNS 4
46#endif
47
48/** @brief Ring-buffer capacity in bytes per connection slot. */
49#ifndef RX_BUF_SIZE
50#define RX_BUF_SIZE 1024
51#endif
52
53/**
54 * @brief Compile-time default for connection idle timeout in milliseconds.
55 *
56 * The actual runtime value is stored in `WebServerConfig::conn_timeout_ms`
57 * and loaded into `DeterministicAsyncTCP::conn_timeout_ms` by init().
58 */
59#ifndef CONN_TIMEOUT_MS
60#define CONN_TIMEOUT_MS 5000
61#endif
62
63/** @brief Maximum HTTP headers stored per request. */
64#ifndef MAX_HEADERS
65#define MAX_HEADERS 8
66#endif
67
68/** @brief Maximum URL path length (including leading `/`). */
69#ifndef MAX_PATH_LEN
70#define MAX_PATH_LEN 64
71#endif
72
73/** @brief Maximum header field-name length (e.g. `"Content-Type"`). */
74#ifndef MAX_KEY_LEN
75#define MAX_KEY_LEN 24
76#endif
77
78/** @brief Maximum header field-value length. */
79#ifndef MAX_VAL_LEN
80#define MAX_VAL_LEN 48
81#endif
82
83/** @brief Maximum raw query-string length (everything after `?`). */
84#ifndef MAX_QUERY_LEN
85#define MAX_QUERY_LEN 128
86#endif
87
88/** @brief Maximum number of parsed query-string parameters. */
89#ifndef MAX_QUERY_PARAMS
90#define MAX_QUERY_PARAMS 8
91#endif
92
93/** @brief Maximum query-parameter key length. */
94#ifndef QUERY_KEY_LEN
95#define QUERY_KEY_LEN 24
96#endif
97
98/** @brief Maximum query-parameter value length. */
99#ifndef QUERY_VAL_LEN
100#define QUERY_VAL_LEN 48
101#endif
102
103/**
104 * @brief Maximum request body bytes stored in `HttpReq::body`.
105 *
106 * Bodies larger than this trigger a 413 Payload Too Large response —
107 * the parser detects the overflow via `Content-Length` before any body
108 * bytes arrive, so no data is read or stored for oversized requests.
109 */
110#ifndef BODY_BUF_SIZE
111#define BODY_BUF_SIZE 256
112#endif
113
114/** @brief Maximum simultaneously registered routes. */
115#ifndef MAX_ROUTES
116#define MAX_ROUTES 16
117#endif
118
119// ---------------------------------------------------------------------------
120// WebSocket sizing constants
121// ---------------------------------------------------------------------------
122
123/**
124 * @brief Maximum simultaneous WebSocket connections.
125 *
126 * Each connection occupies one TCP slot from MAX_CONNS and one entry in
127 * ws_pool[]. MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS.
128 */
129#ifndef MAX_WS_CONNS
130#define MAX_WS_CONNS 2
131#endif
132
133/**
134 * @brief Maximum WebSocket frame payload in bytes.
135 *
136 * Frames larger than this are rejected with Close code 1009 (Message Too Big).
137 * Fragmented messages are not supported; each message must fit in one frame.
138 */
139#ifndef WS_FRAME_SIZE
140#define WS_FRAME_SIZE 512
141#endif
142
143// ---------------------------------------------------------------------------
144// Server-Sent Events sizing constants
145// ---------------------------------------------------------------------------
146
147/**
148 * @brief Maximum simultaneous SSE connections.
149 *
150 * Each connection occupies one TCP slot from MAX_CONNS and one entry in
151 * sse_pool[]. MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS.
152 */
153#ifndef MAX_SSE_CONNS
154#define MAX_SSE_CONNS 2
155#endif
156
157/**
158 * @brief Output buffer size in bytes for a single SSE event.
159 *
160 * An event larger than this is silently truncated. The buffer holds the
161 * formatted `data: ...\n\n` line before it is handed to tcp_write().
162 */
163#ifndef SSE_BUF_SIZE
164#define SSE_BUF_SIZE 256
165#endif
166
167// ---------------------------------------------------------------------------
168// Static file serving sizing constants
169// ---------------------------------------------------------------------------
170
171/**
172 * @brief Bytes read from the filesystem and passed to tcp_write() per loop().
173 *
174 * Smaller values reduce peak stack use; larger values improve throughput.
175 * Must be <= RX_BUF_SIZE to avoid stalling the TCP send window.
176 */
177#ifndef FILE_CHUNK_SIZE
178#define FILE_CHUNK_SIZE 512
179#endif
180
181// ---------------------------------------------------------------------------
182// Basic Auth sizing constants
183// ---------------------------------------------------------------------------
184
185/**
186 * @brief Maximum username or password length for HTTP Basic Authentication.
187 *
188 * Both username and password must fit in this many bytes including the
189 * null terminator. Longer credentials are silently rejected with 401.
190 */
191#ifndef MAX_AUTH_LEN
192#define MAX_AUTH_LEN 32
193#endif
194
195// ---------------------------------------------------------------------------
196// Multipart form-data sizing constants
197// ---------------------------------------------------------------------------
198
199/**
200 * @brief Maximum simultaneously parsed multipart parts per request.
201 *
202 * Parts beyond this limit are silently ignored. A typical upload form
203 * has 1-4 fields; increase this for forms with more.
204 */
205#ifndef MAX_MULTIPART_PARTS
206#define MAX_MULTIPART_PARTS 4
207#endif
208
209/**
210 * @brief Maximum MIME boundary length (RFC 2046 allows up to 70 characters).
211 */
212#ifndef MAX_BOUNDARY_LEN
213#define MAX_BOUNDARY_LEN 72
214#endif
215
216// ---------------------------------------------------------------------------
217// Event queue depth
218// ---------------------------------------------------------------------------
219
220/**
221 * @brief Depth of the FreeRTOS event queue shared between lwIP callbacks and
222 * the main-loop task.
223 *
224 * Each slot holds one TcpEvt (8 bytes). The queue is the only heap
225 * allocation the library makes at begin() time:
226 *
227 * heap = sizeof(StaticQueue_t) + EVT_QUEUE_DEPTH * sizeof(TcpEvt)
228 *
229 * Must be large enough to absorb a burst of MAX_CONNS * 4 events without
230 * blocking the lwIP thread. DeterministicAsyncTCP::heap_needed() returns
231 * the exact byte count; call DetWebServer::heap_available() before begin()
232 * to verify a contiguous block exists.
233 */
234#ifndef EVT_QUEUE_DEPTH
235#define EVT_QUEUE_DEPTH 16
236#endif
237
238// ---------------------------------------------------------------------------
239// Internal response buffer sizing constants
240// ---------------------------------------------------------------------------
241
242/**
243 * @brief Stack buffer for HTTP response header lines in send() / send_empty() /
244 * send_unauth() / serve_file().
245 *
246 * Must be large enough to hold the status line, Content-Type, Content-Length,
247 * Connection, and any CORS headers. The CORS block alone can reach
248 * CORS_HDR_BUF_SIZE bytes, so this value should be at least
249 * CORS_HDR_BUF_SIZE + 96.
250 */
251#ifndef RESP_HDR_BUF_SIZE
252#define RESP_HDR_BUF_SIZE 512
253#endif
254
255/**
256 * @brief Stack buffer for the HTTP 101 Switching Protocols response sent during
257 * the WebSocket handshake.
258 *
259 * Must hold: status line + Upgrade + Connection + Sec-WebSocket-Accept (28
260 * base64 chars) + CRLF pairs. Minimum is ~120 bytes; default leaves margin.
261 */
262#ifndef WS_HDR_BUF_SIZE
263#define WS_HDR_BUF_SIZE 256
264#endif
265
266/**
267 * @brief Size of the pre-built CORS header block stored in DetWebServer.
268 *
269 * Built once by set_cors() and injected into every response. Must hold
270 * Access-Control-Allow-Origin, Access-Control-Allow-Methods, and
271 * Access-Control-Allow-Headers lines for the configured origin.
272 */
273#ifndef CORS_HDR_BUF_SIZE
274#define CORS_HDR_BUF_SIZE 192
275#endif
276
277// ---------------------------------------------------------------------------
278// Feature flags
279// ---------------------------------------------------------------------------
280// Set any of these to 0 in your sketch BEFORE including this library to strip
281// the feature from the build entirely (no code, no RAM, no flash cost).
282//
283// #define DETWS_ENABLE_WEBSOCKET 0
284// #include <DeterministicESPAsyncWebServer.h>
285
286/** @brief WebSocket support (RFC 6455 framing + SHA-1/base64 handshake). */
287#ifndef DETWS_ENABLE_WEBSOCKET
288#define DETWS_ENABLE_WEBSOCKET 1
289#endif
290
291/** @brief Server-Sent Events push support. */
292#ifndef DETWS_ENABLE_SSE
293#define DETWS_ENABLE_SSE 1
294#endif
295
296/** @brief multipart/form-data body parser. */
297#ifndef DETWS_ENABLE_MULTIPART
298#define DETWS_ENABLE_MULTIPART 1
299#endif
300
301/** @brief Static file serving via Arduino FS (LittleFS, SPIFFS, SD). */
302#ifndef DETWS_ENABLE_FILE_SERVING
303#define DETWS_ENABLE_FILE_SERVING 1
304#endif
305
306/** @brief HTTP Basic Authentication per-route. */
307#ifndef DETWS_ENABLE_AUTH
308#define DETWS_ENABLE_AUTH 1
309#endif
310
311/**
312 * @brief Expose a diagnostic JSON endpoint via server.diag().
313 *
314 * Disabled by default — enabling it exposes compile-time configuration
315 * (buffer sizes, feature flags) which could aid an attacker. Only
316 * enable in development or behind an authenticated route.
317 *
318 * When enabled, DETWS_DIAG_JSON is a compile-time string constant you can
319 * serve from any route handler:
320 * @code
321 * server.on("/diag", HTTP_GET, [](uint8_t id, HttpReq *) {
322 * server.diag(id); // convenience wrapper
323 * // or:
324 * server.send(id, 200, "application/json", DETWS_DIAG_JSON);
325 * });
326 * @endcode
327 */
328#ifndef DETWS_ENABLE_DIAG
329#define DETWS_ENABLE_DIAG 0
330#endif
331
332// ---------------------------------------------------------------------------
333// Runtime configuration struct
334// ---------------------------------------------------------------------------
335
336/**
337 * @brief Runtime-tunable server parameters.
338 *
339 * Can be declared as `const PROGMEM` (flash) or as a mutable variable (RAM).
340 * Pass a pointer to DetWebServer::begin() or DeterministicAsyncTCP::init().
341 */
343{
344 /** Milliseconds of inactivity before a connection is force-closed. */
346};
347
348/** @brief Built-in defaults used when no config is supplied to begin(). */
349static const WebServerConfig DET_DEFAULT_CONFIG = {5000u};
350
351// ---------------------------------------------------------------------------
352// Diagnostic JSON string (only defined when DETWS_ENABLE_DIAG == 1)
353// ---------------------------------------------------------------------------
354// DETWS_DIAG_JSON is a compile-time string literal — zero runtime cost.
355// Adjacent string literals are concatenated by the compiler; DETWS_STR()
356// stringifies an integer macro value without evaluating it twice.
357
358#if DETWS_ENABLE_DIAG
359
360#define _DETWS_STR_(x) #x
361#define _DETWS_STR(x) _DETWS_STR_(x)
362
363#if DETWS_ENABLE_WEBSOCKET
364#define _DETWS_F_WS "true"
365#else
366#define _DETWS_F_WS "false"
367#endif
368
369#if DETWS_ENABLE_SSE
370#define _DETWS_F_SSE "true"
371#else
372#define _DETWS_F_SSE "false"
373#endif
374
375#if DETWS_ENABLE_MULTIPART
376#define _DETWS_F_MP "true"
377#else
378#define _DETWS_F_MP "false"
379#endif
380
381#if DETWS_ENABLE_FILE_SERVING
382#define _DETWS_F_FS "true"
383#else
384#define _DETWS_F_FS "false"
385#endif
386
387#if DETWS_ENABLE_AUTH
388#define _DETWS_F_AUTH "true"
389#else
390#define _DETWS_F_AUTH "false"
391#endif
392
393#define DETWS_DIAG_JSON \
394 "{" \
395 "\"lib\":\"DeterministicESPAsyncWebServer\"," \
396 "\"features\":{" \
397 "\"websocket\":" _DETWS_F_WS "," \
398 "\"sse\":" _DETWS_F_SSE "," \
399 "\"multipart\":" _DETWS_F_MP "," \
400 "\"file_serving\":" _DETWS_F_FS "," \
401 "\"auth\":" _DETWS_F_AUTH \
402 "}," \
403 "\"config\":{" \
404 "\"MAX_CONNS\":" _DETWS_STR(MAX_CONNS) "," \
405 "\"RX_BUF_SIZE\":" _DETWS_STR(RX_BUF_SIZE) "," \
406 "\"BODY_BUF_SIZE\":" _DETWS_STR(BODY_BUF_SIZE) "," \
407 "\"MAX_ROUTES\":" _DETWS_STR(MAX_ROUTES) "," \
408 "\"MAX_HEADERS\":" _DETWS_STR(MAX_HEADERS) "," \
409 "\"MAX_PATH_LEN\":" _DETWS_STR(MAX_PATH_LEN) "," \
410 "\"MAX_KEY_LEN\":" _DETWS_STR(MAX_KEY_LEN) "," \
411 "\"MAX_VAL_LEN\":" _DETWS_STR(MAX_VAL_LEN) "," \
412 "\"MAX_QUERY_LEN\":" _DETWS_STR(MAX_QUERY_LEN) "," \
413 "\"MAX_QUERY_PARAMS\":" _DETWS_STR(MAX_QUERY_PARAMS) "," \
414 "\"CONN_TIMEOUT_MS\":" _DETWS_STR(CONN_TIMEOUT_MS) "," \
415 "\"RESP_HDR_BUF_SIZE\":" _DETWS_STR(RESP_HDR_BUF_SIZE) "," \
416 "\"WS_HDR_BUF_SIZE\":" _DETWS_STR(WS_HDR_BUF_SIZE) "," \
417 "\"CORS_HDR_BUF_SIZE\":" _DETWS_STR(CORS_HDR_BUF_SIZE) "," \
418 "\"EVT_QUEUE_DEPTH\":" _DETWS_STR(EVT_QUEUE_DEPTH) \
419 "}" \
420 "}"
421
422#endif // DETWS_ENABLE_DIAG
423
424// ---------------------------------------------------------------------------
425// Compile-time sanity checks
426// ---------------------------------------------------------------------------
427// These produce a clear #error message in the compiler output rather than a
428// cryptic linker failure or silent misbehaviour.
429
430#if EVT_QUEUE_DEPTH < MAX_CONNS * 4
431#error "DeterministicESPAsyncWebServer: EVT_QUEUE_DEPTH must be >= MAX_CONNS * 4 to absorb event bursts without blocking lwIP"
432#endif
433
434#if MAX_CONNS < 1
435#error "DeterministicESPAsyncWebServer: MAX_CONNS must be >= 1"
436#endif
437
438#if MAX_CONNS > 255
439#error "DeterministicESPAsyncWebServer: MAX_CONNS must be <= 255 (slot IDs are uint8_t)"
440#endif
441
442#if DETWS_ENABLE_WEBSOCKET && DETWS_ENABLE_SSE
443#if MAX_WS_CONNS + MAX_SSE_CONNS > MAX_CONNS
444#error "DeterministicESPAsyncWebServer: MAX_WS_CONNS + MAX_SSE_CONNS must not exceed MAX_CONNS"
445#endif
446#elif DETWS_ENABLE_WEBSOCKET
447#if MAX_WS_CONNS > MAX_CONNS
448#error "DeterministicESPAsyncWebServer: MAX_WS_CONNS must not exceed MAX_CONNS"
449#endif
450#elif DETWS_ENABLE_SSE
451#if MAX_SSE_CONNS > MAX_CONNS
452#error "DeterministicESPAsyncWebServer: MAX_SSE_CONNS must not exceed MAX_CONNS"
453#endif
454#endif
455
456#if BODY_BUF_SIZE < 1
457#error "DeterministicESPAsyncWebServer: BODY_BUF_SIZE must be >= 1"
458#endif
459
460#if BODY_BUF_SIZE > RX_BUF_SIZE
461#error "DeterministicESPAsyncWebServer: BODY_BUF_SIZE must not exceed RX_BUF_SIZE (parser reads from the ring buffer)"
462#endif
463
464#if DETWS_ENABLE_FILE_SERVING && FILE_CHUNK_SIZE > RX_BUF_SIZE
465#error "DeterministicESPAsyncWebServer: FILE_CHUNK_SIZE must not exceed RX_BUF_SIZE"
466#endif
467
468#if MAX_KEY_LEN < 4
469#error "DeterministicESPAsyncWebServer: MAX_KEY_LEN must be >= 4 (minimum valid HTTP header name length)"
470#endif
471
472#if MAX_VAL_LEN < 1
473#error "DeterministicESPAsyncWebServer: MAX_VAL_LEN must be >= 1"
474#endif
475
476#if MAX_PATH_LEN < 2
477#error "DeterministicESPAsyncWebServer: MAX_PATH_LEN must be >= 2 (minimum: \"/\")"
478#endif
479
480#if MAX_ROUTES < 1
481#error "DeterministicESPAsyncWebServer: MAX_ROUTES must be >= 1"
482#endif
483
484#if DETWS_ENABLE_AUTH && MAX_AUTH_LEN < 2
485#error "DeterministicESPAsyncWebServer: MAX_AUTH_LEN must be >= 2 when DETWS_ENABLE_AUTH is set"
486#endif
487
488#if DETWS_ENABLE_WEBSOCKET && WS_FRAME_SIZE < 2
489#error "DeterministicESPAsyncWebServer: WS_FRAME_SIZE must be >= 2 when DETWS_ENABLE_WEBSOCKET is set"
490#endif
491
492#if DETWS_ENABLE_SSE && SSE_BUF_SIZE < 8
493#error "DeterministicESPAsyncWebServer: SSE_BUF_SIZE must be >= 8 when DETWS_ENABLE_SSE is set"
494#endif
495
496#if DETWS_ENABLE_MULTIPART && MAX_MULTIPART_PARTS < 1
497#error "DeterministicESPAsyncWebServer: MAX_MULTIPART_PARTS must be >= 1 when DETWS_ENABLE_MULTIPART is set"
498#endif
499
500#if RESP_HDR_BUF_SIZE < 128
501#error "DeterministicESPAsyncWebServer: RESP_HDR_BUF_SIZE must be >= 128 (status line + headers + CORS block)"
502#endif
503
504#if DETWS_ENABLE_WEBSOCKET && WS_HDR_BUF_SIZE < 128
505#error "DeterministicESPAsyncWebServer: WS_HDR_BUF_SIZE must be >= 128 when DETWS_ENABLE_WEBSOCKET is set"
506#endif
507
508#if CORS_HDR_BUF_SIZE < 64
509#error "DeterministicESPAsyncWebServer: CORS_HDR_BUF_SIZE must be >= 64"
510#endif
511
512#if RESP_HDR_BUF_SIZE < CORS_HDR_BUF_SIZE
513#error "DeterministicESPAsyncWebServer: RESP_HDR_BUF_SIZE must be >= CORS_HDR_BUF_SIZE (CORS block is injected into response headers)"
514#endif
515
516#endif
Runtime-tunable server parameters.