NAME

Fugu::Proxy, Fugu::Proxy::Cache, Fugu::Proxy::Meta - a caching HTTP proxy

SYNOPSIS

use Fugu::Proxy;

my $cache = Fugu::Proxy::Cache->new(
    dir       => "$ENV{HOME}/.cache/myapp",
    cacheable => sub ($url) { $url =~ m{/pub/.*\.tgz$} },
);

my $proxy = Fugu::Proxy->new(
    cache   => $cache,
    pidfile => Fugu::Pidfile->new(path => "$dir/proxy.pid"),
    store   => Fugu::StateFile->new(path => "$dir/state.json")->load,
);

my $port = $proxy->start or die $proxy->error . "\n";

DESCRIPTION

This file holds three packages. Fugu::Proxy is the supervisor and the serve loop. Fugu::Proxy::Cache is the URL-to-file store. Fugu::Proxy::Meta is the metadata that makes a cache hit one write instead of a read and a re-encode.

Nothing here knows what content is worth caching. That is a callback on the cache, because the answer is a property of the mirror and not of HTTP.

HTTP::Daemon and LWP::UserAgent load in the child, at serve time. Thus the module keeps the core-Perl load contract of Fugu, and an installation without them still loads it.

THE SUPERVISOR

new

new(%args) creates a supervisor.

These are the arguments:

cache

A Fugu::Proxy::Cache. This argument is necessary.

pidfile

A Fugu::Pidfile for the child. This argument is necessary.

store

A Fugu::StateFile that holds the port. This argument is necessary.

logfile

Where the output of the child goes. The default is /dev/null.

ports

The range to look in, as [first, last]. The default is 8080 to 8180.

log

The logger.

start

start() finds a free port, spawns the child, records its PID and port, and waits until it takes connections. A proxy that already runs returns its port and starts nothing.

stop

stop() stops the child and forgets its port. The stop is a SIGTERM with a grace period, then a SIGKILL, through Fugu::Process.

is_running

is_running() reports if the child is alive. The check reaps, so a child that became a zombie reads as stopped.

port

port() returns the port the running proxy listens on.

wait_ready

wait_ready($timeout) polls until the proxy takes a connection. The default timeout is 30 seconds.

serve

serve($port) runs the server loop. The spawned child calls this, and it returns when a SIGTERM arrives.

The signal handler writes one byte to a self-pipe that the select loop watches. Thus the loop notices the signal between requests and not inside one.

warm

warm() fills the metadata table from what is already on disk. A cold proxy would otherwise stat every file on its first hit.

cache, error

cache() returns the cache object. error() returns the most recent failure.

THE CACHE

new

new(%args) creates a cache.

These are the arguments:

dir

The cache root. This argument is necessary. The mirrored tree goes under <dir>/proxy.

cacheable

A code reference that takes a URL and reports if it is worth keeping. The default says no. A cache that guesses fills a home directory with pages nobody will read again.

types

Extra content types, as a hash reference of pattern to type.

cache_path

cache_path($url) maps a URL to its file: <dir>/proxy/<host>/<path>. The layout is the URL, so a person can find a file and a mirror tree survives a restart.

The method returns undef for a URL that would escape the cache root: no host, an empty path, or a path that walks up. A URL comes from a client, so that check is the boundary.

is_cacheable

is_cacheable($url, $status) asks the callback. Only a status of 200 is a candidate: a cached 404 is a mirror that stays broken after the upstream is fixed.

lookup, store, store_from_file

lookup($url) returns the cached file, or undef.

store($url, $content) writes the content and returns the path. The write is atomic, so a client that reads while another writes never gets a partial file.

store_from_file($url, $source) puts a file that is already on disk into the cache.

size, list, clear

size() returns the total bytes. list() returns every cached file as url, path and size. clear() removes them all and leaves the root.

content_type, path_to_url, dir_size, walk

content_type($path) returns the type from the table. path_to_url($path) rebuilds the URL a cached file came from. dir_size($dir) returns the bytes under a directory. walk($dir, $callback) calls the callback for every regular file under a directory. One walker serves the size, the listing and the metadata warm.

THE METADATA

new, store, lookup

new() creates an empty table.

store($url, $path, $cache) builds the entry: the path, the size, the modification time, the content type and an ETag. The $cache argument names the content type; without it the type is application/octet-stream.

lookup($url) returns the entry, or undef. An entry whose file is gone, or whose size or modification time changed, is not valid and reads as absent. Thus a file that changed under the proxy invalidates its own entry.

count, warm

count() returns the number of entries. warm($cache) builds an entry for every file already in the cache and returns the count.

RETURN VALUES

start() returns the port, or undef with the reason in error(). stop() and serve() return 1. is_running() and wait_ready() return 1 or 0.

store() and cache_path() return a path, or undef. list() returns an array reference.

ERRORS

new() dies when a necessary argument is absent. serve() dies when HTTP::Daemon is missing, and when it cannot listen. Both are startup failures of a child that exists to serve.

No other method dies.

SEE ALSO

Fugu::File, Fugu::Pidfile, Fugu::Process, Fugu::StateFile, HTTP::Daemon, LWP::UserAgent

AUTHORS

Dick Olsson <hi@senzilla.io>

CAVEATS

The cache has no bound of its own and no expiry. Nothing removes an entry until the caller does. A caller whose URLs are version-scoped prunes by version; a caller whose URLs are not needs a policy that this module does not have.

The serve loop handles one client at a time. A large transfer therefore holds the proxy. That is the right trade for a mirror that one machine reads.