DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
h2_server.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 h2_server.cpp
6 * @brief HTTP/2 engine <-> request-pipeline bridge - implementation. See h2_server.h.
7 */
8
10
11#if DETWS_ENABLE_HTTP2 && DETWS_ENABLE_TLS
12
17#include <stdint.h>
18#include <string.h>
19
20// The per-slot engines are large (~28 KB each), so the pool does not fit internal DRAM alongside
21// TLS - it lives in PSRAM (DETWS_H2_POOL_IN_PSRAM). Same mechanism/caveat as the TLS arena: it
22// needs a framework built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y (the stock
23// arduino-esp32 core ships it OFF, so EXT_RAM_BSS_ATTR would no-op); see tools/psram/README.md.
24#if DETWS_H2_POOL_IN_PSRAM && defined(ARDUINO)
25#include <esp_attr.h> // pulls in sdkconfig.h -> CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
26#if !defined(CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY)
27#error \
28 "DETWS_H2_POOL_IN_PSRAM needs a framework built with CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y. The stock arduino-esp32 core ships it OFF, so EXT_RAM_BSS_ATTR silently no-ops and the pool would overflow internal DRAM. Rebuild the core (tools/psram/README.md) or unset DETWS_H2_POOL_IN_PSRAM."
29#endif
30#if defined(EXT_RAM_BSS_ATTR)
31#define DETWS_H2_POOL_ATTR EXT_RAM_BSS_ATTR // IDF v5 / arduino-esp32 3.x
32#elif defined(EXT_RAM_ATTR)
33#define DETWS_H2_POOL_ATTR EXT_RAM_ATTR // IDF v4 / arduino-esp32 2.x
34#else
35#define DETWS_H2_POOL_ATTR
36#endif
37#else
38#define DETWS_H2_POOL_ATTR
39#endif
40
41// HTTP/2 connection pool, owned by one instance (internal linkage): the per-slot H2 connection
42// state. One named owner, unreachable from any other translation unit.
43struct H2ServerCtx
44{
45 H2Conn pool[MAX_CONNS];
46};
47static DETWS_H2_POOL_ATTR H2ServerCtx s_h2;
48
49namespace
50{
51void set_field(char *dst, size_t cap, const char *src, size_t n)
52{
53 if (n >= cap)
54 n = cap - 1;
55 memcpy(dst, src, n);
56 dst[n] = 0;
57}
58
59// --- engine callbacks (io / app carry the slot index) ---------------------------------------
60
61void cb_write(void *io, const uint8_t *data, size_t len)
62{
63 uint8_t slot = (uint8_t)(uintptr_t)io;
64 size_t off = 0;
65 while (off < len)
66 {
67 int w = det_tls_write(slot, data + off, len - off);
68 if (w <= 0)
69 break; // error / would-block: best-effort for this path
70 off += (size_t)w;
71 }
72}
73
74void cb_header(void *app, uint32_t, const char *n, size_t nl, const char *v, size_t vl)
75{
76 HttpReq *r = &http_pool[(uint8_t)(uintptr_t)app];
77 if (nl == 7 && memcmp(n, ":method", 7) == 0)
78 {
79 set_field(r->method, sizeof r->method, v, vl);
80 }
81 else if (nl == 5 && memcmp(n, ":path", 5) == 0)
82 {
83 const char *q = (const char *)memchr(v, '?', vl);
84 size_t plen = q ? (size_t)(q - v) : vl;
85 set_field(r->path, sizeof r->path, v, plen);
86 r->path_idx = strnlen(r->path, sizeof r->path);
87 if (q)
88 {
89 set_field(r->query, sizeof r->query, q + 1, vl - plen - 1);
90 r->query_idx = strnlen(r->query, sizeof r->query);
91 }
92 }
93 else if (nl == 10 && memcmp(n, ":authority", 10) == 0)
94 {
96 {
97 Header *h = &r->headers[r->header_count++];
98 set_field(h->key, sizeof h->key, "host", 4);
99 set_field(h->val, sizeof h->val, v, vl);
100 }
101 }
102 else if (nl >= 1 && n[0] == ':')
103 {
104 // other pseudo-header (:scheme, :protocol) - not needed by the dispatcher
105 }
106 else
107 {
108 if (r->header_count < MAX_HEADERS)
109 {
110 Header *h = &r->headers[r->header_count++];
111 set_field(h->key, sizeof h->key, n, nl);
112 set_field(h->val, sizeof h->val, v, vl);
113 }
114 if (nl == 14 && memcmp(n, "content-length", 14) == 0)
115 {
116 size_t cl = 0;
117 for (size_t i = 0; i < vl && v[i] >= '0' && v[i] <= '9'; i++)
118 cl = cl * 10 + (size_t)(v[i] - '0');
119 r->content_length = cl;
120 }
121 }
122}
123
124void cb_headers_end(void *app, uint32_t sid, bool)
125{
126 uint8_t slot = (uint8_t)(uintptr_t)app;
127 conn_pool[slot].h2_stream = sid;
128 http_pool[slot].parse_state = ParseState::PARSE_COMPLETE; // the worker's handle() loop dispatches it
129}
130
131void cb_data(void *app, uint32_t, const uint8_t *data, size_t len, bool)
132{
133 HttpReq *r = &http_pool[(uint8_t)(uintptr_t)app];
134 for (size_t i = 0; i < len && r->body_len < BODY_BUF_SIZE; i++)
135 r->body[r->body_len++] = data[i];
136 r->body[r->body_len] = 0;
137 r->body_bytes_read += len;
138}
139} // namespace
140
141void h2_server_open(uint8_t slot)
142{
143 H2Callbacks cb;
144 memset(&cb, 0, sizeof cb);
145 cb.write = cb_write;
146 cb.on_header = cb_header;
147 cb.on_headers_end = cb_headers_end;
148 cb.on_data = cb_data;
149 cb.io = (void *)(uintptr_t)slot;
150 cb.app = (void *)(uintptr_t)slot;
151 h2_conn_init(&s_h2.pool[slot], &cb); // emits our SETTINGS through cb_write
153}
154
155void h2_server_data(uint8_t slot)
156{
157 uint8_t buf[512];
158 int n;
159 while ((n = det_tls_read(slot, buf, sizeof buf)) > 0)
160 {
161 if (!h2_conn_recv(&s_h2.pool[slot], buf, (size_t)n))
162 {
163 h2_conn_goaway(&s_h2.pool[slot], 1 /* PROTOCOL_ERROR */);
164 return;
165 }
166 }
167}
168
169bool h2_server_respond(uint8_t slot, int code, const char *content_type, const char *body, size_t len)
170{
171 bool ok = h2_conn_respond(&s_h2.pool[slot], conn_pool[slot].h2_stream, code, content_type, body, len);
172 http_parser_reset(&http_pool[slot]); // ready for the next stream; keep the connection open
173 return ok;
174}
175
176void h2_server_close(uint8_t slot)
177{
178 conn_pool[slot].h2 = 0;
179 conn_pool[slot].h2_checked = 0;
180 conn_pool[slot].resp_sink = nullptr;
181}
182
183#endif // DETWS_ENABLE_HTTP2 && DETWS_ENABLE_TLS
#define BODY_BUF_SIZE
Maximum request body bytes stored in HttpReq::body.
#define MAX_HEADERS
Maximum HTTP headers stored per request.
#define MAX_CONNS
Maximum simultaneous TCP connections (fixed static pool; ~3.95 KB of internal RAM per slot).
HTTP/2 connection + stream engine (RFC 9113) over the HPACK + frame layers.
Bridge between the HTTP/2 engine (h2_conn) and the server's request pipeline.
void http_parser_reset(HttpReq *req)
Reset a parser context to the initial (ParseState::PARSE_METHOD) state.
HttpReq http_pool[CONN_POOL_SLOTS]
Pool of parser contexts, one per connection-pool slot (incl. reserved dispatch slots).
Standalone HTTP/1.1 request parser - no transport dependency.
@ PARSE_COMPLETE
Full request parsed; ready for dispatch.
A single HTTP header field (key: value).
Definition http_parser.h:90
char val[MAX_VAL_LEN]
Field value, null-terminated.
Definition http_parser.h:92
char key[MAX_KEY_LEN]
Field name, null-terminated.
Definition http_parser.h:91
Fully-parsed HTTP/1.1 request.
Header headers[MAX_HEADERS]
Captured header fields.
char query[MAX_QUERY_LEN]
Raw query string (after ?).
char method[DETWS_METHOD_BUF_SIZE]
HTTP method, null-terminated (OPTIONS, or WebDAV methods when enabled).
uint8_t body[BODY_BUF_SIZE+1]
Stored body bytes, always null-terminated.
uint8_t header_count
Valid entries in headers[].
ParseState parse_state
Current parser state.
size_t path_idx
Write cursor into path[].
size_t content_length
Value of Content-Length header (0 if absent).
size_t body_len
Bytes stored in body[] (≤ BODY_BUF_SIZE).
char path[MAX_PATH_LEN]
URL path, null-terminated; no query string.
size_t body_bytes_read
Body bytes received (may exceed BODY_BUF_SIZE).
size_t query_idx
Write cursor into query[].
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Definition tcp.cpp:347
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Deterministic TLS engine: mbedTLS over a static memory pool (DETWS_ENABLE_TLS).