Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions frameworks/varnish/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM varnish:9.0.3 AS build

USER root
RUN apt-get update -qq && \
apt-get install -y -qq --no-install-recommends \
build-essential \
ca-certificates \
clang \
curl \
libclang-dev \
media-types \
pkg-config \
varnish-dev
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable -q
ENV PATH="/root/.cargo/bin:${PATH}"

WORKDIR /vmod
COPY vmod/Cargo.toml ./
COPY vmod/src ./src
RUN cargo build --release && \
cp target/release/libvmod_httparena.so /tmp/libvmod_httparena.so

FROM varnish:9.0.3

COPY --from=build /tmp/libvmod_httparena.so /usr/lib/varnish/vmods/libvmod_httparena.so
COPY --from=build /etc/mime.types /etc/varnish/mime.types
COPY default.vcl /etc/varnish/default.vcl
COPY tls.conf /etc/varnish/tls.conf
COPY --chmod=0755 entrypoint.sh /entrypoint.sh

EXPOSE 8080 8443

ENTRYPOINT ["/entrypoint.sh"]
51 changes: 51 additions & 0 deletions frameworks/varnish/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Varnish

Varnish Cache HTTP accelerator with native TLS/H2 termination (`varnishd -A`),
`vmod-fileserver` serving `/static/*` directly from Varnish's own cache, and a
custom vmod (`vmod_httparena`, written in Rust with
[varnish-rs](https://github.com/varnish-rs/varnish-rs)) computing the
`/baseline11`/`/baseline2` sum entirely inside `varnishd` — no separate
backend process at all.

## Stack

- **Engine:** varnishd 9.0 (native TLS via `-A`, HTTP/2 via `feature=+http2`)
- **Static files:** `vmod-fileserver`, rooted at `/data`, using `/etc/mime.types`
for correct `Content-Type` per extension — installed via the Debian
`media-types` package in the build stage and copied into the final image
(the base image ships no `/etc/mime.types` of its own). Older
`vmod-fileserver` versions hard-errored on the first duplicate extension in
a MIME file (and `media-types`' comprehensive file has several), silently
breaking `Content-Type` for every file via `.ok()`; the currently-packaged
version no longer errors on duplicates (last matching line wins instead).
- **Dynamic logic:** `vmod_httparena` (`vmod/`), a small Rust vmod built
against `varnish-dev` headers matching the base image, computing the sum
and reading the POST body directly via `Ctx::req_body` (varnish-rs 0.7.2+)

## Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/pipeline` | GET | Returns `ok` (plain text), answered directly by Varnish via `vcl_synth` |
| `/baseline11` | GET | Sums query parameter values, computed by `httparena.baseline_sum()` |
| `/baseline11` | POST | Sums query parameters + request body |
| `/baseline2` | GET | Same sum logic, over HTTP/2 + TLS (port 8443) |
| `/static/{filename}` | GET | Served by `vmod-fileserver` from `/data/static`, cached by Varnish |

## Notes

- TLS/H2 is terminated natively by `varnishd` itself via `-A /etc/varnish/tls.conf`
(no Hitch/nginx in front) — no HTTP/3/QUIC support, so `baseline-h3`/`static-h3`
are out of scope.
- `/static/*` responses are real Varnish cache objects (served from memory on
repeat requests), not a workaround — this is Varnish's actual value proposition.
- `/baseline11`/`/baseline2` are always answered synthetically (`vcl_recv`
returns `synth(200)`), so there's no backend fetch or caching to reason
about for these routes — each request is computed fresh.
- POST bodies are read directly by the vmod via `Ctx::req_body` — no
`std.cache_req_body()` needed, since nothing downstream (there's no real
backend) needs to read the same body a second time.
- The vmod is built from source in a throwaway Docker build stage (Rust
toolchain + `varnish-dev` headers matching the base image's exact version);
only the compiled `.so` (and `/etc/mime.types`) is copied into the final
image, keeping the shipped image free of the Rust toolchain and apt cache.
33 changes: 33 additions & 0 deletions frameworks/varnish/default.vcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
vcl 4.1;

import fileserver;
import httparena;

backend default none;

sub vcl_init {
new static = fileserver.root("/data", "/etc/varnish/mime.types");
}

sub vcl_recv {
if (req.url ~ "^/static/") {
set req.backend_hint = static.backend();
return (pass);
} else {
return (synth(200));
}
}

sub vcl_synth {
set resp.http.Content-Type = "text/plain";

if (req.url == "/pipeline") {
synthetic("ok");
} else if (req.url ~ "^/baseline(11|2)(\?|$)") {
synthetic(httparena.baseline_sum());
} else {
set resp.status = 404;
}

return (deliver);
}
12 changes: 12 additions & 0 deletions frameworks/varnish/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh
set -e

exec varnishd -F \
-a :8080 \
-A /etc/varnish/tls.conf \
-f /etc/varnish/default.vcl \
-p feature=+http2 \
-p thread_pool_max=10000 \
-p thread_pool_min=5000 \
-p feature=+http2 \
-s malloc,256m
18 changes: 18 additions & 0 deletions frameworks/varnish/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"display_name": "Varnish",
"language": "C",
"engine": "varnishd",
"type": "infrastructure",
"description": "Varnish Cache HTTP accelerator with native TLS/H2 termination (-A). A custom vmod (Rust, varnish-rs) computes /baseline11 and /baseline2 in-process; vmod-fileserver serves /static.",
"repo": "https://github.com/varnish/varnish",
"enabled": true,
"tests": [
"baseline",
"pipelined",
"limited-conn",
"static",
"baseline-h2",
"static-h2"
],
"maintainers": ["guillaume.quintard@varnish-software.com"]
}
8 changes: 8 additions & 0 deletions frameworks/varnish/tls.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
frontend = {
host = "0.0.0.0"
port = "8443"
pem-file = {
cert = "/certs/server.crt"
private-key = "/certs/server.key"
}
}
10 changes: 10 additions & 0 deletions frameworks/varnish/vmod/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "vmod-httparena"
version = "0.1.0"
edition = "2021"

[dependencies]
varnish = "0.7"

[lib]
crate-type = ["cdylib"]
86 changes: 86 additions & 0 deletions frameworks/varnish/vmod/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::io::Write;

use varnish::vcl::StrOrBytes;

fn as_str(s: StrOrBytes<'_>) -> Option<&str> {
match s {
StrOrBytes::Utf8(s) => Some(s),
StrOrBytes::Bytes(_) => None,
}
}

/// The body is just an integer (e.g. "20"); a stack buffer with a
/// comfortable margin avoids a heap allocation. Excess bytes are dropped.
struct FixedBuf {
data: [u8; 32],
len: usize,
}

impl Write for FixedBuf {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = buf.len().min(self.data.len() - self.len);
self.data[self.len..self.len + n].copy_from_slice(&buf[..n]);
self.len += n;
Ok(n)
}

fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

fn parse_query_sum(url: &str) -> i64 {
let qs = match url.split_once('?') {
Some((_, q)) => q,
None => return 0,
};
qs.split('&')
.filter_map(|pair| pair.split_once('='))
.filter_map(|(_, v)| v.trim().parse::<i64>().ok())
.sum()
}

/// HttpArena benchmark helper: compute the /baseline11 and /baseline2 sum
/// (query params + optional POST body) entirely inside Varnish.
#[varnish::vmod]
mod httparena {
use varnish::vcl::{Ctx, VclError};

use super::{as_str, parse_query_sum};

/// Sum the integer values of all query-string parameters, plus the
/// request body for POST requests.
pub fn baseline_sum(ctx: &mut Ctx) -> Result<String, VclError> {
let (url, is_post) = {
let req = ctx
.http_req
.as_ref()
.ok_or("baseline_sum: no client request available")?;
let url = as_str(req.url().ok_or("baseline_sum: request has no URL")?)
.ok_or("baseline_sum: URL is not valid UTF-8")?
.to_string();
let is_post = req.method().and_then(as_str) == Some("POST");
(url, is_post)
};

let mut sum = parse_query_sum(&url);

if is_post {
let mut buf = super::FixedBuf {
data: [0u8; 32],
len: 0,
};
if ctx.req_body(&mut buf).is_ok() {
if let Ok(n) = std::str::from_utf8(&buf.data[..buf.len])
.unwrap_or_default()
.trim()
.parse::<i64>()
{
sum += n;
}
}
}

Ok(sum.to_string())
}
}
7 changes: 7 additions & 0 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,13 @@
"type": "engine",
"engine": "epoll"
},
"Varnish": {
"dir": "varnish",
"description": "Varnish Cache HTTP accelerator with native TLS/H2 termination (-A). A custom vmod (Rust, varnish-rs) computes /baseline11 and /baseline2 in-process; vmod-fileserver serves /static.",
"repo": "https://github.com/varnish/varnish",
"type": "infrastructure",
"engine": "varnishd"
},
"veb": {
"dir": "veb",
"description": "veb is the web framework in V's standard library, now running on the parallel fasthttp backend (multi-threaded, SO_REUSEPORT epoll, zero-copy append handler). Its deterministic request framing reassembles TCP-fragmented requests and decodes chunked bodies, so baseline is subscribed (the old single-threaded backend could not). JSON responses are built without per-request reflection (precomputed item prefixes + manual serialization); json-comp gzips on Accept-Encoding with a process-shared cache; static assets are served via veb's static handler (sendfile + MIME) mounted at /static/; crud is an in-memory cache-aside (X-Cache MISS/HIT, invalidated on update); fortunes renders the DB rows + a runtime row with HTML escaping; async-db/crud/fortunes use the stdlib db.pg pooled Go-style DB. Built on pinned V master 84a76d791 (the fasthttp vanilla-architecture refactor, vlang/v#27771) with the default GC (Boehm). json-tls is not subscribed: it needs a second TLS listener on :8081 (a separate OpenSSL path) not wired here.",
Expand Down
Loading