slink

package module
v0.0.0-...-6a2234f Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2022 License: Apache-2.0 Imports: 9 Imported by: 0

README

slink is a link shortening app where the short links are generated privately/internally only.

It’s designed for secure, observable, and scalable production use.

Features

  • Nano ID based generated short link IDs
    • URL-safe default character set
    • custom (ASCII) character set
    • configurable length IDs, can be changed over time
    • avoid generated IDs matching a list of words (denylist)
    • supply your own denylist or use the default
  • Amazon DynamoDB storage backend
    • create your own table (must conform to certain structure, recommended)
    • auto create table if missing (requires permission, mainly for development)
    • works with amazon/dynamodb-local for local development and testing
    • single table pattern based on best practices recommended by The DynamoDB Book
  • Expiring links
    • an optional ExpiresAt can be supplied when creating ShortLink
    • the public server will only redirect when the ShortLink has no expiry or is not yet expired
  • Fallback redirect URL for missing or expired links
    • or respond 404 when the fallback URL is not specified
  • LRU cache for public lookups/redirects (in-memory, per-process only)
  • Built-in Prometheus (operational) metrics and pprof
    • running on a separate debug server in the same processs
    • the debug server is optional, it's off by default
  • Docker image/container based deployment (including Kubernetes)
    • small image size (less than 10MB)
    • separate binaries for public facing and admin servers
    • stateless public and admin apps, easy to horizontally scale
    • configurable runtime behaviour via flags and/or a JSON configuration file
  • Use your own domain
  • Use slink as a library, extend it, build your own
    • supply your own short ID generator
    • supply your own storage backend
    • supply your own tracking mechanism
  • Opinionated reasonable defaults for production

What's missing?

I’m planning to add these (eventually):

  • Kubernetes deployment:
    • documentation
  • tests
  • preventing too many redirects to self
  • more admin APIs:
    • update short link target URL (correct mistake on already published short link, swap target after embargo date, etc)
    • expire short links by ID (due to mistake, abuse, disappearing target, etc)
    • expire short links by URL
  • normalising Link URLs before generating a ShortLink for it
    • lower/upper casing
    • query params ordering

I have no plans to add these:

  • built-in analytics (beyond tracking)
    • it should live outside of slink
    • there's already a way to track
      • can implement your own if you need anything other than SNS
  • web-based admin UI
    • it should live in a separate project
  • CLI binary
    • use curl, etc, to interact with the HTTP API
  • pluggable LRU cache for public lookups/redirects (e.g. Redis)
    • consider DynamoDB DAX so no change is needed in slink
      • with DAX, the LRU Cache in slink is unnecessary but it should be relatively harmless
    • I’m somewhat open to making changes to make it possible to supply your own LRU Cache, if you can convince me with a solid use case 🙂
  • built-in option to auto delete expired ShortLink using DynamoDB TTL
    • storage is relatively cheap
    • create your own table and configure ExpiresAt to be TTL if you need it
  • ability to create short links in the public facing app (like bit.ly for example, allowing anyone to create short links)
    • the security requirements is quite different
    • if you really want to do it, you can create your own public server using slink components
  • multi-tenancy
  • fine-grained permissions
    • any authenticated client that can hit the Admin API can do everything
    • do it elsewhere (proxy or client)

The public and admin servers

TODO: Why?

TODO: What?

Configuration

slink uses ff with JSON config file option for the configuration so that you can specify the configuration either via CLI flags, or via a JSON file, or both (the CLI flags take precedence).

See the FlagSet in cmd/slink-admin-server/main.go for all available options.

Or, you can also do go run cmd/slink-public-server -help.

See the FlagSet in cmd/slink-public-server/main.go for all available options.

Or, you can also do go run cmd/slink-admin-server -help.

Kubernetes Deployment

TODO

Docker

TODO: link to Docker hub.

TODO: update instructions below to use the public docker image.

Run slink-admin-server locally against dynamodb-local (assuming dynamodb-local is running at port 8000 on the Docker host):

docker run --rm -it -p 9090:9090 slink slink-admin-server \
  -dynamodb-endpoint http://host.docker.internal:8000 \
  -aws-access-key-id slink

Run slink-public-server locally against dynamodb-local (assuming dynamodb-local is running at port 8000 on the Docker host):

