dserve

Static file server in a single binary. Zero config by default, with opt-in features for live reload, TLS, SPA routing, file uploads, directory zip downloads, and a web UI with dark mode.

Overview

  • Language: Go
  • Repo: peteretelej/dserve
  • Install: go install github.com/peteretelej/dserve/v3@latest
  • Status: stable

Architecture

dserve is a flat, single-package binary with no internal packages. Each feature lives in its own file, and all features compose as HTTP middleware or route handlers on a standard http.ServeMux.

File-per-feature layout:

  • main.go - flag parsing, Config assembly, Serve() function that wires up the mux and middleware chain, basic auth, dotfile hiding
  • config.go - Config, TLSConfig, UploadConfig structs
  • live.go - LiveReload type using fsnotify, SSE endpoint at /__livereload, HTML injection middleware
  • compress.go - gzip middleware with sync.Pool for writer reuse, content-type sniffing
  • spa.go - SPA fallback middleware (serves index.html for missing paths)
  • upload.go - multipart upload handler at /__upload, filename sanitization, deduplication
  • zip.go - on-the-fly zip streaming at /__zip, directory traversal prevention
  • tls.go - auto-generated self-signed ECDSA P-256 certs, cached in user config dir
  • ui.go - web UI handler at /__browse/, embeds web/ui.html via //go:embed, serves JSON directory listings

Middleware chain in Serve():

The handler stack is built inside-out: file server -> basic auth -> gzip -> SPA fallback -> live reload injection. Each layer wraps the previous. Special routes (/__livereload, /__upload, /__zip, /__browse/) are registered directly on the mux before the catch-all / route.

Live reload implementation:

Uses Server-Sent Events. The LiveReload struct watches directories with fsnotify, debounces events (100ms), then broadcasts to connected SSE clients via channels. The liveReloadMiddleware intercepts HTML responses, buffers them with a liveResponseRecorder, and injects a <script> tag before </body>.

Single external dependency. The only non-stdlib import is github.com/fsnotify/fsnotify for file watching in live reload mode. Everything else (gzip, TLS cert generation, zip creation, SSE, file serving) uses the standard library.

Key Design Decisions

Flat package, no abstractions. There are no interfaces, no plugin system, no feature registry. Each feature is a standalone .go file that exports one handler or middleware function. This works because features don’t interact with each other, and the flag set in main.go is the only configuration surface.

Features are opt-in flags, not config files. Every feature is disabled by default and enabled by a single flag (-live, -tls, -upload, etc.). This means dserve with no arguments is a dead-simple file server, and complexity is strictly additive.

Auto-generated TLS certificates. When -tls is passed without cert/key paths, dserve generates a self-signed ECDSA P-256 certificate covering localhost, 127.0.0.1, ::1, and all local network IPs. Certs are cached in the user’s config directory so they persist across runs.

Embedded web UI. The ui.html file is embedded at compile time via //go:embed. The handler injects directory listing data as a <script> tag with window.DSERVE={...}, so the HTML is a single self-contained page with no additional requests for the UI shell.

Dotfile protection by default. Root-level dotfiles are hidden and blocked unless -dotfiles is explicitly passed. This is implemented with a custom http.FileSystem wrapper (dotfileHidingFS) that filters Readdir results and a middleware that rejects /. paths.

gzip writer pooling. The compress middleware uses sync.Pool to recycle gzip.Writer instances, avoiding allocation pressure under concurrent load. Range requests bypass compression entirely to preserve seeking behavior.

Development

go build -o dserve .
go test ./...

# Quick test
./dserve --dir . --live --webui