Help us shape the future of Go! Take the 2026 Go Developer Survey

pluginweb

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 14, 2026 License: GPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package pluginweb mounts every plugin that asked for a route.

One route on the server — /plugin/<name>/... — forwards to the plugin of that name and writes back what it answers. There is no second listener, no port for an operator to open and nothing behind their proxy but this server.

What this package is really for is the boundary. A plugin is somebody else's code running on a team's machine, and giving it a URL means deciding exactly what it may see of a request and what it may say in reply. Those decisions are here rather than spread through the host:

  • The server's own cookies never reach a plugin, and one plugin's cookies never reach another. A plugin sees only cookies whose names begin with its own prefix, and may set only those. Without that rule, installing a leaderboard would hand it the operator's admin session.
  • Who the caller is comes from the server's sessions and never from a header. A plugin is told; it is not asked, and nothing a browser can send becomes an identity.
  • Both bodies are capped, the call has a deadline, and hop-by-hop headers are dropped in both directions. A plugin answers one request; it cannot stream, upgrade or hold a connection.
  • Everything a plugin serves carries nosniff, because a plugin that gets its own content type wrong should serve the wrong thing rather than something a browser decides to execute.

Index

Constants

View Source
const (
	// RequestsPerSecond is the sustained per-address rate.
	RequestsPerSecond = 50
	// RequestBurst is what one address may spend at once — a whole team
	// opening a plugin page at the same moment, through one proxy.
	RequestBurst = 200
	// GlobalRequestsPerSecond and GlobalRequestBurst are the same ceiling over
	// every address at once, so that a limit per address is not simply a limit
	// per address an attacker is willing to rent.
	GlobalRequestsPerSecond = 500
	GlobalRequestBurst      = 1000

	// MaxInFlight is how many plugin calls may be in progress at once. At the
	// body caps in [plugins] this is the memory a plugin's routes can reserve:
	// MaxInFlight × (MaxServeRequestBytes + MaxServeResponseBytes).
	MaxInFlight = 16
	// AcquireGrace is how long a request waits for one of those slots before it
	// is turned away. It is short on purpose. Waiting absorbs a burst that is
	// merely unlucky; waiting long turns a slow plugin into a queue of held
	// connections, which is the thing the cap exists to prevent.
	AcquireGrace = 2 * time.Second
)

What a plugin route costs this server, and what bounds it.

A route declared public is unauthenticated by design — a payment provider's webhook has no session — so the address is reachable by anyone who knows it. Everything else on this server that answers a stranger is bounded already: the setup token, the sign-in form, the device API. This is that same bound for the surface plugins open.

Two different things are capped, because a plugin route can be expensive in two different ways:

  • How often. A token bucket per address, with a burst wide enough for a page that loads its own assets and a refill far below what a script doing it deliberately would want.

    The address is httpx.ClientHost, which does not read X-Forwarded-For because it cannot tell a proxy's header from a caller's. That is the right refusal and it has a cost: behind the operator's own proxy every caller keys to the proxy, and this limit becomes one limit for the whole server. So it is sized for that — generous enough that a team whose drivers all arrive as one address is never touched by it, which leaves it a brake on a script rather than a tight quota. The cap below is what actually bounds what this server will spend, and it does not depend on telling callers apart.

  • How many at once. Each call in progress holds a request body and an answer in memory and occupies the plugin for as long as it takes to answer, which may be plugins.ServeTimeout. Without a ceiling, enough concurrent callers reserve MaxInFlight-free memory and leave the plugin with nothing spare for the operator.

MaxInFlightBytes is what MaxInFlight reserves at worst, for anyone sizing a machine.

View Source
const Prefix = "/plugin/"

Prefix is where every plugin's route is mounted.

Variables

This section is empty.

Functions

This section is empty.

Types

type Caller

type Caller struct {
	DriverSlug string
	DriverName string
	AdminEmail string
}

Caller is what the server knows about whoever made a request, resolved from the server's own sessions.

type Deps

type Deps struct {
	Log *slog.Logger
	// Plugins is the host. A nil one is a server not running plugins, and
	// every route under the prefix answers not-found.
	Plugins Host
	// Settings is the organisation's own settings, for the public address a
	// plugin builds links against.
	Settings func(ctx context.Context) (config.Settings, error)
	// Who resolves the caller from the server's sessions. It must never read a
	// header the caller controls.
	Who func(*http.Request) Caller
	// SignIn establishes a driver session for this browser, by the slug a
	// plugin named. It is how a plugin that authenticates drivers hands the
	// result back: the plugin says who, and this mints the session.
	//
	// Nil is a server that cannot sign drivers in, which answers a plugin that
	// asks with a refusal rather than pretending it worked.
	SignIn func(w http.ResponseWriter, r *http.Request, slug, by string) error
	// SignOut ends whatever driver session this browser has.
	SignOut func(w http.ResponseWriter, r *http.Request) error
}

Deps is what the router needs.

type Host

type Host interface {
	// Access is what a plugin requires of a caller at this path, and whether
	// it serves the path at all.
	Access(name, path string) (plugin.Access, bool)
	// Serve hands one request over.
	Serve(ctx context.Context, name string, r plugin.HTTPRequest) (plugin.HTTPResponse, error)
}

Host is the part of the plugin host this package uses. It is an interface so that the router can be tested against a plugin that answers exactly what a test wants, without a subprocess.

type Router

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

Router serves the plugin routes.

func New

func New(d Deps) (*Router, error)

New builds it.

func (*Router) Routes

func (rt *Router) Routes(mux *http.ServeMux)

Routes mounts the one route this package has.

Jump to

Keyboard shortcuts

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