From: Kirill A. Korinsky Subject: httpd: add cache controls for static files To: OpenBSD tech Date: Tue, 05 May 2026 21:58:01 +0200 tech@, I'd like to teach httpd to advertise static file revalidation by default with Cache-Control: no-cache, preserving the existing Last-Modified and If-Modified-Since flow; add a [no] cache directive for opting out, and advertise Vary: Accept-Encoding whenever gzip-static is enabled. Ok? Index: config.c =================================================================== RCS file: /home/cvs/src/usr.sbin/httpd/config.c,v diff -u -p -r1.69 config.c --- config.c 2 Mar 2026 19:24:58 -0000 1.69 +++ config.c 5 May 2026 19:35:45 -0000 @@ -629,6 +629,10 @@ config_getserver_config(struct httpd *en sizeof(srv_conf->path)); } + f = SRVFLAG_CACHE|SRVFLAG_NO_CACHE; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= parent->flags & f; + f = SRVFLAG_SERVER_HSTS; srv_conf->flags |= parent->flags & f; srv_conf->hsts_max_age = parent->hsts_max_age; Index: httpd.conf.5 =================================================================== RCS file: /home/cvs/src/usr.sbin/httpd/httpd.conf.5,v diff -u -p -r1.129 httpd.conf.5 --- httpd.conf.5 18 Jan 2026 16:38:02 -0000 1.129 +++ httpd.conf.5 5 May 2026 19:48:51 -0000 @@ -254,6 +254,18 @@ If is omitted, enable the banner for the current .Ic server if it was disabled globally. +.It Oo Ic no Oc Ic cache +Send +.Dq Cache-Control: no-cache +with static file responses. +When +.Ic gzip-static +is enabled, also send +.Dq Vary: Accept-Encoding +with static file responses. +This is enabled by default; use +.Ic no cache +to disable these headers. .It Ic block drop Drop the connection without sending an error page. .It Ic block Op Ic return Ar code Op Ar uri Index: httpd.h =================================================================== RCS file: /home/cvs/src/usr.sbin/httpd/httpd.h,v diff -u -p -r1.169 httpd.h --- httpd.h 2 Mar 2026 19:24:58 -0000 1.169 +++ httpd.h 5 May 2026 19:41:51 -0000 @@ -390,6 +390,8 @@ SPLAY_HEAD(client_tree, client); #define SRVFLAG_NO_PATH_REWRITE 0x02000000 #define SRVFLAG_GZIP_STATIC 0x04000000 #define SRVFLAG_NO_BANNER 0x08000000 +#define SRVFLAG_CACHE 0x10000000 +#define SRVFLAG_NO_CACHE 0x20000000 #define SRVFLAG_LOCATION_FOUND 0x40000000 #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000 @@ -399,8 +401,8 @@ SPLAY_HEAD(client_tree, client); "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH_REWRITE" \ - "\32NO_PATH_REWRITE\34NO_BANNER\33GZIP_STATIC\37LOCATION_FOUND" \ - "\40LOCATION_NOT_FOUND" + "\32NO_PATH_REWRITE\33GZIP_STATIC\34NO_BANNER\35CACHE" \ + "\36NO_CACHE\37LOCATION_FOUND\40LOCATION_NOT_FOUND" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 Index: parse.y =================================================================== RCS file: /home/cvs/src/usr.sbin/httpd/parse.y,v diff -u -p -r1.131 parse.y --- parse.y 2 Mar 2026 19:24:58 -0000 1.131 +++ parse.y 5 May 2026 19:38:15 -0000 @@ -142,7 +142,7 @@ typedef struct { %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE %token CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT -%token ERRDOCS GZIPSTATIC BANNER +%token ERRDOCS GZIPSTATIC BANNER CACHE %token STRING %token NUMBER %type port @@ -275,7 +275,7 @@ server : SERVER optmatch STRING { SERVER_REQUESTTIMEOUT; s->srv_conf.maxrequests = SERVER_MAXREQUESTS; s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; - s->srv_conf.flags = SRVFLAG_LOG; + s->srv_conf.flags = SRVFLAG_LOG | SRVFLAG_CACHE; if ($2) s->srv_conf.flags |= SRVFLAG_SERVER_MATCH; s->srv_conf.logformat = LOG_FORMAT_COMMON; @@ -558,6 +558,7 @@ serveroptsl : LISTEN ON STRING opttls po | root | directory | banner + | cache | logformat | fastcgi | authenticate @@ -1250,6 +1251,16 @@ gzip_static : NO GZIPSTATIC { } ; +cache : NO CACHE { + srv_conf->flags &= ~SRVFLAG_CACHE; + srv_conf->flags |= SRVFLAG_NO_CACHE; + } + | CACHE { + srv_conf->flags &= ~SRVFLAG_NO_CACHE; + srv_conf->flags |= SRVFLAG_CACHE; + } + ; + tcpip : TCP '{' optnl tcpflags_l '}' | TCP tcpflags ; @@ -1457,6 +1468,7 @@ lookup(char *s) { "body", BODY }, { "buffer", BUFFER }, { "ca", CA }, + { "cache", CACHE }, { "certificate", CERTIFICATE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, Index: server_http.c =================================================================== RCS file: /home/cvs/src/usr.sbin/httpd/server_http.c,v diff -u -p -r1.161 server_http.c --- server_http.c 2 Mar 2026 19:24:58 -0000 1.161 +++ server_http.c 5 May 2026 19:36:23 -0000 @@ -1608,6 +1608,16 @@ server_response_http(struct client *clt, kv_set(cl, "%lld", (long long)size) == -1)) return (-1); + if (srv_conf->flags & SRVFLAG_CACHE) { + if (kv_add(&resp->http_headers, "Cache-Control", + "no-cache") == NULL) + return (-1); + if (srv_conf->flags & SRVFLAG_GZIP_STATIC && + kv_add(&resp->http_headers, "Vary", + "Accept-Encoding") == NULL) + return (-1); + } + /* Set last modification time */ if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 || kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) -- wbr, Kirill