40#ifndef DETERMINISTICESPASYNCWEBSERVER_H
41#define DETERMINISTICESPASYNCWEBSERVER_H
47#if DETWS_ENABLE_WEBSOCKET
53#if DETWS_ENABLE_MULTIPART
57#if DETWS_ENABLE_FILE_SERVING
116typedef const char *(*TemplateVar)(
const char *name);
126typedef void (*
RequestLogCb)(
const char *method,
const char *path,
int status,
int body_len);
160#if DETWS_ENABLE_WEBSOCKET
166typedef void (*WsConnectHandler)(uint8_t ws_id);
176typedef void (*WsMessageHandler)(uint8_t ws_id);
183typedef void (*WsCloseHandler)(uint8_t ws_id);
194typedef void (*SseConnectHandler)(uint8_t sse_id);
205#if DETWS_ENABLE_WEBSOCKET
211#if DETWS_ENABLE_FILE_SERVING
214#if DETWS_ENABLE_WEBDAV
250#if DETWS_ENABLE_WEBSOCKET
251 WsConnectHandler ws_connect;
252 WsMessageHandler ws_message;
257 SseConnectHandler sse_connect;
260#if DETWS_ENABLE_FILE_SERVING
262 const char *static_root;
310typedef size_t (*
ChunkSource)(uint8_t *buf,
size_t cap,
void *ctx);
351 uint8_t _route_count;
357#if DETWS_ENABLE_STATS
358 uint32_t _stat_requests;
367 uint8_t _listener_count;
394#if DETWS_ENABLE_HTTP3
396 const uint8_t *_h3_cert =
nullptr;
397 size_t _h3_cert_len = 0;
398 uint8_t _h3_seed[32] = {0};
400 bool _h3_enabled =
false;
410 uint8_t _middleware_count;
414 uint32_t _rl_window_ms;
415 uint32_t _rl_window_start;
423 bool run_middleware(uint8_t slot_id,
HttpReq *req);
430 bool rate_limit_check(uint8_t slot_id);
443 static bool path_matches(
const char *route,
bool is_wildcard,
const char *req_path);
446 void note_response(uint8_t slot_id,
int code,
int body_len);
448#if DETWS_ENABLE_KEEPALIVE
458 bool keepalive_eval(uint8_t slot_id);
470 void resp_end(uint8_t slot_id,
int code,
int body_len,
bool keep,
bool pre_flushed =
false);
479 const char *resp_conn_hdr(uint8_t slot_id,
bool *keep_out);
487 int append_resp_trailer(
char *buf,
size_t cap,
int hlen, uint8_t slot_id,
const char *cl);
491 void chunk_send_pump(uint8_t slot_id);
495 static bool check_basic_auth(uint8_t slot_id,
HttpReq *req,
const Route *r);
500 bool check_digest_auth(uint8_t slot_id,
HttpReq *req,
const Route *r,
bool *stale);
503 void send_unauth(uint8_t slot_id,
const Route *r,
bool stale =
false);
505 uint8_t _digest_secret[16];
507 void regen_digest_secret();
509 void make_digest_nonce(
char *out,
size_t cap);
512 bool verify_digest_nonce(
const char *nonce,
bool *expired);
515#if DETWS_ENABLE_FILE_SERVING
517 void serve_static_request(uint8_t slot_id,
HttpReq *req,
const Route *r);
519 void serve_file_internal(uint8_t slot_id,
bool head, fs::FS &file_sys,
const char *fs_path,
520 const char *content_type,
const char *content_encoding);
522 void file_send_pump(uint8_t slot_id);
525#if DETWS_ENABLE_WEBDAV
527 bool try_serve_dav(uint8_t slot_id,
HttpReq *req);
529 void serve_dav_request(uint8_t slot_id,
HttpReq *req,
const Route *r);
531 void dav_send_status(uint8_t slot_id,
int code,
const char *extra_headers);
532#if DETWS_ENABLE_STREAM_BODY
534 bool dav_stream_put_begin(
HttpReq *req);
536 void dav_stream_put_data(
HttpReq *req,
const uint8_t *data,
size_t len);
538 static bool dav_put_begin_tramp(
HttpReq *req);
539 static void dav_put_data_tramp(
HttpReq *req,
const uint8_t *data,
size_t len);
541 static void dav_put_abort_tramp(
HttpReq *req);
554 void match_and_execute(uint8_t slot_id);
559 bool route_admits(
const Route *r, uint8_t slot_id,
HttpReq *req)
const;
564 bool dispatch_matched_route(uint8_t slot_id,
HttpReq *req,
HttpMethod method,
Route *r,
bool *path_matched,
565 char *allow_buf,
size_t allow_cap);
573#if DETWS_ENABLE_WEBSOCKET
581 bool authorize_request(uint8_t slot_id,
HttpReq *req,
const Route *r);
584#if DETWS_ENABLE_WEBSOCKET
586 void ws_dispatch_message(
WsConn *ws);
588 void ws_dispatch_close(
WsConn *ws);
661 bool tls_cert(
const uint8_t *cert,
size_t cert_len,
const uint8_t *key,
size_t key_len);
669 int32_t listen_tls(uint16_t port);
685 int32_t begin_tls(uint16_t port,
const uint8_t *cert,
size_t cert_len,
const uint8_t *key,
size_t key_len,
702 bool tls_require_client_cert(
const uint8_t *ca,
size_t ca_len);
716 int tls_client_subject(uint8_t slot_id,
char *out,
size_t out_len);
720#if DETWS_ENABLE_HTTP3
729 bool h3_cert(
const uint8_t *cert_der,
size_t cert_len,
const uint8_t ed25519_seed[32],
737 void dispatch_h3_request(uint32_t conn_id, uint64_t stream_id,
const char *method,
const char *path,
738 const char *authority,
const uint8_t *body,
size_t body_len);
850 void on(
const char *path,
HttpMethod method,
Handler callback,
const char *realm,
const char *user,
851 const char *pass,
bool digest =
false);
854#if DETWS_ENABLE_FILE_SERVING
868 void serve_file(uint8_t slot_id, fs::FS &file_sys,
const char *fs_path,
const char *content_type);
892 void serve_static(
const char *url_prefix, fs::FS &file_sys,
const char *fs_root);
895#if DETWS_ENABLE_WEBDAV
921 void dav(
const char *url_prefix, fs::FS &file_sys,
const char *fs_root);
980#if DETWS_ENABLE_STATS
992 void stats(uint8_t slot_id);
995#if DETWS_ENABLE_METRICS
1008 void metrics(uint8_t slot_id);
1114 void send(uint8_t slot_id,
int code,
const char *content_type,
const char *payload);
1129 void send(uint8_t slot_id,
int code,
const char *content_type,
const uint8_t *body,
size_t body_len);
1154 void redirect(uint8_t slot_id,
int code,
const char *location);
1192 void send_chunked(uint8_t slot_id,
int code,
const char *content_type,
ChunkSource source,
void *ctx =
nullptr);
1221 void set_cookie(uint8_t slot_id,
const char *name,
const char *value,
const char *attrs =
nullptr);
1241 static const char *
mime_type(
const char *path);
1243#if DETWS_ENABLE_DIAG
1253 void diag(uint8_t slot_id);
1256#if DETWS_ENABLE_WEBSOCKET
1276 void on_ws(
const char *path, WsConnectHandler on_connect, WsMessageHandler on_message, WsCloseHandler on_close);
1284 void ws_send_text(uint8_t ws_id,
const char *text);
1293 void ws_send_binary(uint8_t ws_id,
const uint8_t *data, uint16_t len);
1303 void ws_disconnect(uint8_t ws_id);
1321 void on_sse(
const char *path, SseConnectHandler on_connect);
1335 void sse_send(uint8_t sse_id,
const char *data,
const char *event =
nullptr,
const char *
id =
nullptr);
1348 void sse_broadcast(
const char *path,
const char *data,
const char *event =
nullptr,
const char *
id =
nullptr);
DetIface
Network interface a connection arrived on (for per-route filtering).
#define MAX_LISTENERS
Maximum number of simultaneously active listener ports.
ConnProto
Application protocol spoken on a listener port or connection slot.
@ PROTO_HTTP
HTTP/1.1 with optional WS and SSE upgrades.
#define CACHE_CONTROL_BUF_SIZE
Size of the optional Cache-Control header line stored in DetWebServer.
#define DETWS_HTTP3_PORT
UDP port the HTTP/3 (QUIC) server binds by default (used by DetWebServer::h3_cert).
#define MAX_MIDDLEWARE
Maximum globally-registered middleware functions.
#define MAX_PATH_LEN
Maximum URL path length (including leading /).
#define MAX_AUTH_LEN
Maximum username or password length for HTTP Basic Authentication.
#define CORS_HDR_BUF_SIZE
Size of the pre-built CORS header block stored in DetWebServer.
#define EXTRA_HDR_BUF_SIZE
Per-connection buffer for app-supplied custom response headers and cookies.
#define MAX_ROUTES
Maximum simultaneously registered routes.
Single-port HTTP server with deterministic, zero-allocation execution.
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 set_cache_control(const char *value)
Set the Cache-Control header emitted for static files.
void on_regex(const char *pattern, HttpMethod method, Handler callback)
Register a route whose path is a regular expression.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
int32_t restart(const WebServerConfig *cfg=nullptr)
Hard-reset all connections and re-open all registered listeners.
int32_t listen(uint16_t port, ConnProto proto=ConnProto::PROTO_HTTP)
Register a port to listen on when begin() is called.
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 clear_response_headers(uint8_t slot_id)
Discard any headers/cookies queued for this 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 on_not_found(Handler callback)
Register a fallback handler for unmatched requests.
void send_empty(uint8_t slot_id, int code)
Send a headers-only HTTP response and close the connection.
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_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 on_request_log(RequestLogCb cb)
Install a per-request access-log callback (one hook, no buffering).
void service_once(int worker_id=0)
Run exactly one service iteration for worker worker_id (the body driven by that worker's task,...
static const char * mime_type(const char *path)
Guess a Content-Type from a path's file extension.
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 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 set_ap_ip(uint32_t ap_ip)
Tell the server the softAP IPv4 address for STA/AP route filtering.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
DetWebServer()
Construct a DetWebServer with an empty routing table.
void stop()
Gracefully stop the server.
size_t(* ChunkSource)(uint8_t *buf, size_t cap, void *ctx)
Source callback that produces a chunked response body incrementally.
DetWebServerResult
Result codes for listen(), begin(), and restart().
@ DETWS_ERR_LISTEN_FAILED
A listener failed to open (bind/listen/lwIP error).
@ DETWS_ERR_LISTENER_FULL
listen(): listener pool (MAX_LISTENERS) is full.
@ DETWS_ERR_NO_LISTENERS
begin() called before any listen() / begin(port).
void(* Handler)(uint8_t slot_id, HttpReq *request)
Callback signature for HTTP request handlers.
void(* RequestLogCb)(const char *method, const char *path, int status, int body_len)
Per-request access-log callback (see DetWebServer::on_request_log()).
const char *(* TemplateVar)(const char *name)
Resolver for {{name}} template placeholders used by send_template().
RouteType
Discriminates between HTTP, WebSocket, and SSE route entries.
@ ROUTE_HTTP
Standard HTTP request/response.
HttpMethod
HTTP request methods supported by the router.
@ HTTP_METHOD_UNKNOWN
Unrecognized method token → 501 Not Implemented.
@ HTTP_DELETE
Idempotent delete.
@ HTTP_POST
Non-idempotent create / action.
@ HTTP_OPTIONS
Capability query / CORS preflight.
@ HTTP_PATCH
Partial update.
@ HTTP_GET
Safe, idempotent read.
@ HTTP_PUT
Idempotent replace.
@ HTTP_HEAD
Same as GET but no response body.
MwResult(* Middleware)(uint8_t slot_id, HttpReq *request)
Composable pre-dispatch middleware (see DetWebServer::use()).
MwResult
Outcome of a middleware function (see Middleware).
@ MW_HALT
Stop dispatch; the middleware already sent a response.
@ MW_NEXT
Continue to the next middleware / the route handler.
Layer 6 (Presentation) - zero-heap JSON: a bounded writer and top-level reader.
In-place multipart/form-data parser (RFC 7578).
Layer 6 (Presentation) - wires the transport ring buffer to the HTTP parser.
Layer 5 (Session) - event queue dispatcher and session lifecycle.
Layer 6 (Presentation) – Server-Sent Events connection pool.
Fully-parsed HTTP/1.1 request.
Internal route entry stored in the routing table.
bool is_param
true when the path contains a :name segment.
RouteType type
HTTP, WS, or SSE.
bool is_regex
true when the path is a regex (see on_regex()).
Handler callback
HTTP handler (RouteType::ROUTE_HTTP only).
DetIface iface_filter
Interface gate; DetIface::DETIFACE_ANY (0) = match any interface.
HttpMethod method
HTTP method (RouteType::ROUTE_HTTP only).
bool is_active
false for unused table slots.
bool is_wildcard
true when path ends with *.
char path[MAX_PATH_LEN]
Null-terminated path pattern.
Runtime-tunable server parameters.
WebSocket connection state stored in ws_pool[].
void ws_close(WsConn *ws, WsCloseCode code)
Send a Close frame and mark the slot WsParseState::WS_CLOSED.
Layer 6 (Presentation) – WebSocket frame parser and connection pool.
Layer 5 (Session) - server worker identity.
void(* detws_deferred_fn)(void *arg)
Deferred callback signature.