22#if DETWS_ENABLE_METRICS || DETWS_ENABLE_STATS
39static void tmpl_take_placeholder(uint8_t slot,
const char *&p,
TemplateVar resolver,
bool emit,
size_t &total)
41 const char *end = strstr(p + 2,
"}}");
42 size_t nlen = end ? (size_t)(end - (p + 2)) : 0;
43 if (!end || nlen > 32)
53 memcpy(name, p + 2, nlen);
55 const char *val = resolver ? resolver(name) : nullptr;
58 size_t vlen = strnlen(val, 0xFFFF);
66static size_t tmpl_walk(uint8_t slot,
const char *tmpl,
TemplateVar resolver,
bool emit)
72 if (p[0] ==
'{' && p[1] ==
'{')
74 tmpl_take_placeholder(slot, p, resolver, emit, total);
80 while (*p && !(p[0] ==
'{' && p[1] ==
'{'))
82 size_t rlen = (size_t)(p - run);
95 if (!det_conn_active(slot_id))
102 size_t body_len = tmpl_walk(slot_id, tmpl, resolver,
false);
105 const char *cl = resp_conn_hdr(slot_id, &keep);
108 int hlen = snprintf(header,
sizeof(header),
110 "Content-Type: %s\r\n"
111 "Content-Length: %d\r\n",
112 code,
status_text(code), content_type, (
int)body_len);
113 hlen = append_resp_trailer(header,
sizeof(header), hlen, slot_id, cl);
119 if (!head && body_len > 0)
120 tmpl_walk(slot_id, tmpl, resolver,
true);
122 resp_end(slot_id, code, (
int)body_len, keep);
141 if (!det_conn_active(slot_id))
148 const char *cl = resp_conn_hdr(slot_id, &keep);
161 cl =
"Connection: close\r\n";
162 hlen = snprintf(header,
sizeof(header),
164 "Content-Type: %s\r\n",
168 hlen = snprintf(header,
sizeof(header),
170 "Content-Type: %s\r\n"
171 "Transfer-Encoding: chunked\r\n",
173 hlen = append_resp_trailer(header,
sizeof(header), hlen, slot_id, cl);
180 resp_end(slot_id, code, 0, keep);
192 chunk_send_pump(slot_id);
197void DetWebServer::chunk_send_pump(uint8_t slot_id)
203 if (!det_conn_active(slot_id))
218 static const u16_t CHUNK_HDR_RESERVE = 8;
219 const u16_t FRAME = s.
raw ? 0 : 12;
229 size_t cap = (size_t)(avail - FRAME);
233 uint8_t *body = framed + CHUNK_HDR_RESERVE;
256 int sn = snprintf(sz,
sizeof(sz),
"%x\r\n", (
unsigned)n);
257 uint8_t *start = body - sn;
258 memcpy(start, sz, (
size_t)sn);
278 if (slot_id >=
MAX_CONNS || name ==
nullptr || value ==
nullptr)
281 char *buf = _extra_hdr[slot_id];
284 int n = snprintf(buf + used, room,
"%s: %s\r\n", name, value);
285 if (n < 0 || (
size_t)n >= room)
291 if (slot_id >=
MAX_CONNS || name ==
nullptr || value ==
nullptr)
294 char *buf = _extra_hdr[slot_id];
298 if (attrs !=
nullptr && attrs[0] !=
'\0')
299 n = snprintf(buf + used, room,
"Set-Cookie: %s=%s; %s\r\n", name, value, attrs);
301 n = snprintf(buf + used, room,
"Set-Cookie: %s=%s\r\n", name, value);
302 if (n < 0 || (
size_t)n >= room)
310 _extra_hdr[slot_id][0] =
'\0';
320 return DET_MIME_OCTET_STREAM;
323 const char *dot =
nullptr;
324 for (
const char *p = path; *p; p++)
331 if (!dot || dot[1] ==
'\0')
332 return DET_MIME_OCTET_STREAM;
333 const char *ext = dot + 1;
341 {
"html", DET_MIME_TEXT_HTML}, {
"htm", DET_MIME_TEXT_HTML}, {
"css",
"text/css"},
342 {
"js", DET_MIME_JAVASCRIPT}, {
"mjs", DET_MIME_JAVASCRIPT}, {
"json", DET_MIME_JSON},
343 {
"xml",
"application/xml"}, {
"txt", DET_MIME_TEXT_PLAIN}, {
"csv",
"text/csv"},
344 {
"svg",
"image/svg+xml"}, {
"png",
"image/png"}, {
"jpg",
"image/jpeg"},
345 {
"jpeg",
"image/jpeg"}, {
"gif",
"image/gif"}, {
"ico",
"image/x-icon"},
346 {
"webp",
"image/webp"}, {
"wasm",
"application/wasm"}, {
"woff",
"font/woff"},
347 {
"woff2",
"font/woff2"}, {
"ttf",
"font/ttf"}, {
"pdf",
"application/pdf"},
348 {
"gz",
"application/gzip"},
350 for (
size_t i = 0; i <
sizeof(table) /
sizeof(table[0]); i++)
353 const char *b = table[i].ext;
357 char ca = (*a >=
'A' && *a <=
'Z') ? (
char)(*a + 32) : *a;
367 if (eq && *a ==
'\0' && *b ==
'\0')
368 return table[i].type;
370 return DET_MIME_OCTET_STREAM;
377#if DETWS_ENABLE_STATS
392static StatsCtx s_stats;
394static const char *stats_var(
const char *name)
396 if (!strcmp(name,
"uptime_ms"))
397 return s_stats.uptime;
398 if (!strcmp(name,
"requests"))
399 return s_stats.requests;
400 if (!strcmp(name,
"http_2xx"))
402 if (!strcmp(name,
"http_4xx"))
404 if (!strcmp(name,
"http_5xx"))
406 if (!strcmp(name,
"active_conns"))
407 return s_stats.active;
408 if (!strcmp(name,
"free_heap"))
413void DetWebServer::stats(uint8_t slot_id)
417 unsigned long up = millis();
419 uint32_t heap = ESP.getFreeHeap();
424 snprintf(s_stats.uptime,
sizeof(s_stats.uptime),
"%lu", up);
425 snprintf(s_stats.requests,
sizeof(s_stats.requests),
"%lu", (
unsigned long)_stat_requests);
426 snprintf(s_stats.n2xx,
sizeof(s_stats.n2xx),
"%lu", (
unsigned long)_stat_2xx);
427 snprintf(s_stats.n4xx,
sizeof(s_stats.n4xx),
"%lu", (
unsigned long)_stat_4xx);
428 snprintf(s_stats.n5xx,
sizeof(s_stats.n5xx),
"%lu", (
unsigned long)_stat_5xx);
429 snprintf(s_stats.active,
sizeof(s_stats.active),
"%d", active);
430 snprintf(s_stats.heap,
sizeof(s_stats.heap),
"%u", (
unsigned)heap);
436#if DETWS_ENABLE_METRICS
456static MetricsCtx s_metrics;
458static const char *metrics_var(
const char *name)
460 if (!strcmp(name,
"uptime_seconds"))
461 return s_metrics.uptime;
462 if (!strcmp(name,
"requests_total"))
463 return s_metrics.requests;
464 if (!strcmp(name,
"resp_2xx"))
465 return s_metrics.n2xx;
466 if (!strcmp(name,
"resp_4xx"))
467 return s_metrics.n4xx;
468 if (!strcmp(name,
"resp_5xx"))
469 return s_metrics.n5xx;
470 if (!strcmp(name,
"active_conns"))
471 return s_metrics.active;
472 if (!strcmp(name,
"max_conns"))
473 return s_metrics.max;
474 if (!strcmp(name,
"free_heap"))
475 return s_metrics.heap;
476 if (!strcmp(name,
"min_free_heap"))
477 return s_metrics.minheap;
478 if (!strcmp(name,
"heap_size"))
479 return s_metrics.heapsize;
480 if (!strcmp(name,
"max_alloc_heap"))
481 return s_metrics.maxalloc;
485void DetWebServer::metrics(uint8_t slot_id)
489 unsigned long up = millis();
491 uint32_t heap = ESP.getFreeHeap();
492 uint32_t min_heap = ESP.getMinFreeHeap();
493 uint32_t heap_size = ESP.getHeapSize();
494 uint32_t max_alloc = ESP.getMaxAllocHeap();
497 uint32_t min_heap = 0;
498 uint32_t heap_size = 0;
499 uint32_t max_alloc = 0;
502 snprintf(s_metrics.uptime,
sizeof(s_metrics.uptime),
"%lu", up / 1000UL);
503 snprintf(s_metrics.requests,
sizeof(s_metrics.requests),
"%lu", (
unsigned long)_stat_requests);
504 snprintf(s_metrics.n2xx,
sizeof(s_metrics.n2xx),
"%lu", (
unsigned long)_stat_2xx);
505 snprintf(s_metrics.n4xx,
sizeof(s_metrics.n4xx),
"%lu", (
unsigned long)_stat_4xx);
506 snprintf(s_metrics.n5xx,
sizeof(s_metrics.n5xx),
"%lu", (
unsigned long)_stat_5xx);
507 snprintf(s_metrics.active,
sizeof(s_metrics.active),
"%d", active);
508 snprintf(s_metrics.max,
sizeof(s_metrics.max),
"%d", (
int)
MAX_CONNS);
509 snprintf(s_metrics.heap,
sizeof(s_metrics.heap),
"%u", (
unsigned)heap);
510 snprintf(s_metrics.minheap,
sizeof(s_metrics.minheap),
"%u", (
unsigned)min_heap);
511 snprintf(s_metrics.heapsize,
sizeof(s_metrics.heapsize),
"%u", (
unsigned)heap_size);
512 snprintf(s_metrics.maxalloc,
sizeof(s_metrics.maxalloc),
"%u", (
unsigned)max_alloc);
#define CHUNK_BUF_SIZE
Per-chunk staging buffer for send_chunked()'s ChunkSource (max bytes a source produces per call,...
#define RESP_HDR_BUF_SIZE
Stack buffer for HTTP response header lines in send() / send_empty() / send_unauth() / serve_file().
#define MAX_CONNS
Maximum simultaneous TCP connections (fixed static pool; ~3.95 KB of internal RAM per slot).
#define EXTRA_HDR_BUF_SIZE
Per-connection buffer for app-supplied custom response headers and cookies.
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 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_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.
static const char * mime_type(const char *path)
Guess a Content-Type from a path's file extension.
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.
const char * status_text(int code)
Convert an HTTP status code to its standard reason phrase.
bool req_is_head(uint8_t slot_id)
True if the request in slot slot_id used the HEAD method (send headers, no body).
Layer 7 (Application) - public HTTP routing API.
size_t(* ChunkSource)(uint8_t *buf, size_t cap, void *ctx)
Source callback that produces a chunked response body incrementally.
const char *(* TemplateVar)(const char *name)
Resolver for {{name}} template placeholders used by send_template().
Library-private declarations shared between dwserver.cpp and the src/server/*.cpp request-handler tra...
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
@ HTTP_11
HTTP/1.1 - persistent connection by default.
Shared HTTP Content-Type ("MIME") string constants (one source of truth).
void http_reset(uint8_t slot_id)
Reset the HTTP parser for a connection slot.
ChunkSource source
body generator (active==false means none).
int status
response status, for note_response.
void * ctx
caller state passed to source (must outlive the send).
bool raw
HTTP/1.0 client: stream the body unframed, close-delimited (no chunk wrapping).
bool active
a chunked response is in progress on this slot.
int total
body bytes emitted so far (excludes framing).
bool keep
keep-alive vs close at completion.
HttpVersion version
Protocol version parsed from the request line.
ChunkSend chunk[MAX_CONNS]
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
u16_t det_conn_sndbuf(uint8_t slot)
Bytes that can currently be queued for sending on slot.
void det_conn_touch_active(uint8_t slot_id)
Refresh slot's idle-timeout timestamp while a response body is in flight.
uint8_t det_conn_active_count()
Number of server connection slots currently in the CONN_ACTIVE state.
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
const char DETWS_STATS_JSON[]
Runtime stats JSON (rendered via send_template); add/rename fields to taste.
const char DETWS_METRICS_PROM[]
All available Prometheus metrics; comment a value line out with a leading # to drop it.
Layer 7 (Application) - embedded web assets generated from src/web/input/.