librespeed

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 23 Imported by: 0

README

caddy-librespeed

A Caddy module that serves a speed test: the LibreSpeed backend protocol, plus a built-in browser UI. No PHP, no separate server, and no files on disk.

speedtest.example.com {
	librespeed
}

That's the whole site. Open it in a browser and press Start.

Why

LibreSpeed's reference backend is PHP. Each transfer ties up a PHP worker for its whole duration, and every download request generates a fresh random megabyte. This module answers the same protocol from inside Caddy instead:

  • Downloads come from one random 1 MiB buffer, generated at startup and written as many times as the client asks for.
  • Uploads are read and discarded.
  • Nothing else is needed beyond the Caddy binary.

Clients that speak LibreSpeed's protocol work unchanged; for example, librespeed-cli with its usual backend/garbage.php server entries.

Install

xcaddy build --with github.com/cinderblock/caddy-librespeed

Endpoints

The handler matches the last path segment, with or without .php, so it works under any prefix. For example, LibreSpeed's backend/garbage.php layout works unchanged.

Segment Does
garbage / garbage.php GET ?ckSize=N streams N MiB of random data (default 4, at most 1024)
empty / empty.php Reads and discards the request body. Used for the upload test and pings
getIP / getIP.php {"processedString":"<your IP>","rawIspInfo":""}
config.json The UI's server list
anything else The built-in UI (…/ serves index.html), or 404

Notes:

  • Add ?cors to any endpoint to get CORS headers, as with LibreSpeed. The built-in UI always sends it.
  • Responses are never cached.
  • Downloads carry Cache-Control: no-transform, so a stray encode directive (or a CDN) won't compress the test data.
  • getIP reports Caddy's client_ip, so it honours your trusted_proxies setting. It never looks up ISP details from a third party.

Options

librespeed [<matcher>] {
	max_upload_size <size>     # per request; default 64MiB
	ui on|off                  # the built-in UI; default on
	server <name> <url>        # repeatable; see below
}

server adds an entry to the UI's server menu. The UI pings each server when the page loads, so you can pick the closest. Each URL must be absolute and end with /. Other servers must also run this module (or another LibreSpeed backend that honours ?cors). With no server lines, the UI tests the server it was loaded from.

(speedtest) {
	librespeed {
		server "San Francisco" https://speedtest.sf.example.com/
		server "Helsinki"      https://speedtest.hel.example.com/
	}
}

speedtest.sf.example.com {
	import speedtest
}
Under a path prefix
example.com {
	redir /speedtest /speedtest/
	handle /speedtest/* {
		librespeed
	}
}

The UI uses relative URLs, so it needs the trailing slash, which is what the redir adds.

Measuring

The UI runs three tests in order:

  1. Ping and jitter. Ten sequential requests to empty, after one warm-up request. It uses Resource Timing where available.
  2. Download. Six parallel streams for 13 s.
  3. Upload. Three parallel streams of 20 MiB blobs for 13 s.

The first 3 s of each throughput test is a warm-up and isn't counted: TCP slow start and send buffers distort it. The reported speed is the average over the remaining 10 s. That is goodput (payload bytes per second), with no overhead fudge factor.

Development

go test ./...                     # unit tests, plus a real Caddy from a Caddyfile
go test -run '^$' -bench .        # handler benchmarks
xcaddy build --with github.com/cinderblock/caddy-librespeed=.

The tests keep Caddy's state in a temporary directory and never touch your real Caddy config or certificate storage.

The UI is plain HTML, CSS and JavaScript modules in ui/, embedded with go:embed, with no build step. ui/speedtest.js is the measurement engine and doesn't touch the DOM.

License

MIT

Documentation

Overview

Package librespeed is a Caddy HTTP handler that serves a speed test: the LibreSpeed backend protocol (garbage, empty, getIP) plus an embedded UI.

Index

Constants

View Source
const (

	// DefaultMaxUploadSize caps one upload request. LibreSpeed clients send
	// 20 MB blobs, and Cloudflare rejects bodies over 100 MB.
	DefaultMaxUploadSize = 64 << 20
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// MaxUploadSize caps one request body sent to "empty", in bytes.
	// Default: 64 MiB.
	MaxUploadSize int64 `json:"max_upload_size,omitempty"`

	// DisableUI turns off the embedded UI; only the API endpoints answer.
	DisableUI bool `json:"disable_ui,omitempty"`

	// Servers lists the speed test servers the UI offers to test against.
	// Empty means just this one.
	Servers []Server `json:"servers,omitempty"`
	// contains filtered or unexported fields
}

Handler serves a LibreSpeed-compatible speed test.

API endpoints are matched by the request path's last segment, with or without a ".php" suffix, so the handler works at any path prefix:

  • garbage: GET ?ckSize=N streams N MiB of random data (default 4, max 1024)
  • empty: drains the request body (upload test) or answers a ping
  • getIP: the client's IP, as LibreSpeed's JSON

Every other path serves the embedded UI, unless it is disabled.

func (Handler) CaddyModule

func (Handler) CaddyModule() caddy.ModuleInfo

CaddyModule returns the Caddy module information.

func (*Handler) Provision

func (h *Handler) Provision(ctx caddy.Context) error

Provision sets defaults and prepares the UI's config.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error

ServeHTTP implements caddyhttp.MiddlewareHandler. It is a terminal handler: next is never called.

func (*Handler) UnmarshalCaddyfile

func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

UnmarshalCaddyfile sets up the handler from Caddyfile tokens:

librespeed [<matcher>] {
    max_upload_size <size>
    ui on|off
    server <name> <url>
}

func (*Handler) Validate

func (h *Handler) Validate() error

Validate checks the configuration.

type Server

type Server struct {
	// Name is shown in the UI, e.g. "San Francisco".
	Name string `json:"name"`
	// URL is the absolute base URL the handler answers at, e.g.
	// "https://speedtest.example.com/". It must end with "/".
	URL string `json:"url"`
}

Server is one entry in the UI's server menu.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL