DeterministicESPAsyncWebServer v7.0.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
httpcache.h
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 httpcache.h
6 * @brief HTTP `Cache-Control` directive builder + parser + freshness helper (RFC 9111),
7 * DWS_ENABLE_HTTP_CACHE.
8 *
9 * The origin-side of edge caching: first-class helpers to emit correct, edge-cacheable
10 * `Cache-Control` responses from app routes (so a device sitting behind a real CDN - or the
11 * library's own future cache tier - is cached correctly), a tolerant parser to read the
12 * directives on a request or an upstream response, and the RFC 9111 sec 4.2.1 freshness-lifetime
13 * calculation. Pure text - build the value with ::cache_control_build and hand it to
14 * DWS::set_cache_control(); no heap, no stdlib, host-testable.
15 *
16 * Directives: RFC 9111 (max-age, s-maxage, no-cache, no-store, no-transform, must-revalidate,
17 * proxy-revalidate, must-understand, private, public) plus the widely-used extensions
18 * `immutable` (RFC 8246) and `stale-while-revalidate` / `stale-if-error` (RFC 5861), and the
19 * request directives a server may want to read (only-if-cached, max-stale, min-fresh).
20 *
21 * This is the standards-mechanics layer only. The caching proxy/tier itself (RAM/SD storage,
22 * cache key, invalidation, mesh replication) is a separate, larger piece still being scoped.
23 *
24 * @author Douglas Quigg (dstroy0)
25 * @date 2026
26 */
27
28#ifndef DETERMINISTICESPASYNCWEBSERVER_HTTPCACHE_H
29#define DETERMINISTICESPASYNCWEBSERVER_HTTPCACHE_H
30
31#include "ServerConfig.h"
32
33#if DWS_ENABLE_HTTP_CACHE
34
35#include <stddef.h>
36#include <stdint.h>
37
38/**
39 * @brief A `Cache-Control` directive set (a superset of request + response directives).
40 *
41 * Flags are presence; each delta-seconds value is -1 when the directive is absent. The optional
42 * field-name lists on `no-cache` / `private` are not captured (presence only). @ref max_stale
43 * uses -1 = absent and -2 = present with no value ("accept any staleness").
44 */
45struct DetwsCacheControl
46{
47 // response cacheability
48 bool cc_public; ///< `public`
49 bool cc_private; ///< `private` (field-name list not captured)
50 bool no_store; ///< `no-store`
51 bool no_cache; ///< `no-cache` (field-name list not captured)
52 bool no_transform; ///< `no-transform`
53 bool must_revalidate; ///< `must-revalidate`
54 bool proxy_revalidate; ///< `proxy-revalidate`
55 bool must_understand; ///< `must-understand`
56 bool cc_immutable; ///< `immutable` (RFC 8246)
57 // request
58 bool only_if_cached; ///< `only-if-cached` (request)
59 // delta-seconds (-1 = absent)
60 int32_t max_age; ///< `max-age=N`
61 int32_t s_maxage; ///< `s-maxage=N`
62 int32_t stale_while_revalidate; ///< `stale-while-revalidate=N` (RFC 5861)
63 int32_t stale_if_error; ///< `stale-if-error=N` (RFC 5861)
64 int32_t max_stale; ///< `max-stale[=N]` (request; -1 absent, -2 no value)
65 int32_t min_fresh; ///< `min-fresh=N` (request)
66};
67
68/** @brief Reset to an empty set (all flags false, all delta-seconds -1). */
69void cache_control_init(DetwsCacheControl *cc);
70
71/**
72 * @brief Build the canonical `Cache-Control` value (no `Cache-Control:` prefix, no CRLF).
73 *
74 * Emits every set directive as a comma-separated list in a stable order. Pass the result to
75 * DWS::set_cache_control().
76 *
77 * @return bytes written (excluding NUL), or 0 on overflow or an empty directive set.
78 */
79size_t cache_control_build(char *buf, size_t cap, const DetwsCacheControl *cc);
80
81/**
82 * @brief Parse a `Cache-Control` header value into @p cc (initializes it first).
83 *
84 * Tolerant: directive names are case-insensitive, unknown directives are ignored, delta-seconds
85 * values may be bare or quoted. Optional field-name lists are recognized but not captured.
86 *
87 * @return true if at least one known directive was parsed.
88 */
89bool cache_control_parse(const char *s, size_t len, DetwsCacheControl *cc);
90
91// --- first-class origin presets (fill @p cc for a common edge-cacheable response) ---
92
93/** @brief Long-lived immutable static asset: `public, max-age=<secs>, immutable`. */
94void cache_immutable_asset(DetwsCacheControl *cc, uint32_t max_age);
95
96/**
97 * @brief Cacheable but served-while-revalidating: `public, max-age=<secs>` plus
98 * `stale-while-revalidate=<swr>` when @p stale_while_revalidate >= 0.
99 */
100void cache_revalidatable(DetwsCacheControl *cc, uint32_t max_age, int32_t stale_while_revalidate);
101
102/** @brief Dynamic / sensitive - never store: `no-store`. */
103void cache_no_store(DetwsCacheControl *cc);
104
105/** @brief Distinct shared-cache TTL: `public, max-age=<browser>, s-maxage=<shared>`. */
106void cache_shared(DetwsCacheControl *cc, uint32_t max_age, uint32_t s_maxage);
107
108/**
109 * @brief Freshness lifetime in seconds (RFC 9111 sec 4.2.1), first-match precedence:
110 * s-maxage (only when @p shared) -> max-age -> @p expires_minus_date (if >= 0) -> heuristic.
111 *
112 * @param shared true for a shared cache (honors s-maxage).
113 * @param expires_minus_date `Expires` minus `Date` in seconds, or < 0 when that pair is absent.
114 * @return the freshness lifetime in seconds, or -1 when none is explicit (caller applies a heuristic).
115 */
116long cache_freshness_lifetime(const DetwsCacheControl *cc, bool shared, long expires_minus_date);
117
118#endif // DWS_ENABLE_HTTP_CACHE
119
120#endif // DETERMINISTICESPASYNCWEBSERVER_HTTPCACHE_H
User-facing configuration for DeterministicESPAsyncWebServer.