Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Releases: zopdev/static-server

v1.0.0

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 10 Aug 12:45
c0f09e5

What's Changed

Features

Markdown content negotiation (Accept: text/markdown)

A request whose Accept header names text/markdown is served the markdown sibling of the page when one exists — /about returns about.md instead of about/index.html. Static site generators already emit these files, so no build change is needed in the site being served; if the .md isn't there, the request falls through to HTML untouched.

Agents otherwise have to download the entire HTML document just to discover the <link rel="alternate"> inside it. Measured on a real build of zop.dev, the markdown ran 15–96× smaller than the page it replaces (e.g. /changelog/v1-33-0: 145,354 B → 1,512 B).

Behavior Detail
What counts as asking The media type must be named. Browsers send */*;q=0.8, which matches text/markdown by the letter of RFC 9110 — matching wildcards would serve raw source to every visitor. text/x-markdown is accepted too.
Preference respected q-values are honored: text/html, text/markdown;q=0.1 still gets HTML.
Which URLs negotiate Extensionless routes only. / is always index.html; a path with an extension resolves identically for every client.
Vary: Accept Set on responses that can actually depend on Accept — negotiable routes, their SPA fallback, and every miss. Not set on assets, so a CDN isn't asked to fragment its cache on a header that cannot change the response. A Vary your _headers declares is preserved, not replaced.
Misses A client that asked for markdown gets a 138-byte markdown 404 naming /sitemap.xml and /llms.txt, instead of an HTML error shell it cannot parse (a real site's 404 page measured 144,188 B).
Content-Type Set explicitly for negotiated responses only — the distroless base has no /etc/mime.types and Go's MIME table has no .md entry. A directly requested .md is left alone, so existing .md links don't turn into download prompts.

_headers file support

If the published directory contains a _headers file — the Netlify / Cloudflare Pages convention — its rules are parsed once at startup and applied to matching responses. Generators emit this file expecting the host to honor it; a host that ignores it fails silently while the file looks authoritative in the repo.

Measured against live zop.dev, which had shipped a 2,437-byte _headers for months: not one rule was in effect — no X-Frame-Options, no X-Content-Type-Options, no Referrer-Policy, no Permissions-Policy, and content-hashed /_astro/* bundles served with no Cache-Control at all.

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff

/_astro/*
  Cache-Control: public, max-age=31536000, immutable
Behavior Detail
Matching * matches any run of characters, including /. Patterns are anchored, so /docs/* does not match /other/docs/x. Netlify's :placeholder syntax is not supported.
Precedence Every matching rule contributes, in file order, so a later specific block overrides an earlier catch-all.
Malformed lines Skipped individually — one bad rule doesn't cost the file its other headers.
Errors and misses Rules apply to 404s and the SPA fallback too: a 404 that leaks framing protection is as exploitable as a 200 that does. Cache-Control is the exception — withdrawn from a miss, and from a delegated response returning 4xx/5xx, so a /* cache rule cannot pin a file that is merely un-propagated mid-deploy. The SPA fallback keeps its caching, being a real route rather than a miss.
/.well-known/ Still delegated to the framework so ACME challenges resolve untouched, but the header rules now apply to it — previously that early return skipped them, so a site declaring X-Frame-Options for /* got it everywhere except there.

No _headers file means no rules and no change, so this is inert for sites that don't ship one.

Bug fixes

Config values that were set but empty fell back to nothing

gofr's GetOrDefault only falls back when a key is absent. The shipped configs/.env declares STATIC_DIR_PATH= and DEFAULT_EXTENSION= with empty values, so a deployment supplying STATIC_DIR_PATH through the environment received "" and silently rooted every lookup at the process working directory — serving pages while loading zero _headers rules.

Container shape Before After
default ./static 22 rules ✅ 22 ✅
config-file STATIC_DIR_PATH 22 rules ✅ 22 ✅
env-var STATIC_DIR_PATH 0 rules, pages still served 22 ✅

The default shape was unaffected — by luck, not design. Found by running the actual distroless image rather than a native binary.

Internals

  • The startup log now names the resolved directory alongside the rule count: 0 rules is normal for a site without the file and otherwise indistinguishable from a misrooted path.
  • The response writer is wrapped only on the /.well-known/ path. Doing it on the main serving path would hide net/http's io.ReaderFrom from http.ServeFile and cost every static file its sendfile fast path.

Verification

  • go test -race green. 20 Accept-parsing cases; the _headers suite is built on a verbatim excerpt of the real zop.dev file.
  • Mutation-tested — the suite goes red on each of 16 seeded defects, including treating */* as markdown, ignoring q-values, applying only the first matching rule, dropping pattern anchoring, and removing the Cache-Control withdrawal.
  • Run under golang:1.26 with media-types installed, so /etc/mime.types really contained text/markdown — the platform shape that behaves differently from a dev Mac and from the shipped distroless image.
  • Verified in the real image (gcr.io/distroless/static-debian12) against a 5,978-page build: 16/16 on Content-Type, every _headers rule including on 404s, negotiation, Vary, and the markdown 404.
  • Regression diff of base vs. patched binaries over path × Accept combinations, comparing status, Content-Type, Vary, Cache-Control, X-Frame-Options, Content-Length and body SHA-256: the only changed-or-removed field across the whole matrix is the one intended negotiation. Every other delta is an addition of a header the site's own _headers file declares.

Compatibility

Fully backward compatible, and both features are gated on something the published directory opts into:

  • Markdown negotiation is inert until the served site emits .md siblings — a build that doesn't is byte-for-byte unchanged.
  • _headers does nothing until a directory ships the file.
  • Reverse proxies still win. If something in front of this server sets the same header, its value is what reaches the client — ingress-nginx sends Strict-Transport-Security by default, for instance. Headers with no proxy counterpart take effect immediately.