docker run --rm -it -p 8080:8080 slink slink-public-server \
  -dynamodb-endpoint http://host.docker.internal:8000 \
  -aws-access-key-id slink

Running dynamodb-local

The easiest way to run dynamodb-local is probably by using their public docker image, run as a daemon, with a shared local data directory on the host for persistence. This single container can be used to develop multiple applications at the same time (dynamodb-local uses AWS_ACCESS_KEY_ID to separate the data into different namespaces), so you should only need one per development machine.

docker run -d \
  --name ddblocal
  -p 8000:8000 \
  -v "/path/to/dynamodb-local:/home/dynamodblocal/data"
  amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb -dbPath ./data

See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html for more information and options.

Documentation

Index

Constants

View Source
const DefaultMaxCreateAttempts = 3

Variables

This section is empty.

Functions

func WithIDGenerator

func WithIDGenerator(idgen ids.Generator) func(*Slink)

func WithMaxCreateAttempts

func WithMaxCreateAttempts(maxCreateAttempts int) func(*Slink)

func WithStorage

func WithStorage(storage storage.Storage) func(*Slink)

Types

type CreateInput

type CreateInput struct {
	LinkURL   string `json:"linkUrl"`
	ExpiresAt string `json:"expiresAt,omitempty"`
}

type ErrCreateAttemptsExhausted

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

func (*ErrCreateAttemptsExhausted) Error

type ErrInvalidLinkURL

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

func (*ErrInvalidLinkURL) Error

func (e *ErrInvalidLinkURL) Error() string

type ErrInvalidShortLinkID

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

func (*ErrInvalidShortLinkID) Error

func (e *ErrInvalidShortLinkID) Error() string
type Slink struct {
	// contains filtered or unexported fields
}
func NewSlink(ctx context.Context, options ...func(*Slink)) (*Slink, error)
func (s *Slink) CreateShortLink(ctx context.Context, input *CreateInput) (*models.ShortLink, error)

CreateShortLink unconditionally creates a new ShortLink, even when one with the exact same LinkURL already exists

func (s *Slink) GetOrCreateShortLink(ctx context.Context, input *CreateInput) (*models.ShortLink, error)

GetOrCreateShortLink returns an existing ShortLink if both LinkURL and ExpiresAt match the input, or creates a new ShortLink if no matching ShortLink can be found.

No normalisation is done on LinkURL. `https://example.com?a=1&b=2` is considered different to `https://example.com?b=2&a1`. Only strict exact string match is used for lookups (whatever's supported by the storage backend).

func (*Slink) GetShortLinkByID

func (s *Slink) GetShortLinkByID(ctx context.Context, shortLinkID string) (*models.ShortLink, error)

GetShortLinkByID looks up a ShortLink by its ID, returing it if found, or nil otherwise.

func (*Slink) GetShortLinkByIDWithCache

func (s *Slink) GetShortLinkByIDWithCache(ctx context.Context, shortLinkID string) (*models.ShortLink, error)

GetShortLinkByIDWithCache looks up ShortLink by the given ID from the LRU cache first, if found it returns it, otherwise it looks the ShortLink up in the storage, and returns it if it's found in the storage, adding it to the LRU cache if found, or it returns nil otherwise.

func (*Slink) GetShortLinksByURL

func (s *Slink) GetShortLinksByURL(ctx context.Context, linkURL string) ([]*models.ShortLink, error)

Directories

Path Synopsis
cmd
slink-admin-server
Package main is `slink-admin-server`.
Package main is `slink-admin-server`.
slink-public-server
Package main is `slink-public-server`.
Package main is `slink-public-server`.
Package debug contains prometheus metric definitions, wrapper functions for tracking metrics, and some code to serve the prometheus metrics and pprof via http.
Package debug contains prometheus metric definitions, wrapper functions for tracking metrics, and some code to serve the prometheus metrics and pprof via http.
Package ids covers mainly ID generation
Package ids covers mainly ID generation
Package models contains the data models used in Slink
Package models contains the data models used in Slink
Package storage provides low-level access to storage
Package storage provides low-level access to storage
Package tracker provides a mechanism to track short link lookups/redirects.
Package tracker provides a mechanism to track short link lookups/redirects.

Jump to

Keyboard shortcuts

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