Why caddy 2 is not able to serve static brotli files?

1. My Caddy version (caddy version):

RC3

It used to be a cool feature? I was just about to switch to caddy 2 but found out that my staticly compiled brotli files for styles and everything are not served. It uses dynamic gzip instead…

Docs say that it can do it, but in my case it doesn’t work

Sorry, which doc?

What’s your Caddyfile?

This doc encode (Caddyfile directive) — Caddy Documentation
My caddyfile is:

{
    email faradaytrs1@gmail.com
    experimental_http3
}

(headers) {
    header Strict-Transport-Security "max-age=31536000;"
    header / {
        X-Frame-Options SAMEORIGIN
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options nosniff
    }
    encode zstd gzip
}

globus.world, globus-china.com, mlchina.ru {
    import headers
    root * /var/www/html/globus/web
    try_files {path} /index.php?p={path}&{query} /index.php?{query}
    php_fastcgi unix//run/php/php7.4-fpm.sock
    file_server
    log {
        output file /var/log/caddy/globus.log {
            roll_size 50mb
            roll_keep 1
        }
    }
}

I ahve precompiled .br files for .css and for .js files, but caddy ignores them.

Why caddy 2 is not able to serve static brotli files?

Well, simply because I haven’t implemented it. :man_shrugging: caddy/modules/caddyhttp/fileserver/staticfiles.go at da8686c4b974ba0b35f3be5a43b6a2f3a528fed2 · caddyserver/caddy · GitHub

(It was kind of a hack in v1.)

You can already do this explicitly though. I think something like this would work:

@brotli {
    file {path}.br
}
handle @brotli {
    header Content-Encoding br
    rewrite * {http.matchers.file.relative}.br
}
file_server

Any plans to implement it again?
I think it can serve .br .gz .zstd files if they are precompiled… why not?

I feel like the docs aren’t wrong here because they don’t actually claim Caddy automatically does it - they advise that you do it. It is possible to do manually.

Very basically achieved like so:

# Find if the request has a .br variant (or .br index variant)
@brotli {
  file {
    try_files {uri}/index.html.br {uri}.br
  }
}

# If it does exist, rewrite to it instead
rewrite @brotli {http.matchers.file.relative}

Edit: Matt beat me to the example. Oh well.

Brotli encoding support was included in Caddy’s core earlier, but it was stripped out for performance reasons. It was done in this commit:

This plugin did actual brotli compression on the fly, but it wasn’t ideal. The better approach is to serve pre-compressed like @Whitestrake and @matt already mentioned

I was asking about precombiled .br files, i never asked about dynamic brotli compression.

You linked encode (Caddyfile directive) — Caddy Documentation which is about dynamic compression. Just clarifying that!

It doesn’t work with my example because i already use try_files.
By some reason when i add your example or Matt’s it just sets encoding to all files to be “br”, but it doesn’t really serve encoded files.

None of your examples work because they somehow confict with try_files.

I came up with this, but it doesn’t work as well.

localhost {
    root * "/Users/imax/PhpstormProjects/globus/web"
    encode gzip zstd
    php_fastcgi 127.0.0.1:9000

    try_files {path}.br {path} /index.php?p={path}&{query} /index.php?{query}
    @brotli {
        expression {http.matchers.file.relative}.endsWith(".br")
    }
    handle @brotli {
        respond "file"
        header Content-Encoding br
    }
    file_server
}

You are a little confused.

There is a difference between:

Your Caddyfile above uses the directive, but the examples Matt and I gave show use of the matcher. You do not need to merge the contents of the file matcher with your existing try_files directive. You can have as many matchers as you want, all matching separate things.

But it doesn’t work lol. rewrite and try_files conflict.

If i follow your logic i can just use it like that:

localhost {
    root * "/Users/imax/PhpstormProjects/globus/web"
    encode gzip zstd
    php_fastcgi 127.0.0.1:9000
    @brotli {
        file {path}.br
    }
    handle @brotli {
        header Content-Encoding br
        rewrite * {http.matchers.file.relative}.br
    }
    try_files {path} /index.php?p={path}&{query} /index.php?{query}

    file_server
}

But it doesn’t work, it sets brotli encoding for html files which clearly don’t exist

Here i have .br files for .css files but i don’t ahve for .js files.
It doesn’t detect it properly, i have no idea how to make it work.

When i try to debug and do this:

localhost {
    root * "/Users/imax/PhpstormProjects/globus/web"
    encode gzip zstd
    php_fastcgi 127.0.0.1:9000
    @brotli {
        file {path}.br
    }
    handle @brotli {
        respond "{file} {http.matchers.file.relative}.br"
        #header Content-Encoding br
        #rewrite @brotli {http.matchers.file.relative}.br
    }
    try_files {path} /index.php?p={path}&{query} /index.php?{query}

    file_server
}

{file} and {http.matchers.file.relative} are both already set to index.php

You are right, it’s not wrong, it’s confusing… especially for people who used it in caddy1

Sorry, it should be:

@brotli {
    file {
        try_files {path}.br
    }
}

I am on my way to sleep so I didn’t test it for you. (I would like the first syntax I gave you to work though, so I’ll submit a PR for that in the morning.)

It doesn’t work, now it’s okay for not-br-precompiled links but for https://localhost/assets/styles/blue/sections/globus.css for example (has .br file nearby) it is decoding failed.

My caddyfile:

localhost {
    root * "/Users/imax/PhpstormProjects/globus/web"
    encode gzip zstd
    php_fastcgi 127.0.0.1:9000
    @brotli {
        file {
            try_files {path}.br
        }
    }
    handle @brotli {
        #respond "{file} {http.matchers.file.relative}.br"
        header Content-Encoding br
        rewrite @brotli {http.matchers.file.relative}.br
    }
    try_files {path} /index.php?p={path}&{query} /index.php?{query}

    file_server
}

Aye. Unlearning convention Caddy 1 invented is a pain point we see all over the place. Ultimately it’s important to remember that Caddy v1 and v2 are, essentially, completely different programs. There’s an absolutely massive amount of feature parity, but they’re just not the same underneath the hood at all.

Implementing automatic precompressed asset serving in Caddy v2 could be a good issue. Currently the closest one that mentions this is v2: Content negotiation · Issue #2665 · caddyserver/caddy · GitHub, but it’s not explicitly for this feature, only some functionality that would make this feature easier.

I actually figured it out, the solution is:

localhost {
    root * "/Users/imax/PhpstormProjects/globus/web"
    encode gzip zstd
    php_fastcgi 127.0.0.1:9000
    @brotli {
        file {
            try_files {path}.br
        }
    }
    handle @brotli {
        header Content-Encoding br
        rewrite * {http.matchers.file.relative}
    }
    try_files {path} /index.php?p={path}&{query} /index.php?{query}

    file_server
}

Thank you everyone

3 Likes

D’oh, you’re right. I added an extra .br to the end of my rewrite. I need some sleep. :sleeping:

Thanks for following up with the solution! Maybe we should make a wiki page here explaining it.

Edit: The file matcher syntax (for most common use cases) will become one-liner soon: file_server: Accept files args in one-liner of Caddyfile matcher by mholt · Pull Request #3298 · caddyserver/caddy · GitHub

2 Likes