shortlink

package
v0.60.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package shortlink is filex's side of an app's public route (a link shortener's `go.example.com/<code>`): the host split that keeps a route's host of its own to that route, the per-client miss limiter, the link-preview filter, and the live snapshot the certificate questions read.

The app keeps its data and answers each visit; filex holds the invariants around it, whichever app it is (docs/APP-PLUGINS-API.md → Public routes).

Index

Constants

View Source
const (
	MissBurst       = 20
	MissBurstWindow = 10 * time.Minute
	MissBurstLock   = 30 * time.Minute
	MissDaily       = 100
	MissDailyWindow = 24 * time.Hour
	MissDailyLock   = 24 * time.Hour
	// MissesPerDay is the most wrong guesses one client gets in a day.
	MissesPerDay = MissDaily
	// AlarmPerHour misses from every client together raise the alarm once in
	// an hour: many addresses guessing at once is an attack the per-client
	// limit alone does not stop.
	AlarmPerHour = 2000
	// MaxClients is the most clients one window's map holds.
	MaxClients = 200000
)

The limiter's rule. A client is an IPv4 address or an IPv6 /64.

View Source
const (
	HitLimit  = 120
	HitWindow = time.Minute
)

HitLimit visits from one client over this minute and the last reach the app at all, hits and misses alike: one address cannot take the route's whole pool.

View Source
const ClickDedupe = time.Minute

ClickDedupe is how long one client's repeated opening of one link counts once: the rest of the minute it was first counted in, and the next one.

Variables

View Source
var (
	ErrHost         = errors.New("short_link_host_invalid")
	ErrPrefix       = errors.New("short_link_prefix_invalid")
	ErrPrefixInUse  = errors.New("short_link_prefix_reserved")
	ErrPrefixNeeded = errors.New("short_link_prefix_required")
)

Errors a base is refused with. The API answers them as codes.

Functions

func CheckHost

func CheckHost(host string) (string, error)

CheckHost spells a host the way it is stored and compared (lower case, no trailing dot, an internationalised name in its xn-- form) and refuses what cannot be one: an IP address, a port, a single label.

func CheckPrefix

func CheckPrefix(prefix string, ownHost bool, reserved func(first string) bool) (string, error)

CheckPrefix normalises a prefix (no leading or trailing slash, lower case) and refuses one that is not 0-3 segments of `[a-z0-9-]`, an empty one on filex's own address, or one whose first segment is a path filex uses (reserved).

func ClientKey

func ClientKey(ip string) string

ClientKey is the limiter's key for an address: the address itself for IPv4, its /64 for IPv6 (one subscriber holds a whole /64). "" for no address.

func Countable

func Countable(r *http.Request) bool

Countable reports whether r is a person opening the link: a GET, not a prefetch, from a client that does not call itself a previewer or a crawler. A request with no User-Agent at all is not counted either.

func HostSplit

func HostSplit(routes *Snapshot, serve func(http.ResponseWriter, *http.Request, Visit)) func(http.Handler) http.Handler

HostSplit is the router's first word on a short-link host of its own: /healthz goes on (a probe), a short link is served, and everything else is a plain 404 - nothing of filex but its short links is reachable there. Every other host goes on untouched.

⚠ Above the base path: such a host serves its links at ITS root (or its own prefix), whatever FILEX_BASE_PATH says - like a dedicated S3 host.

func OwnAddress

func OwnAddress(routes *Snapshot, serve func(http.ResponseWriter, *http.Request, Visit)) func(http.Handler) http.Handler

OwnAddress serves short links under their prefix on filex's own address (any host HostSplit let through). Below the base path, so the path it reads is the one every route reads.

Types

type Base

type Base struct {
	ID       int64
	Host     string // lower case, ASCII (xn--); "" = filex's own address
	Prefix   string // "l", "go/x"; "" only on a host of its own
	TenantID int64  // the tenant a host of its own belongs to; 0 = the platform
	Enabled  bool
	// App and Route are whose base this is: the app that answers it and the
	// manifest route it was given to.
	App   string
	Route string
}

Base is one address short links are served at.

type Dedupe

type Dedupe struct {
	// Now is the clock; nil = time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

Dedupe remembers the (link, client) pairs counted in the last minute or two. O(1) a visit: two generations, the older dropped whole when the minute turns, each at most MaxClients pairs. Past that a visit is counted without the check: more distinct visitors in a minute than the map holds are counted, never silently dropped.

func (*Dedupe) First

func (d *Dedupe) First(link int64, client string) bool

First reports whether this is the client's first opening of the link in this minute or the last, and remembers it.

type MissLimiter

type MissLimiter struct {
	// Alarm is called (outside the lock) once in an hour when the misses of
	// every client together pass AlarmPerHour; nil = nobody is told.
	Alarm func(misses int)
	// Now is the clock; nil = time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

MissLimiter counts the addresses each client asked for that led nowhere.

func (*MissLimiter) Locked

func (l *MissLimiter) Locked(client string) bool

Locked reports whether the client is held back now.

func (*MissLimiter) Miss

func (l *MissLimiter) Miss(client string) bool

Miss counts one address that led nowhere and answers whether the client is locked out now.

type RateLimiter

type RateLimiter struct {
	Limit  int
	Window time.Duration
	Now    func() time.Time
	// contains filtered or unexported fields
}

RateLimiter is a fixed window per client, O(1) a request (window).

func (*RateLimiter) Allow

func (l *RateLimiter) Allow(client string) bool

Allow counts one request and answers whether it is within the limit. A client the full window does not track is let through: the pool bounds it.

type Routes

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

Routes is a snapshot of the enabled bases, ready to be asked per request.

func NewRoutes

func NewRoutes(bases []Base) *Routes

NewRoutes builds a snapshot. Disabled bases are left out.

func (*Routes) Hosts

func (rt *Routes) Hosts() []string

Hosts are the hosts of their own, for the certificate question.

func (*Routes) Serves

func (rt *Routes) Serves(host string) bool

Serves reports whether host is a short-link host of its own.

type Snapshot

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

Snapshot holds the live Routes; a write replaces it whole.

func Live

func Live() *Snapshot

Live is the instance's live snapshot.

func (*Snapshot) Load

func (s *Snapshot) Load() *Routes

Load answers the current routes (never nil).

func (*Snapshot) Store

func (s *Snapshot) Store(rt *Routes)

Store replaces the routes.

type Visit

type Visit struct {
	Base Base
	// Code is the code's path segment as it arrived, still escaped.
	Code string
	// Preview: the code was followed by `+` - show where the link goes,
	// do not go there.
	Preview bool
}

Visit is one request for a short link, as the path spelled it.

Jump to

Keyboard shortcuts

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