DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
middleware.cpp
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 middleware.cpp
6 * @brief Middleware chain + built-in fixed-window rate limiter for DetWebServer.
7 *
8 * Split out of dwserver.cpp (single-purpose server files). use() registers a middleware;
9 * run_middleware() runs the chain in order (first MW_HALT stops dispatch); enable_rate_limit()
10 * / rate_limit_check() implement a rollover-safe fixed-window counter that answers 429 +
11 * Retry-After past the budget. All four are DetWebServer methods over member state - a pure move.
12 */
13
14#include "dwserver.h"
15#include "shared_primitives/mime.h" // DET_MIME_TEXT_PLAIN
16#include <stdio.h>
17
18// ---------------------------------------------------------------------------
19// Middleware chain + built-in rate limiter
20// ---------------------------------------------------------------------------
21
23{
24 if (mw == nullptr || _middleware_count >= MAX_MIDDLEWARE)
25 return;
26 _middleware[_middleware_count++] = mw;
27}
28
29// Run the chain in registration order. The first middleware to return MwResult::MW_HALT
30// stops dispatch; it is responsible for having sent a response.
31bool DetWebServer::run_middleware(uint8_t slot_id, HttpReq *req)
32{
33 for (uint8_t i = 0; i < _middleware_count; i++)
34 {
35 if (_middleware[i] && _middleware[i](slot_id, req) == MwResult::MW_HALT)
36 return true;
37 }
38 return false;
39}
40
41void DetWebServer::enable_rate_limit(uint16_t max_requests, uint32_t window_ms)
42{
43 _rl_max = max_requests;
44 _rl_window_ms = window_ms;
45 _rl_window_start = millis();
46 _rl_count = 0;
47}
48
49// Fixed-window counter. Unsigned subtraction is rollover-safe across the millis()
50// wrap. On the request that tips past _rl_max, reply 429 + Retry-After and stop.
51bool DetWebServer::rate_limit_check(uint8_t slot_id)
52{
53 if (_rl_max == 0 || _rl_window_ms == 0)
54 return false; // disabled
55
56 uint32_t now = millis();
57 if ((uint32_t)(now - _rl_window_start) >= _rl_window_ms)
58 {
59 _rl_window_start = now; // new window
60 _rl_count = 0;
61 }
62
63 _rl_count++;
64 if (_rl_count <= _rl_max)
65 return false; // within budget
66
67 // Over budget: advertise how long until the window resets, then 429.
68 uint32_t elapsed = (uint32_t)(now - _rl_window_start);
69 uint32_t remain_ms = (_rl_window_ms > elapsed) ? (_rl_window_ms - elapsed) : 0;
70 char secs[12];
71 snprintf(secs, sizeof(secs), "%lu", (unsigned long)((remain_ms + 999) / 1000));
72 add_response_header(slot_id, "Retry-After", secs);
73 send(slot_id, 429, DET_MIME_TEXT_PLAIN, "Too Many Requests");
74 return true;
75}
#define MAX_MIDDLEWARE
Maximum globally-registered middleware functions.
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.
Definition response.cpp:276
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 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.
Layer 7 (Application) - public HTTP routing API.
MwResult(* Middleware)(uint8_t slot_id, HttpReq *request)
Composable pre-dispatch middleware (see DetWebServer::use()).
Definition dwserver.h:158
@ MW_HALT
Stop dispatch; the middleware already sent a response.
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
Fully-parsed HTTP/1.1 request.