Skip to content

Run multiple apps dispatched by the request Host header

Notifications You must be signed in to change notification settings

ap/Plack-App-Hostname

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NAME

Plack::App::Hostname - Run multiple apps dispatched by the request Host header

SYNOPSIS

Plack::App::Hostname->new
    ->map_hosts_to( $redirector_app => glob '{www.,}example.{com,net,org}' )
    ->map_hosts_to( $content_app    => 'example.org' )
    ->map_hosts_to( $admin_app      => 'edit.example.org' )
    ->map_hosts_to( $tenant_app     => '**.example.org' )
    ->to_app;

DESCRIPTION

This PSGI application dispatches requests to any number of other applications based on the Host request header. This is sometimes referred to as virtual hosting. The mapping as well as the configuration is reconfigurable at runtime with immediate effect. It is fast in the simple case but will accept a custom match callback to support complex scenarios.

One likely "complex" scenario is deployment on a multiprocess forking server, where updates to the mapping only take effect per worker process. Arranging for all workers to update their mappings in lockstep is fiddly but a custom matcher doing lookups against some type of IPC-able table is not.

Instances of this application can be introspected to ask whether a particular hostname can be dispatched. This makes it easy to make other parts of compound PSGI applications adapt to changes in the dynamic mapping.

CONFIGURATION

These are the named arguments you can pass to new. All of these are also accessors if you prefer to write them that way.

default_app

An app that will be called if nothing else matched. You might want to use this for some kind of default page.

If you do not provide this, a 400 error page will be returned for such requests.

missing_header_app

An app that will be called in case the client did not provide a Host header at all. This should only occur with legacy clients that do not speak HTTP/1.1.

If you do not provide this, a 400 error page will be returned for such requests.

custom_matcher

A code ref that will be invoked if the hostname was not found in the internal mapping. You can use this to implement your own dynamic mapping.

The hostname will be passed in $_ and will already be lowercased. The return value should be either a PSGI application code reference or undef.

E.g.

Plack::App:Hostname->new( custom_matcher => sub {
    $dbix_simple
       ->query( 'SELECT COUNT(*) FROM site WHERE hostname = ?', $_ )
       ->into( my $is_known );
    return $is_known ? $customer_site_app : undef;
} );

Or even:

Plack::App:Hostname->new( custom_matcher => sub {
    return unless /^(\d+)\.wat\.info$/;
    my $n = $1;
    sub { [ 200, ['Content-Type','text/html'], ['w'.('a' x $n).'t'] ] }
} );

You can go hog wild here.

METHODS

map_hosts_to

$hostmap->map_hosts_to( $app => 'example.com', 'www.example.com' );

Maps any number of hostnames to an application. Remaps any existing mapping for these hostnames.

You can include a ** wildcard prefix to dispatch that entire (sub)domain to an app, e.g. **.example.org will match foo.example.org bar.example.org foo.bar.example.org foo.bar.baz.example.org etc. Note that this wildcard will not match example.org itself.

The order in which hostnames are added is irrelevant: matches are always tried in order of decreasing specificity. So a matching non-wildcard hostname always takes precedence, and otherwise the longest matching pattern wins.

unmap_app

$hostmap->unmap_app( $admin_app );

Unmaps the given application from all hostnames it is mapped to.

You can pass several applications to unmap at once.

unmap_host

$hostmap->unmap_host( 'www.example.net' );

Unmaps the given hostname.

Wildcards have no special meaning. If you request unmapping **.example.org, only the single entry for **.example.org will be unmapped (if any).

You can pass several hostnames to unmap at once.

to_app

my $app = $hostmap->to_app;

Returns the application code reference for the Plack::App::Hostname instance.

matching

$hostmap->matching( 'www.example.com' );

Returns the application mapped to a known hostname.

It first tries to find an application in its internal mapping, then calls your custom_matcher if one is configured. If both attempts fail, it returns undef.

Note that this will not fall back to your default_app. This makes it usable as a predicate to test whether the Plack::App::Hostname instance in question will serve a given hostname. (It's easy enough to do the fallback explicitly if needed.)

TODO

  • Wildcard matching for a specific number of levels, i.e. *.*.example.com would match foo.bar.example.com but neither www.example.com nor foo.bar.baz.example.com. But an implementation of this must not have undue runtime cost.

Patches welcome.

SEE ALSO

Plack::App::URLMap

Performs a much more complex job: it dispatches not only based on hostname, but also based on the path, modifying the PATH_INFO accordingly. It is much more convenient if you need that, but also much slower to dispatch, and only provides a static mapping that cannot be modified after initialisation.

Plack::App::HostMap

This supports a *. prefix to match all subdomains of a domain, but requires Domain::PublicSuffix for this feature, and includes caching to reduce the overhead incurred by that. The mapping is theoretically modifiable at runtime, but the caching complicates matters: there is no interface to clear it, so you either have to turn it off or monkey with internals.

Plack::App::Vhost

Mappings are specified as a series of patterns to try in order. This is slower than a hash lookup in the simple case, but due to lack of dynamic matching, not flexible enough for complex cases either. I consider this a bad trade-off: you pay extra and gain little; mostly, you just pay extra.