static

package
v0.0.0-...-8df018f Latest Latest
Warning

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

Go to latest
Published: Jun 2, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

static — static file server plugin

Serves a static file tree (a built SPA, a docs site, an asset bundle) from the gateway, so a sov binary can host its own frontend with no sidecar nginx/Caddy. One process serves both the API (/rpc/...) and the web app (/).

Use

Single-binary deploy (embed the build):

//go:embed all:dist
var dist embed.FS

func main() {
	gw := sov.New()
	sub, _ := fs.Sub(dist, "dist")          // strip the embed prefix → index.html at root
	gw.Use(static.New(static.Config{
		FS:          sub,
		SPAFallback: true,                    // deep links / client-side routing → index.html
	}))
	gw.Run(ctx, ":8080")
}

Dev / bind-mounted build (serve a directory):

gw.Use(static.New(static.Config{Dir: "./frontend/dist", SPAFallback: true}))

Config

Field Default Meaning
FS fs.FS to serve (e.g. an embed.FS sub-tree). Wins over Dir.
Dir Filesystem dir to serve when FS is nil. One of FS/Dir is required (else New panics).
PathPrefix / Mount point. Leading slash added if missing.
SPAFallback false Serve IndexFile (HTTP 200) for unresolved paths — required for SPA routing. Off → 404.
IndexFile index.html Served for the root and directories; the SPAFallback target.

Behavior

  • Implements gateway.RouteHandler — claims the PathPrefix subtree.
  • Does not shadow the API. Framework endpoints and /rpc/... are matched before plugin routes and win by longest-prefix, so a / mount never captures /rpc/_introspect etc. Pinned by the TestRPCNotShadowed case.
  • Only GET/HEAD are served (else 405).
  • Path traversal (..) is collapsed to the tree root — requests cannot escape the served tree.
  • Content-Type is derived from the file extension, sniffed when unknown.

Documentation

Overview

Package static mounts a static-file tree (a built SPA, a docs site, a bundle of assets) on the gateway via a RouteHandler. It lets a sov binary serve its own frontend — no sidecar nginx/Caddy — so a single-binary deploy can host both the API (/rpc/...) and the web app (/) from one process.

//go:embed all:dist
var dist embed.FS
sub, _ := fs.Sub(dist, "dist")
gw.Use(static.New(static.Config{FS: sub, SPAFallback: true}))

The plugin claims its PathPrefix subtree (default "/"). Framework endpoints and RPC routes are matched BEFORE plugin routes and win by longest-prefix, so mounting at "/" never shadows /rpc/... — see gateway/plugin_interfaces.go (RouteHandler match order). The static_test.go "rpc not shadowed" case pins this.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// FS is the file tree to serve. Use fs.Sub to strip the embed prefix
	// so paths resolve from the tree root (index.html at FS root).
	FS fs.FS
	// Dir is a filesystem directory to serve when FS is nil.
	Dir string
	// PathPrefix is the mount point. Default "/". A leading slash is
	// added if missing; the subtree (prefix) match is derived from it.
	PathPrefix string
	// SPAFallback serves IndexFile (HTTP 200) for any path that does not
	// resolve to a file — required for client-side routing / deep links
	// in a single-page app. Off → unresolved paths 404.
	SPAFallback bool
	// IndexFile is served for the prefix root and for directory paths,
	// and is the SPAFallback target. Default "index.html".
	IndexFile string
	// ReservedPrefixes are request-path prefixes the plugin DECLINES
	// (returns nil) instead of serving, so routing falls through to the
	// gateway's business RPC dispatch. Without this a catch-all "/" mount
	// would shadow every /rpc/{Router}/{method} call. Defaults to
	// {"/rpc/"} — the sov RPC namespace. Set explicitly to override
	// (e.g. add a reverse-proxied API path); pass a non-nil empty slice
	// to reserve nothing.
	ReservedPrefixes []string
}

Config configures the static plugin. Provide exactly one source: FS (e.g. an embed.FS sub-tree, for single-binary deploys) or Dir (a filesystem path, for bind-mounted dev builds). FS wins if both set.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin is the static-file route owner returned by New.

func New

func New(cfg Config) *Plugin

New returns a static plugin from cfg. It panics if neither FS nor Dir is set — a misconfigured asset server should fail at boot, not serve 404s silently.

func (*Plugin) Doc

func (p *Plugin) Doc() string

Doc surfaces a one-line description in /rpc/_introspect + the explorer.

func (*Plugin) PluginName

func (p *Plugin) PluginName() string

PluginName surfaces in /rpc/_introspect.plugins[].

func (*Plugin) RoutePatterns

func (p *Plugin) RoutePatterns() []string

RoutePatterns claims the prefix subtree. A trailing slash → subtree (prefix) match per net/http ServeMux convention; the bare prefix is claimed too so "/app" (no slash) resolves as well as "/app/".

func (*Plugin) ServeRoute

func (p *Plugin) ServeRoute(ctx context.Context, req *gateway.Request) *gateway.Response

ServeRoute resolves req.Path to a file under the configured tree. Only GET/HEAD are served; everything else is 405. Unresolved paths fall back to the index (200) when SPAFallback is set, else 404.

Jump to

Keyboard shortcuts

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