Upgrading from v0.0.9 with no site changes behaves identically.

Full Changelog: v0.0.9...v1.0.0

v0.0.9

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 22 Apr 06:57
cdc4024

What's Changed

Features

SPA fallback mode (SPA_MODE)

When SPA_MODE=true, extensionless routes that don't match a file now serve index.html with status 200, enabling client-side routing (React Router, Vue Router, etc.) without duplicating index.html as 404.html. Defaults to false — existing setups are unaffected.

Configurable default extension (DEFAULT_EXTENSION)

Replaces the hardcoded .html auto-resolution. Set DEFAULT_EXTENSION=.json (or any extension) to serve extensionless requests from a different file type. Defaults to .html to preserve existing behavior.

Note: only one extension is tried per request. Setting DEFAULT_EXTENSION to a non-html value disables .html auto-resolution. See README for interaction with SPA_MODE.

Multi-platform Docker images (amd64 + arm64)

Images now build for both linux/amd64 and linux/arm64 via Docker buildx with native Go cross-compilation. Apple Silicon users no longer need emulation.

Bug fixes

404 response headers no longer flushed prematurely

The previous handler called w.WriteHeader(404) before http.ServeFile, which flushed headers early and prevented ServeFile from setting Content-Type, Content-Length, and caching headers. A new statusOverrideWriter lets ServeFile set all headers normally and only overrides the status code.

Broader Stat error handling

ServeHTTP now treats any Stat error (permissions, I/O, symlink loop) as "not serveable" and routes to the 404 / SPA fallback, instead of falling through to http.ServeFile with a broken path.

Dockerfile builds all Go files

Build step changed from go build main.go to go build . so handler.go and future files are included.

Refactor & internals

  • Middleware extracted into testable staticFileHandler struct backed by GoFr's file.FileSystem interface.
  • Per-request regexp.MatchString replaced with filepath.Ext — eliminates regex compilation on every request.
  • SPA_MODE parsing uses strconv.ParseBool.
  • Tests replaced non-deterministic go main() + time.Sleep with httptest.NewServer for proper lifecycle.

Dependencies & tooling

  • GoFr upgraded to v1.56.0; replaced deprecated file.New with file.NewLocalFileSystem.
  • GitHub Actions: checkout v4→v6, setup-go v4→v6 (caching default), golangci-lint-action v8→v9.

Compatibility

Fully backward compatible. SPA_MODE defaults to false and DEFAULT_EXTENSION defaults to .html, so services upgrading from v0.0.8 with no env changes behave identically.

Full Changelog: v0.0.8...v0.0.9

v0.0.8

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 10 Mar 05:56
e8d0988

What's Changed

Features

1. Config File Hydration

When the CONFIG_FILE_PATH environment variable is set, the server replaces any ${VAR} placeholders in that file at startup using values from the environment (including .env files). The file is rewritten in-place before serving begins.

This is useful for injecting runtime configuration into static front-end apps without rebuilding them.

If any placeholders have no matching environment variable, the server still writes the file (substituting empty strings for missing values) and logs an error listing the unresolved variables.

Full Changelog: v0.0.7...v0.0.8

v0.0.7

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 09 Mar 06:37
4cb4390

Release Notes

🚀 New Features

  • Added ARM64 support enabling the image to run on ARM-based systems (e.g., Apple Silicon, AWS Graviton).

🔧 Improvements

  • Updated GitHub Action versions to the latest supported releases for improved stability and security.
  • Added a workflow step to print the image registry path, making it easier to identify the published image location during CI runs.

📦 Compatibility

  • Images are now available for both amd64 and arm64 architectures.

v0.0.6

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 25 Feb 21:00
3d03efe

🚀 Release Notes — v0.0.6

🔧 CI & Build

  • Migrated to golangci-lint v2 with caching
  • Improved GitHub Actions workflow (faster builds, cleaner job separation)
  • Docker push now configurable and skips gracefully if credentials are missing

🔐 Security

  • Improved path handling logic
  • Added tests for path validation
  • Updated gosec configuration to handle false positives

🐳 Docker

  • Switched to distroless base image
  • Built fully static binary (CGO_ENABLED=0, stripped flags)

📘 Docs

  • Updated README to reference v0.0.6 tag

Focus: CI reliability, performance improvements, container hardening, and minor security refinements.

v0.0.5

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 24 Feb 11:46
1d7f3b8

Fixes:

Corrected File Serving Priority Between filePath.html and filePath/index.html

  • The server now correctly prioritizes filePath.html if it exists.
  • If filePath.html does not exist but filePath/ is a directory, the server serves filePath/index.html.
  • If neither exists, the server returns 404 Not Found.

v0.0.4

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 19 Feb 09:51
20cc193
  1. Update gofr version to v1.34.0

v0.0.3

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 10 Feb 05:46
150f9b6

What's Changed

  • Exempt gofr's well-known endpoint in static server middleware.

Full Changelog: v0.0.2...v0.0.3

v0.0.2

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 09 Jan 15:46
24a9a10

Enhancements

  1. Removed File Mapping
    The server no longer creates a map of files, streamlining the file-serving process.

  2. 404 Status for Missing Files
    If a requested file is not found, the server now returns a 404 status code, improving error handling.

  3. Default Directory Update
    The default directory for serving static files has been changed to static.

    • To customize this directory set the STATIC_DIR_PATH environment variable.

v0.0.1

Choose a tag to compare

@aryanmehrotra aryanmehrotra released this 27 Nov 17:47
e58debd

Release Notes

  1. Create Static File Server which reads from website directory with the following support.
    • Re-direct to 404.html endpoint when webpage does not exist.
    • Add .html endpoint to urls which does not have any extension.