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
- Variables
- func CheckHost(host string) (string, error)
- func CheckPrefix(prefix string, ownHost bool, reserved func(first string) bool) (string, error)
- func ClientKey(ip string) string
- func Countable(r *http.Request) bool
- func HostSplit(routes *Snapshot, serve func(http.ResponseWriter, *http.Request, Visit)) func(http.Handler) http.Handler
- func OwnAddress(routes *Snapshot, serve func(http.ResponseWriter, *http.Request, Visit)) func(http.Handler) http.Handler
- type Base
- type Dedupe
- type MissLimiter
- type RateLimiter
- type Routes
- type Snapshot
- type Visit
Constants ¶
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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.