I expect those who know me will have raised an eyebrow as they saw my previous blog post. “Guillaume wrote a blog post about go? I thought his whole schtick was rust?”. Don’t worry, I’m still a rust fanboy, and here’s a blog post just to tell you how great it is!
I wrote about writing VMODs for the first time here more than 10 years ago, and I’ve been at it on and off ever since. Things have changed massively and it went from a cute experiment into a full-blown SDK for developers who want to leverage the power of Varnish and extend its VCL capabilities, create their own backends or more generally do useful and weird stuff in a fun language.
It’s high time for a quick recap and a presentation of one of my favorite projects ever.
What’s a VMOD again, and how do I write one?
If you’re new here, here’s the crash course:
-
The Varnish Configuration Language (VCL) is an imperative language that give a lot of freedom to the admin to pilot how Varnish will block, route, process and cache the traffic
-
Because speed matters,
VCLis actually transpiled into (very safe)C, then compiled into a shared library that Varnish loads like a plugin - VCL also has support for
VCLModules[1], or “VMODs”, which can do a lot of things, from s3 signing to load-balancing to random body backends and a lot more
Because VCL is an imperative language, a VMOD doesn’t provide new configuration statements like you’d see in apache, instead you get functions and objects that you can use in your code like you would in python for example:
This makes writing VMODs quite easy to write. I wrote a tutorial a while ago, but the gist of it is:
-
You create a
vccfile that describes the API you want to implement (example), notably the name of the functions, arguments and return types -
This runs a
pythonscript that will generate the boilerplate code and glue for the VMOD in C -
Finally you can start hacking and actually implement the meat of your VCL
To give you an idea, here’s a vcc excerpt for vmod-str:
And the corresponding C code:
Not too hard, but there's a problem.
C kinda sucks
There, I said it. I don’t really mean it, but I said it. C is an incredibly portable language, it’s simple, it’s fast and I think everybody should know the basics about it before programming because it’s an incredible base.
But it’s a gigantic foot gun full of undefined behavior, dangling pointers and null-terminated strings. I can hear the pitchforks being sharpened right now to the tune of “you just need to know what you’re doing!”, but I stand my ground: writing safe C is as hard as writing legible perl, sure it’s possible, but you’re going to slip at some point, and you’re going to fall. Hard.
“But Guillaume, isn’t Varnish written in C?”. You’re bloody right it is! And it’s the right choice for it: it’s incredibly fast, provides amazing control over memory allocation, and very little happens behind your back. That’s perfect for an HTTP reverse-proxy that wants to be extremely scalable and performant.
However, for my plugins, I want something different:
-
I don’t want a language that explodes in production with a grin blaming me because of a stupid mistake
gcccould have caught at compile-time -
I want a rich library ecosystem that I can leverage instead of having to write yet another JSON/YAML converter or a
xoroshirohasher -
Above all, I want proper string support, I’ve had enough of looping through bytes manually, incrementing a pointer until I find a null character that would actually be valid in UTF-8
I could have picked go, zig, or nim, but chance had it that I fell in love with rust for a few reasons:
-
Tooling is good and modern (even though
go‘s is better) -
The FFI story that allows us to hook into C code is decent (yes,
zigis better on that front) -
Once you’ve passed the initial wall of fighting the borrow-checker, the compiler is extremely useful and a joy to work with.
Alright, turning the fanboy mode off, let’s actually dig in.
Ergonomics
The first varnish-rs version copied the C approach: it used a vcc file, a python script to convert it to rust boilerplate and you were on your own dealing with C structures. It was better than pure C, but not enough for Yuri of MapLibre fame who one day dropped in and started making some big changes to truly oxidize varnish-rs. Credit where credit is due, the project would look nothing like it does without him.
Fast forward a few years, and you can reimplement the str.endswith() function from above with stupidly simple rust:
And the documentation is automatically generated in API.md:
The compiler will catch you trying to do silly things (but you can opt-out if you want to), you will get automatic VCL to rust type conversion and you can leverage the full ecosystem of crates.io! You want a function that generates N words of lorem ipsum? Easy, use the lipsum crate and be done with it:
Which can then be used in VCL:
And that’s it! VMODs like vmod-reqwest (HTTP requests and dynamic backends) or vmod-rers (dynamic regular expressions and body manipulation) shamelessly leverage expert crates and focus on providing the necessary glue to be called from VCL.
And backends! And VDPs! And VFPs!
Where I think varnish-rs really shines is in the backend implementation. You can implement them very simply by defining two types:
-
A response struct implementing the VclResponse trait, i.e. what you should send as a body response
-
A backend struct implementing the VclBackend trait, which just sets the response headers and returns a
VclResponsestruct
You can see an implementation of an echo server in the example directory in the main repository, and
vmod-reqwest and vmod-fileserver notably use this API to connect you to dynamic backends and to serve files from disk, but really, the sky is the limit!
But that’s not all, you can create Varnish Fetch/Delivery Processors (VDP/VFP) that won’t generate new data, but instead hook into either the fetch or delivery pipelines of Varnish and modify data on-the-fly. This is for example how vmod-rers can dynamically rewrite your documents before it enters the cache, and/or as it’s being delivered to each user.
Having a whole layer of abstraction here really saves the vmod writer from a bunch of gotchas. Thanks to the type system, we can enforce a lot of rules that C refuses to struggle with and that can only be enforced at runtime. To be fair, the safety net isn’t perfect (yet!) but it has absolutely freed me from worrying about a ton of details that would have surely bitten me later on.
And much much more
While the coverage and capabilities of varnish-rs isn’t equal to the core C framework, it’s close. There are counters, restricted call sites, subroutine calls, optional arguments and pretty much everything you might wish for. But in the end, varnish-rs is just a glue, what matters is what is built on top of it, and how users benefit from it.
So this feels like the perfect segue into two calls of actions. The first one is to join us on discord to discuss your VMODs and tooling ideas around Varnish, or just to chill, that works too!
The second call to action is that if you have a VMOD (rust or C) you’d like to see packaged, you just have to reach out: https://github.com/varnish/pkg-varnish-cache is our central repository that hydrates repositories for Debian, Ubuntu, the Red Hat family and Docker, and we are taking contributions!
[1]: Yes, technically, “VMOD” means “Varnish Configuration Language Module”, but who has the time to pronounce all that?