|
DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
|
Single-port HTTP server with deterministic, zero-allocation execution. More...
#include <dwserver.h>
Public Member Functions | |
| DetWebServer () | |
| Construct a DetWebServer with an empty routing table. | |
| int32_t | listen (uint16_t port, ConnProto proto=ConnProto::PROTO_HTTP) |
| Register a port to listen on when begin() is called. | |
| int32_t | begin (const WebServerConfig *cfg=nullptr) |
| Initialize all connection slots and open all registered listeners. | |
| int32_t | begin (uint16_t port, const WebServerConfig *cfg=nullptr) |
Convenience overload: register port as HTTP and start listening. | |
| void | stop () |
| Gracefully stop the server. | |
| int32_t | restart (const WebServerConfig *cfg=nullptr) |
| Hard-reset all connections and re-open all registered listeners. | |
| void | on (const char *path, HttpMethod method, Handler callback) |
| Register a route handler. | |
| void | on (const char *path, HttpMethod method, Handler callback, DetIface iface) |
| Register a route that only matches on a specific network interface. | |
| void | on_regex (const char *pattern, HttpMethod method, Handler callback) |
| Register a route whose path is a regular expression. | |
| void | set_ap_ip (uint32_t ap_ip) |
| Tell the server the softAP IPv4 address for STA/AP route filtering. | |
| void | on_not_found (Handler callback) |
| Register a fallback handler for unmatched requests. | |
| void | on_request_log (RequestLogCb cb) |
| Install a per-request access-log callback (one hook, no buffering). | |
| void | use (Middleware mw) |
| Register a middleware to run before every request is dispatched. | |
| void | enable_rate_limit (uint16_t max_requests, uint32_t window_ms) |
| Enable a built-in fixed-window request rate limiter. | |
| void | set_cors (const char *origin) |
| Enable CORS by pre-building the Access-Control headers. | |
| void | set_cache_control (const char *value) |
Set the Cache-Control header emitted for static files. | |
| void | handle () |
Drive the server - call every Arduino loop() iteration. | |
| void | service_once (int worker_id=0) |
Run exactly one service iteration for worker worker_id (the body driven by that worker's task, or by handle() when no task is running). | |
| void | http_poll_slot (uint8_t slot_id) |
| The instance-bound HTTP poll pump for one slot (the HTTP ProtoHandler's on_poll). | |
| bool | defer (uint8_t slot, detws_deferred_fn fn, void *arg) |
Run fn(arg) on the worker that owns connection slot. | |
| void | send (uint8_t slot_id, int code, const char *content_type, const char *payload) |
| Send an HTTP response with a body and close the connection. | |
| void | send (uint8_t slot_id, int code, const char *content_type, const uint8_t *body, size_t body_len) |
| Send an HTTP response with an explicit-length (possibly binary) body. | |
| void | send_empty (uint8_t slot_id, int code) |
| Send a headers-only HTTP response and close the connection. | |
| void | redirect (uint8_t slot_id, int code, const char *location) |
| Send an HTTP redirect (Location header, empty body) and close. | |
| void | send_template (uint8_t slot_id, int code, const char *content_type, const char *tmpl, TemplateVar resolver) |
Send a response body with {{name}} placeholders substituted. | |
| void | send_chunked (uint8_t slot_id, int code, const char *content_type, ChunkSource source, void *ctx=nullptr) |
| Stream a response body of unknown length via chunked transfer. | |
| void | add_response_header (uint8_t slot_id, const char *name, const char *value) |
| Queue a custom response header for the next send on this slot. | |
| void | set_cookie (uint8_t slot_id, const char *name, const char *value, const char *attrs=nullptr) |
Queue a Set-Cookie response header for the next send on this slot. | |
| void | clear_response_headers (uint8_t slot_id) |
| Discard any headers/cookies queued for this slot. | |
Static Public Member Functions | |
| static const char * | mime_type (const char *path) |
Guess a Content-Type from a path's file extension. | |
Single-port HTTP server with deterministic, zero-allocation execution.
MAX_CONNS (default 4).MAX_ROUTES (default 16).Definition at line 347 of file dwserver.h.
| DetWebServer::DetWebServer | ( | ) |
Construct a DetWebServer with an empty routing table.
All route slots are marked inactive. CORS is disabled. The not-found handler is null (falls back to built-in 404 response).
Definition at line 236 of file dwserver.cpp.
References MAX_CONNS, MAX_MIDDLEWARE, and MAX_ROUTES.
| int32_t DetWebServer::listen | ( | uint16_t | port, |
| ConnProto | proto = ConnProto::PROTO_HTTP |
||
| ) |
Register a port to listen on when begin() is called.
Call this before begin() for each port you want the server to accept connections on. The proto argument tells the session layer which protocol handler to invoke for events on this port.
For the common single-HTTP-port case, prefer begin(80) which calls this internally. Use the explicit listen() + begin() form when you need multiple ports (e.g., HTTP on 80 and Telnet on 23).
| port | TCP port to open. |
| proto | Application protocol; defaults to ConnProto::PROTO_HTTP. |
Definition at line 411 of file dwserver.cpp.
References DETWS_ERR_LISTENER_FULL, and MAX_LISTENERS.
Referenced by begin().
| int32_t DetWebServer::begin | ( | const WebServerConfig * | cfg = nullptr | ) |
Initialize all connection slots and open all registered listeners.
Resets the HTTP parser pool, calls DeterministicAsyncTCP::pool_init(), then calls listener_add() for each port registered via listen(). Requires at least one prior listen() call. For the common single-port case use begin(port, cfg) instead.
| cfg | Optional runtime configuration. Pass nullptr for defaults. |
Definition at line 495 of file dwserver.cpp.
References DETWS_ENABLE_HTTP3, DETWS_ERR_LISTEN_FAILED, DETWS_ERR_NO_LISTENERS, DETWS_OK, detws_workers_start(), http_reset(), listener_add(), MAX_CONNS, DeterministicAsyncTCP::pool_init(), sse_init(), InstanceCtx::worker_server, and ws_init().
| int32_t DetWebServer::begin | ( | uint16_t | port, |
| const WebServerConfig * | cfg = nullptr |
||
| ) |
Convenience overload: register port as HTTP and start listening.
Equivalent to listen(port); begin(cfg);. Preserved for backward compatibility with single-port sketches.
| port | TCP port to listen on (typically 80). |
| cfg | Optional runtime configuration. Pass nullptr for defaults. |
Definition at line 561 of file dwserver.cpp.
| void DetWebServer::stop | ( | ) |
Gracefully stop the server.
Aborts all active connections, closes the listener, frees the event queue, and resets all HTTP parser slots. The WiFi and TCP/IP stack remain active. Call begin() or restart() to bring the server back up.
Definition at line 715 of file dwserver.cpp.
References detws_workers_stop(), http_reset(), listener_stop_all(), MAX_CONNS, sse_init(), DeterministicAsyncTCP::stop(), and ws_init().
Referenced by restart().
| int32_t DetWebServer::restart | ( | const WebServerConfig * | cfg = nullptr | ) |
Hard-reset all connections and re-open all registered listeners.
Equivalent to stop() followed by begin(cfg) using the ports and protocols registered via listen() (or the port passed to begin(port)). The WiFi and TCP/IP stack are not touched.
Calling restart() before any listen() / begin(port) has no effect and returns -1.
| cfg | Optional new runtime configuration. Pass nullptr to reuse the compile-time default (CONN_TIMEOUT_MS). |
Definition at line 707 of file dwserver.cpp.
References begin(), DETWS_ERR_NO_LISTENERS, and stop().
| void DetWebServer::on | ( | const char * | path, |
| HttpMethod | method, | ||
| Handler | callback | ||
| ) |
Register a route handler.
Routes are matched in registration order (first match wins). A trailing * in path enables prefix matching: "/api/" followed by * matches "/api/users", "/api/devices", etc.
| path | URL path pattern, e.g. "/api/status", or a prefix ending in a * wildcard. Must be ≤ MAX_PATH_LEN - 1 characters. |
| method | HTTP method this route accepts. |
| callback | Function called when this route is matched. |
Definition at line 759 of file dwserver.cpp.
References Route::callback, fill_route_base(), MAX_ROUTES, Route::method, ROUTE_HTTP, and Route::type.
| void DetWebServer::on | ( | const char * | path, |
| HttpMethod | method, | ||
| Handler | callback, | ||
| DetIface | iface | ||
| ) |
Register a route that only matches on a specific network interface.
Identical to on(path, method, callback) but the route is invisible unless the request arrived on iface (DetIface::DETIFACE_STA or DetIface::DETIFACE_AP). A non-matching interface falls through to other routes / 404, so you can, e.g., expose a provisioning UI only on the softAP and the app API only on the station link. Requires set_ap_ip() to have been called so connections can be classified.
| path | URL path pattern. |
| method | HTTP method. |
| callback | Handler invoked on a match. |
| iface | DetIface::DETIFACE_STA or DetIface::DETIFACE_AP (DetIface::DETIFACE_ANY = no filter). |
Definition at line 770 of file dwserver.cpp.
References Route::callback, fill_route_base(), Route::iface_filter, MAX_ROUTES, Route::method, ROUTE_HTTP, and Route::type.
| void DetWebServer::on_regex | ( | const char * | pattern, |
| HttpMethod | method, | ||
| Handler | callback | ||
| ) |
Register a route whose path is a regular expression.
The whole request path must match pattern (implicitly anchored). The matcher is a small, bounded, allocation-free backtracker supporting: . (any char), * + ? quantifiers, character classes [...] / [^...] with a-z ranges, the shorthands \d \w \s (and \D \W \S), and \ to escape a metacharacter. It is non-capturing and has no groups () or alternation | - use :name path parameters (see the other on() overload notes / http_get_param) when you need to capture. Matching is bounded by RE_MAX_STEPS and fails closed past that budget.
| pattern | Regex the full path must match (stored, <= MAX_PATH_LEN-1). |
| method | HTTP method. |
| callback | Handler invoked on a match. |
Definition at line 787 of file dwserver.cpp.
References Route::callback, fill_route_base(), Route::is_regex, MAX_ROUTES, Route::method, ROUTE_HTTP, and Route::type.
| void DetWebServer::set_ap_ip | ( | uint32_t | ap_ip | ) |
Tell the server the softAP IPv4 address for STA/AP route filtering.
Each accepted connection is tagged DetIface::DETIFACE_AP when its local IP equals ap_ip, else DetIface::DETIFACE_STA. Call once after starting the softAP, e.g. server.set_ap_ip(WiFi.softAPIP()) (IPAddress converts to uint32_t). Without it, every connection is treated as DetIface::DETIFACE_STA.
| ap_ip | softAP IPv4 address in network byte order (0 to clear). |
Definition at line 782 of file dwserver.cpp.
References detws_ap_ip.
| void DetWebServer::on_not_found | ( | Handler | callback | ) |
Register a fallback handler for unmatched requests.
Called instead of sending a built-in 404 when no route matches. The callback may call send() to return a custom error page.
| callback | Handler to invoke on a 404 condition. |
Definition at line 848 of file dwserver.cpp.
| void DetWebServer::on_request_log | ( | RequestLogCb | cb | ) |
Install a per-request access-log callback (one hook, no buffering).
cb is invoked once per response with the method, path, status code, and response body length. Pass nullptr to remove. See RequestLogCb.
Definition at line 259 of file dwserver.cpp.
| void DetWebServer::use | ( | Middleware | mw | ) |
Register a middleware to run before every request is dispatched.
Middlewares run in registration order (see Middleware) ahead of route matching, after the built-in rate-limit check. Up to MAX_MIDDLEWARE may be registered; further calls are ignored. Use this to add cross-cutting behavior - request logging, custom auth, header injection, feature gating - composed independently of individual routes.
| mw | Middleware function pointer (must not be nullptr). |
Definition at line 22 of file middleware.cpp.
References MAX_MIDDLEWARE.
| void DetWebServer::enable_rate_limit | ( | uint16_t | max_requests, |
| uint32_t | window_ms | ||
| ) |
Enable a built-in fixed-window request rate limiter.
Counts all incoming requests in a sliding fixed window; once more than max_requests arrive within window_ms the server answers further requests in that window with 429 Too Many Requests (plus a Retry-After header) instead of dispatching them. The check runs before the middleware chain and route matching, so it bounds work under flood. State is a few per-server counters (no heap, no per-IP table) - a global throttle suited to a small device behind a trusted LAN. For connection-level flood defense see also DETWS_ENABLE_ACCEPT_THROTTLE.
| max_requests | Requests allowed per window. Pass 0 to disable. |
| window_ms | Window length in milliseconds (must be > 0). |
Definition at line 41 of file middleware.cpp.
| void DetWebServer::set_cors | ( | const char * | origin | ) |
Enable CORS by pre-building the Access-Control headers.
Once called, every response produced by send() and send_empty() includes the CORS headers. OPTIONS requests are intercepted and answered with 204 automatically (preflight short-circuit).
| origin | Access-Control-Allow-Origin value, e.g. "*" or "https://example.com". Pass "" to disable CORS. |
Definition at line 866 of file dwserver.cpp.
References CORS_HDR_BUF_SIZE.
| void DetWebServer::set_cache_control | ( | const char * | value | ) |
Set the Cache-Control header emitted for static files.
Applies to serve_file() / serve_static() responses (beside the ETag), so browsers can cache assets and revalidate cheaply with If-None-Match. Examples: "no-cache" (cache but always revalidate), "max-age=3600", "public, max-age=31536000, immutable". Pass "" / nullptr to disable.
| value | Cache-Control directive, or empty/null to emit no header. |
Definition at line 882 of file dwserver.cpp.
References CACHE_CONTROL_BUF_SIZE.
| void DetWebServer::handle | ( | ) |
Drive the server - call every Arduino loop() iteration.
Main application tick - tick the session layer then dispatch completed requests.
On ESP32 begin() spawns the server worker task(s) (see DETWS_WORKER_COUNT), which run the pipeline on their own core; handle() is then a no-op and your loop() is free for application code. On host builds (and if no worker task is running) handle() drives one service iteration inline, so existing sketches and the native tests keep working unchanged.
One service iteration (see service_once()):
DeterministicAsyncTCP::check_timeouts() to kill stale connections.ParseState::PARSE_COMPLETE requests and dispatches them to the matching route handler.ParseState::PARSE_ERROR.ParseState::PARSE_ENTITY_TOO_LARGE.ParseState::PARSE_URI_TOO_LONG.Threading note: with the worker task running, route/WS/SSE handlers execute in the worker task. Do server I/O from handlers; pushing from loop() (e.g. SSE broadcast on a timer) runs concurrently with the worker and is made thread-safe in a later phase.
Call this repeatedly from loop(). Each call:
Definition at line 1009 of file dwserver.cpp.
References detws_workers_running(), and service_once().
| void DetWebServer::service_once | ( | int | worker_id = 0 | ) |
Run exactly one service iteration for worker worker_id (the body driven by that worker's task, or by handle() when no task is running).
Services only the connection slots owned by worker_id, so multiple workers run disjoint slot sets in parallel. At DETWS_WORKER_COUNT=1 worker 0 owns every slot. Public so the worker task can invoke it; application code should call handle() rather than this directly.
Definition at line 1019 of file dwserver.cpp.
References conn_pool, det_conn_ack_consumed(), detws_millis(), detws_worker_run_deferred(), InstanceCtx::http_instance, http_proto_set_poll(), MAX_CONNS, ProtoHandler::on_poll, proto_get(), and server_tick().
Referenced by handle().
| void DetWebServer::http_poll_slot | ( | uint8_t | slot_id | ) |
The instance-bound HTTP poll pump for one slot (the HTTP ProtoHandler's on_poll).
Installed into the HTTP handler at begin() via http_proto_set_poll() so the worker dispatch loop pumps HTTP through the same uniform ProtoHandler seam as every other protocol - there is no HTTP special case in the loop. Runs the file/chunk send pumps, the WebSocket + SSE drains, the keep-alive re-parse, and dispatches a completed request into this server's routes. Public only so the poll trampoline can reach it (like service_once); application code never calls it directly.
| slot_id | Connection slot to pump. |
Definition at line 1075 of file dwserver.cpp.
References ChunkSend::active, SendCtx::chunk, CONN_ACTIVE, conn_pool, det_conn_abort_slot(), det_conn_begin_close(), DETWS_ENABLE_TLS, http_parse(), http_pool, http_reset(), PARSE_COMPLETE, PARSE_ENTITY_TOO_LARGE, PARSE_ERROR, WsConn::parse_state, PARSE_URI_TOO_LONG, s_send, send(), sse_find(), WS_CLOSED, WS_ERROR, ws_feed_byte(), ws_find(), WS_FRAME_READY, ws_free(), ws_parse(), and ws_reset_frame().
| bool DetWebServer::defer | ( | uint8_t | slot, |
| detws_deferred_fn | fn, | ||
| void * | arg | ||
| ) |
Run fn(arg) on the worker that owns connection slot.
The thread-safe way to push to a connection from outside a handler - e.g. an SSE broadcast or a ws_send from loop() or a sensor task. Calling the send API directly from another task would race the worker that owns the slot; instead wrap the send in fn and defer it, and it runs single-threaded in the owning worker's context. arg must stay valid until the callback runs. On host builds (no worker task) it runs inline immediately.
Definition at line 1201 of file dwserver.cpp.
References conn_pool, detws_defer(), and MAX_CONNS.
| void DetWebServer::send | ( | uint8_t | slot_id, |
| int | code, | ||
| const char * | content_type, | ||
| const char * | payload | ||
| ) |
Send an HTTP response with a body and close the connection.
Writes status line, Content-Type, Content-Length, optional CORS headers, and the payload; then calls tcp_close (tcp_abort on failure). Always calls http_reset() at the end to free the parser slot.
| slot_id | Connection slot index returned by the router. |
| code | HTTP status code (200, 404, 500, …). |
| content_type | MIME type string, e.g. "application/json". |
| payload | Null-terminated response body. |
Definition at line 1582 of file dwserver.cpp.
References send().
Referenced by http_poll_slot(), and send().
| void DetWebServer::send | ( | uint8_t | slot_id, |
| int | code, | ||
| const char * | content_type, | ||
| const uint8_t * | body, | ||
| size_t | body_len | ||
| ) |
Send an HTTP response with an explicit-length (possibly binary) body.
Same as send() above but the body length is given, so the body may contain NUL bytes (protobuf, gRPC-web frames, octet-stream, images). body_len is bounded by the single-write limit (65535); larger bodies need the chunked/file path.
| slot_id | Connection slot index returned by the router. |
| code | HTTP status code. |
| content_type | MIME type string, e.g. "application/grpc-web+proto". |
| body | Response body (may contain NULs); not required to be terminated. |
| body_len | Number of body octets. |
Definition at line 1588 of file dwserver.cpp.
References CONN_ACTIVE, conn_pool, CONN_POOL_SLOTS, det_conn_send(), det_conn_send_flush(), http_reset(), TcpConn::pcb, req_is_head(), RESP_HDR_BUF_SIZE, TcpConn::state, and status_text().
| void DetWebServer::send_empty | ( | uint8_t | slot_id, |
| int | code | ||
| ) |
Send a headers-only HTTP response and close the connection.
Equivalent to send() with an empty body and Content-Length: 0. Useful for 204 No Content, 304 Not Modified, HEAD responses, and CORS preflight replies.
| slot_id | Connection slot index. |
| code | HTTP status code. |
Definition at line 1662 of file dwserver.cpp.
References CONN_ACTIVE, conn_pool, CONN_POOL_SLOTS, det_conn_send_flush(), http_reset(), TcpConn::pcb, RESP_HDR_BUF_SIZE, TcpConn::state, and status_text().
| void DetWebServer::redirect | ( | uint8_t | slot_id, |
| int | code, | ||
| const char * | location | ||
| ) |
Send an HTTP redirect (Location header, empty body) and close.
Convenience for the common /→/index.html or canonical-host case, previously hand-rolled via send_empty() plus a manual Location header.
| slot_id | Connection slot index. |
| code | Redirect status: 301, 302, 303, 307, or 308. Any other value is treated as 302 Found. |
| location | Value for the Location response header. |
Definition at line 1695 of file dwserver.cpp.
References CONN_ACTIVE, conn_pool, det_conn_send_flush(), http_reset(), MAX_CONNS, TcpConn::pcb, RESP_HDR_BUF_SIZE, TcpConn::state, and status_text().
| void DetWebServer::send_template | ( | uint8_t | slot_id, |
| int | code, | ||
| const char * | content_type, | ||
| const char * | tmpl, | ||
| TemplateVar | resolver | ||
| ) |
Send a response body with {{name}} placeholders substituted.
Streams tmpl to the client, replacing each {{name}} token with the string returned by resolver (nullptr → empty). The body is never buffered whole: it is walked twice - once to compute Content-Length, once to write - so memory use is constant regardless of body size. A {{ with no matching }} (or a name longer than 32 chars) is emitted literally.
| slot_id | Connection slot index. |
| code | HTTP status code. |
| content_type | Response Content-Type. |
| tmpl | Null-terminated template text. |
| resolver | Placeholder resolver (see TemplateVar), or nullptr. |
Definition at line 90 of file response.cpp.
References det_conn_send(), http_reset(), MAX_CONNS, req_is_head(), RESP_HDR_BUF_SIZE, and status_text().
| void DetWebServer::send_chunked | ( | uint8_t | slot_id, |
| int | code, | ||
| const char * | content_type, | ||
| ChunkSource | source, | ||
| void * | ctx = nullptr |
||
| ) |
Stream a response body of unknown length via chunked transfer.
Writes the status line and headers (including Transfer-Encoding: chunked, plus any CORS / queued custom headers), then pulls the body from source one piece at a time, adding the chunk framing and the terminating chunk. The body is never buffered whole and the send paces with the TCP window - paging across server loops as it drains - so output size is unbounded in constant memory and a body larger than the send buffer is never truncated. This is the complement to send(), which needs the full payload up front. A HEAD request sends the headers only (source is not called).
| slot_id | Connection slot index. |
| code | HTTP status code. |
| content_type | Response Content-Type. |
| source | Generator that produces the body (must not be nullptr). |
| ctx | Opaque state handed to source; see ChunkSource for the lifetime requirement (must outlive the response). |
Definition at line 137 of file response.cpp.
References ChunkSend::active, SendCtx::chunk, ChunkSend::ctx, det_conn_send(), HTTP_11, http_pool, http_reset(), ChunkSend::keep, MAX_CONNS, ChunkSend::raw, req_is_head(), RESP_HDR_BUF_SIZE, s_send, ChunkSend::source, ChunkSend::status, status_text(), ChunkSend::total, and HttpReq::version.
| void DetWebServer::add_response_header | ( | uint8_t | slot_id, |
| const char * | name, | ||
| const char * | value | ||
| ) |
Queue a custom response header for the next send on this slot.
Call from inside a handler before send() / send_empty() / redirect(). The header is appended to a fixed per-slot buffer (EXTRA_HDR_BUF_SIZE) and emitted verbatim as Name: value\r\n. Headers that would overflow the buffer are dropped whole (never truncated mid-line). The buffer is cleared automatically at the start of each request.
| slot_id | Connection slot index. |
| name | Header field name (no : or CRLF). |
| value | Header field value (no CRLF). |
Definition at line 276 of file response.cpp.
References EXTRA_HDR_BUF_SIZE, and MAX_CONNS.
| void DetWebServer::set_cookie | ( | uint8_t | slot_id, |
| const char * | name, | ||
| const char * | value, | ||
| const char * | attrs = nullptr |
||
| ) |
Queue a Set-Cookie response header for the next send on this slot.
Emits Set-Cookie: name=value\r\n, or Set-Cookie: name=value; attrs\r\n when attrs is non-null (e.g. "Path=/; HttpOnly; Max-Age=3600"). Shares the per-slot buffer with add_response_header().
| slot_id | Connection slot index. |
| name | Cookie name. |
| value | Cookie value. |
| attrs | Optional ;-separated attribute string, or nullptr. |
Definition at line 289 of file response.cpp.
References EXTRA_HDR_BUF_SIZE, and MAX_CONNS.
| void DetWebServer::clear_response_headers | ( | uint8_t | slot_id | ) |
Discard any headers/cookies queued for this slot.
| slot_id | Connection slot index. |
Definition at line 306 of file response.cpp.
References MAX_CONNS.
|
static |
Guess a Content-Type from a path's file extension.
Small static extension→type table covering the common web asset types (html, css, js, json, svg, png, jpg, gif, ico, txt, wasm, woff2, …). Case-insensitive on the extension. Falls back to "application/octet-stream" when the extension is unknown or absent.
| path | File path or name (e.g. "/css/site.css"). |
Definition at line 317 of file response.cpp.