Download raw body.
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files
SRVFLAG_BITS in httpd.h will need to be updated (used for debugging).
Now that I look at it, the values seem to be out of order in -current.
Might as well fix that while you're in there.
Regards
Lloyd
Adam Mullins wrote:
> Whoops. I sent that a little early. I also did not justify it 72
> characters. Sorry about that.
>
> I also meant to add that I commented more heavily than the rest of the
> httpd files. I don't expect these comments to survive review, but I
> wanted to outline my thought process for the reviewer.
>
> The unified diff is included below.
>
> On 2/23/26 9:31 PM, Adam Mullins wrote:
> > Hello. This is my first time contributing to a free software
> > project. Feel free to let me know if I have made any mistakes or
> > missed anything. Sorry for the long email, but I wanted to cover
> > everything.
> >
> > Currently httpd has the ability to preferentially serve gzip encoded
> > static files, but not brotli encoded files. This patch adds the
> > brotli-static option to httpd, allowing it to serve .br files to
> > clients. Brotli offers 10-20% better compression than gzip for common
> > web file types.
> >
> > With this patch, when brotli-static is set in httpd.conf, httpd will
> > serve a *.br file if the client has signaled it will accept it, and
> > the connection is inside of TLS, and the *.br version is no older than
> > the *.gz and unencoded file. Failing that, it will try to serve the
> > gzip version under the same constraints, minus the TLS
> > requirement. Finally, it defaults to serving the unencoded file.
> >
> > Changes:
> > - Update httpd.conf.5 to explain the new option.
> > - Add rules and logic for parsing httpd.conf to parse.y.
> > - Add SRVFLAG_BROTLI_STATIC to httpd.h.
> > - Migrate encoded file selection logic out of
> > server_file.c:server_file_access() to a helper
> > static function, and adds logic to select *.br files if they exist.
> >
> > I was uncertain about a couple of things.
> >
> > First, I #define'd SRVFLAG_BROTLI_STATIC to 0x10000000. This particular
> > number was skipped by the other flags (ie, the flags went from
> > 0x08... to 0x40...). I'm not sure if this constant is being avoided for
> > some reason, but everything seems to be working okay. Also, with this
> > addition we are out of possible 32 bit combinations unless I am
> > missing something.
> >
> > Second, I tried a few approaches to the selection logic flow, but I
> > couldn't get it any more compact. It was taking up a lot of space in the
> > original function, so I migrated it to a helper. I only noticed a few
> > others like this in the project, so I'm not sure if this idiomatic. Also,
> > while my additions check the return of libc calls like fstat() and
> > snprintf() for errors, it _does not_ check that all passed in pointers
> > are non-NULL. This seems reasonable to me given that it is a file-local
> > helper, called from only one function, and that caller likewise does not
> > check its arguments (ie server_file_access does not verify
> > `struct client *clt` is non-NULL before dereferencing it).
>
> cvs server: Diffing .
> Index: httpd.conf.5
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v
> diff -u -p -u -r1.129 httpd.conf.5
> --- httpd.conf.5 18 Jan 2026 16:38:02 -0000 1.129
> +++ httpd.conf.5 24 Feb 2026 02:41:19 -0000
> @@ -464,6 +464,15 @@ Enable static gzip compression to save b
> If gzip encoding is accepted and if the requested file exists with
> an additional .gz suffix, use the compressed file instead and deliver
> it with content encoding gzip.
> +.It Ic brotli-static
> +Enable static brotli compression to save bandwidth.
> +.Pp
> +If brotli encoding is accepted, the requested file exists with a .br
> suffix,
> +and the client is connected with TLS, use the compressed file instead and
> +deliver it with content encoding br.
> +.Pp
> +If both brotli-static and gzip-static are enabled, brotli is preferred
> +if the above conditions are satisfied.
> .It Ic hsts Oo Ar option Oc
> Enable HTTP Strict Transport Security.
> Valid options are:
> Index: httpd.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
> diff -u -p -u -r1.167 httpd.h
> --- httpd.h 28 Nov 2025 16:10:00 -0000 1.167
> +++ httpd.h 24 Feb 2026 02:41:19 -0000
> @@ -390,6 +390,7 @@ SPLAY_HEAD(client_tree, client);
> #define SRVFLAG_NO_PATH_REWRITE 0x02000000
> #define SRVFLAG_GZIP_STATIC 0x04000000
> #define SRVFLAG_NO_BANNER 0x08000000
> +#define SRVFLAG_BROTLI_STATIC 0x10000000
> #define SRVFLAG_LOCATION_FOUND 0x40000000
> #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000
>
> Index: parse.y
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/parse.y,v
> diff -u -p -u -r1.130 parse.y
> --- parse.y 28 Nov 2025 16:10:00 -0000 1.130
> +++ parse.y 24 Feb 2026 02:41:19 -0000
> @@ -141,7 +141,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 BROTLISTATIC
> %token <v.string> STRING
> %token <v.number> NUMBER
> %type <v.port> port
> @@ -561,6 +561,7 @@ serveroptsl : LISTEN ON STRING opttls po
> | fastcgi
> | authenticate
> | gzip_static
> + | brotli_static
> | filter
> | LOCATION optfound optmatch STRING {
> struct server *s;
> @@ -1249,6 +1250,14 @@ gzip_static : NO GZIPSTATIC {
> }
> ;
>
> +brotli_static : NO BROTLISTATIC {
> + srv->srv_conf.flags &= ~SRVFLAG_BROTLI_STATIC;
> + }
> + | BROTLISTATIC {
> + srv->srv_conf.flags |= SRVFLAG_BROTLI_STATIC;
> + }
> + ;
> +
> tcpip : TCP '{' optnl tcpflags_l '}'
> | TCP tcpflags
> ;
> @@ -1455,6 +1464,7 @@ lookup(char *s)
> { "block", BLOCK },
> { "body", BODY },
> { "buffer", BUFFER },
> + { "brotli-static", BROTLISTATIC },
> { "ca", CA },
> { "certificate", CERTIFICATE },
> { "chroot", CHROOT },
> Index: server_file.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/server_file.c,v
> diff -u -p -u -r1.80 server_file.c
> --- server_file.c 29 Apr 2024 16:17:46 -0000 1.80
> +++ server_file.c 24 Feb 2026 02:41:19 -0000
> @@ -55,6 +55,8 @@ int server_file_method(struct client *
> int parse_range_spec(char *, size_t, struct range *);
> int parse_ranges(struct client *, char *, size_t);
> static int select_visible(const struct dirent *);
> +static int server_file_encoded_path(const struct client *, const
> char *,
> + int *, struct stat *);
>
> int
> server_file_access(struct httpd *env, struct client *clt,
> @@ -168,44 +170,10 @@ server_file_access(struct httpd *env, st
> fd, &st, r->kv_value));
> }
>
> - /* change path to path.gz if necessary. */
> - if (srv_conf->flags & SRVFLAG_GZIP_STATIC) {
> - struct http_descriptor *req = clt->clt_descreq;
> - struct http_descriptor *resp = clt->clt_descresp;
> - struct stat gzst;
> - int gzfd;
> - char gzpath[PATH_MAX];
> -
> - /* check Accept-Encoding header */
> - key.kv_key = "Accept-Encoding";
> - r = kv_find(&req->http_headers, &key);
> -
> - if (r != NULL && strstr(r->kv_value, "gzip") != NULL) {
> - /* append ".gz" to path and check existence */
> - ret = snprintf(gzpath, sizeof(gzpath), "%s.gz",
> path);
> - if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
> - close(fd);
> - return (500);
> - }
> -
> - if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
> - /* .gz must be a file, and not older */
> - if (fstat(gzfd, &gzst) != -1 &&
> - S_ISREG(gzst.st_mode) &&
> - timespeccmp(&gzst.st_mtim, &st.st_mtim,
> - >=)) {
> - kv_add(&resp->http_headers,
> - "Content-Encoding", "gzip");
> - /* Use original file timestamp */
> - gzst.st_mtim = st.st_mtim;
> - st = gzst;
> - close(fd);
> - fd = gzfd;
> - } else {
> - close(gzfd);
> - }
> - }
> - }
> + /* Send encoded file if client and server agree. */
> + if((ret = server_file_encoded_path(clt, path, &fd, &st)) != 0) {
> + close(fd);
> + return (ret);
> }
>
> return (server_file_request(env, clt, media, fd, &st));
> @@ -823,4 +791,124 @@ parse_range_spec(char *str, size_t size,
> return (0);
>
> return (1);
> +}
> +
> +/*
> + * If brotli-static is enabled,
> + * and the connection is inside of TLS,
> + * and the file isn't older than the html
> + * and (if gzip-static is enabled, and
> + * the brotli isn't older than the gzip),
> + * then send the brotli version.
> + * Else if gzip-static is enabled,
> + * and the file exists
> + * and it's newer than the html version,
> + * then send the gzip version.
> + * Otherwise send the regular file.
> + */
> +static int
> +server_file_encoded_path(const struct client *clt, const char *path,
> + int *fd, struct stat *st)
> +{
> + struct kv *r, key;
> + struct server_config *srv_conf = clt->clt_srv_conf;
> + int ret;
> + struct http_descriptor *req = clt->clt_descreq;
> + struct http_descriptor *resp = clt->clt_descresp;
> + struct stat brst;
> + int brfd = -1;
> + char brpath[PATH_MAX];
> + struct stat gzst;
> + int gzfd = -1;
> + char gzpath[PATH_MAX];
> +
> + /* did client send Accept-Encoding header? */
> + key.kv_key = "Accept-Encoding";
> + r = kv_find(&req->http_headers, &key);
> +
> + /* if brotli-static is set, client accepts brotli, and
> + * connection is inside tls, set brst & brfd for later use. */
> + if ((srv_conf->flags & SRVFLAG_BROTLI_STATIC) &&
> + r != NULL &&
> + strstr(r->kv_value, "br") != NULL &&
> + clt->clt_tls_ctx != NULL) {
> + /* append .br... */
> + ret = snprintf(brpath, sizeof(brpath), "%s.br", path);
> + if (ret < 0 || (size_t)ret >= sizeof(brpath)) {
> + return (500);
> + }
> + /* ...and check existence. */
> + if ((brfd = open(brpath, O_RDONLY)) != -1) {
> + if(fstat(brfd, &brst) == -1) {
> + /* couldn't stat file, bail */
> + close(brfd);
> + return (500);
> + }
> + }
> + }
> +
> + /* if client accepts gzip */
> + if ((srv_conf->flags & SRVFLAG_GZIP_STATIC) &&
> + r != NULL && strstr(r->kv_value, "gzip") != NULL) {
> + /* append .gz... */
> + ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path);
> + if (ret < 0 || (size_t)ret >= sizeof(gzpath)) {
> + /* brfd might be open here */
> + if (brfd != -1) close(brfd);
> + return (500);
> + }
> + /* ...and check existence. */
> + if ((gzfd = open(gzpath, O_RDONLY)) != -1) {
> + if(fstat(gzfd, &gzst) == -1) {
> + /* brfd might be open here */
> + if (brfd != -1) close(brfd);
> + close(gzfd);
> + return (500);
> + }
> + }
> + }
> +
> + if (brfd != -1 &&
> + S_ISREG(brst.st_mode) &&
> + timespeccmp(&brst.st_mtim, &st->st_mtim, >=) &&
> + (gzfd == -1 || timespeccmp(&brst.st_mtim, &gzst.st_mtim, >=)))
> + {
> + /* if .br is a valid option, is a regular file, not
> older than
> + the base file, and not older than the gzip version (if it
> + exists), then serve the .br file. */
> + kv_add(&resp->http_headers,
> + "Content-Encoding", "br");
> + /* Use original file timestamp */
> + brst.st_mtim = st->st_mtim;
> + /* Point fd at the br version. */
> + *st = brst;
> + close(*fd);
> + *fd = brfd;
> + brfd = -1;
> + } else if (gzfd != -1 &&
> + S_ISREG(gzst.st_mode) &&
> + timespeccmp(&gzst.st_mtim, &st->st_mtim, >=))
> + {
> + /* otherwise, if .gz is a valid option, is a regular
> + file, and not older than the base file, serve it.*/
> + kv_add(&resp->http_headers,
> + "Content-Encoding", "gzip");
> + /* Use original file timestamp */
> + gzst.st_mtim = st->st_mtim;
> + /* Point fd at the gz version. */
> + *st = gzst;
> + close(*fd);
> + *fd = gzfd;
> + gzfd = -1;
> + }
> +
> + /*
> + * It's possible to reach this point with fd, brfd, and gzfd
> + * all open and valid; eg, if *.br and *.gz both exist, but
> + * are older than the uncompressed version.
> + */
> + if (brfd != -1) close(brfd);
> + if (gzfd != -1) close(gzfd);
> +
> + return (0);
> }
>
>
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files
add support to httpd for serving static brotli encoded files