thde.io/rulefiles
GitHub · pkg.go.dev
rulefiles
Go parsers for the _headers and _redirects files of a static site build, the
formats popularised by Netlify and
Cloudflare Pages.
| Package | File | Purpose | Documentation |
|---|---|---|---|
header | _headers | Response header fields for a path | pkg.go.dev/thde.io/rulefiles/header |
redirect | _redirects | URL redirects, rewrites, and errors | pkg.go.dev/thde.io/rulefiles/redirect |
go get thde.io/rulefiles@latestQuick Start
NewHandler reads a rules file and wraps an http.Handler. Handlers compose
with header innermost (around the static file server) and redirect outermost,
so that header rules apply to the rewritten destination of a redirect:
site, err := header.NewHandler(headersFile, http.FileServerFS(build))
if err != nil {
return err
}
site, err = redirect.NewHandler(redirectsFile, site)
if err != nil {
return err
}
For callers that need to inspect or act on rules directly without middleware,
both packages also export Parse and Resolve functions. See the
package documentation for details.
File Formats
_redirects
_redirects holds one <source> <target> [<status>] rule per line, as
documented by
Netlify and
Cloudflare Pages.
The status defaults to 301. A status of 200 rewrites the request internally
instead of redirecting. Query parameters of the request are preserved unless
the target defines them.
/old/path /new/path
/docs/:id/* /articles/:id/:splat 302
/gone / 410_headers
_headers holds an unindented path pattern followed by indented
<name>: <value> or ! <name> lines, as documented by
Netlify and Cloudflare Pages. All
matching rules apply in the order declared; a field set by more than one rule is
joined with a comma. Field names are case insensitive.
/*
X-Robots-Tag: noindex
/static/*
Cache-Control: public, max-age=31536000, immutable
! X-Robots-TagPath Patterns & Placeholders
Both formats share pattern syntax:
- Patterns match against the decoded request path and are case sensitive.
- Named placeholders (
:name) capture individual path segments. - A trailing
*captures the remainder of the path as:splat. - Placeholders expand into the target path, query, and fragment separately with
proper escaping. Placeholders cannot introduce directory traversals (
..). - Comments start with
#at the start of a line or after whitespace.
Differences from Netlify and Cloudflare Pages
- Matching on a scheme, host, query string, country, language, role, or cookie
is not supported. Proxying to another host is supported via
redirect.WithProxyingandredirect.WithProxy. - A trailing
!on a redirect status (e.g.301!) forces a rule to apply even if a file of the same name exists, reported asRule.Force(enabled withredirect.WithExists). - A rule that omits the status redirects with 301 (Netlify default). Cloudflare
defaults to 302; use
redirect.WithDefaultStatusto configure. - By default a trailing slash is ignored on both sides (Netlify default). Use
WithExactTrailingSlash()to match trailing slashes exactly (Cloudflare default). *is only supported as the last segment of a pattern.- Special characters in source paths may be URL-encoded or written as literals.
- Header fields that frame the response (
Content-Length,Transfer-Encoding,Connection,Upgrade, etc.) are rejected withErrUnsupported. - A field declared more than once is joined with
", "(Cloudflare behavior).Set-Cookievalues are retained as separate header lines. - Lines longer than 1 MiB are rejected to bound allocation.
Documentation
Full API documentation, options, and examples are available via go doc or pkg.go.dev:
thde.io/rulefiles: Root module overview, architecture, and pipeline composition.thde.io/rulefiles/header: Header rule parsing, resolution, and response writer middleware.thde.io/rulefiles/redirect: Redirect rule parsing, resolution, proxying, and middleware.
Tests
go test ./...
Both parsers include fuzz tests verifying round-trip encoding and safe resolution:
go test ./redirect -run '^$' -fuzz FuzzResolve
go test ./header -run '^$' -fuzz FuzzResolve
Copy Code
https://thde.io/search_index.en.json
$MATCHES more matches