root/trunk/lib/Perlbal/Plugin/Vhosts.pm

Revision 824, 5.2 kB (checked in by hachi, 2 months ago)

Rudimentary dumpconfig command, 90% working.

This reverts commit c5dc669c6250b46f82a6cc4da38f45be7f45c296.

(Yes, that would be a double-revert that I am doing.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1###########################################################################
2# plugin to do name-based virtual hosts
3###########################################################################
4
5# things to test:
6#   one persistent connection, first to a docs plugin, then to web proxy... see if it returns us to our base class after end of request
7#   PUTing a large file to a selector, seeing if it is put correctly to the PUT-enabled web_server proxy
8#   obvious cases:  non-existent domains, default domains (*), proper matching (foo.brad.lj before *.brad.lj)
9#
10
11package Perlbal::Plugin::Vhosts;
12
13use strict;
14use warnings;
15no  warnings qw(deprecated);
16
17our %Services;  # service_name => $svc
18
19# when "LOAD" directive loads us up
20sub load {
21    my $class = shift;
22
23    Perlbal::register_global_hook('manage_command.vhost', sub {
24        my $mc = shift->parse(qr/^vhost\s+(?:(\w+)\s+)?(\S+)\s*=\s*(\w+)$/,
25                              "usage: VHOST [<service>] <host_or_pattern> = <dest_service>");
26        my ($selname, $host, $target) = $mc->args;
27        unless ($selname ||= $mc->{ctx}{last_created}) {
28            return $mc->err("omitted service name not implied from context");
29        }
30
31        my $ss = Perlbal->service($selname);
32        return $mc->err("Service '$selname' is not a selector service")
33            unless $ss && $ss->{role} eq "selector";
34
35        $host = lc $host;
36        return $mc->err("invalid host pattern: '$host'")
37            unless $host =~ /^[\w\-\_\.\*\;\:]+$/;
38
39        $ss->{extra_config}->{_vhosts} ||= {};
40        $ss->{extra_config}->{_vhosts}{$host} = $target;
41
42        return $mc->ok;
43    });
44    return 1;
45}
46
47# unload our global commands, clear our service object
48sub unload {
49    my $class = shift;
50
51    Perlbal::unregister_global_hook('manage_command.vhost');
52    unregister($class, $_) foreach (values %Services);
53    return 1;
54}
55
56# called when we're being added to a service
57sub register {
58    my ($class, $svc) = @_;
59    unless ($svc && $svc->{role} eq "selector") {
60        die "You can't load the vhost plugin on a service not of role selector.\n";
61    }
62
63    $svc->selector(\&vhost_selector);
64    $svc->{extra_config}->{_vhosts} = {};
65
66    $Services{"$svc"} = $svc;
67    return 1;
68}
69
70# called when we're no longer active on a service
71sub unregister {
72    my ($class, $svc) = @_;
73    $svc->selector(undef);
74    delete $Services{"$svc"};
75    return 1;
76}
77
78sub dumpconfig {
79    my ($class, $svc) = @_;
80
81    my $vhosts = $svc->{extra_config}->{_vhosts};
82
83    return unless $vhosts;
84
85    my @return;
86
87    while (my ($vhost, $target) = each %$vhosts) {
88        push @return, "VHOST $vhost = $target";
89    }
90
91    return @return;
92}
93
94# call back from Service via ClientHTTPBase's event_read calling service->select_new_service(Perlbal::ClientHTTPBase)
95sub vhost_selector {
96    my Perlbal::ClientHTTPBase $cb = shift;
97
98    my $req = $cb->{req_headers};
99    return $cb->_simple_response(404, "Not Found (no reqheaders)") unless $req;
100
101    my $vhost = $req->header("Host");
102
103    # Browsers and the Apache API considers 'www.example.com.' == 'www.example.com'
104    $vhost and $vhost =~ s/\.$//;
105
106    my $uri = $req->request_uri;
107    my $maps = $cb->{service}{extra_config}{_vhosts} ||= {};
108
109    # ability to ask for one host, but actually use another.  (for
110    # circumventing javascript/java/browser host restrictions when you
111    # actually control two domains).
112    if ($vhost && $uri =~ m!^/__using/([\w\.]+)(?:/\w+)(?:\?.*)?$!) {
113        my $alt_host = $1;
114
115        # update our request object's Host header, if we ended up switching them
116        # around with /__using/...
117        my $svc_name = $maps->{"$vhost;using:$alt_host"};
118        my $svc = $svc_name ? Perlbal->service($svc_name) : undef;
119        unless ($svc) {
120            $cb->_simple_response(404, "Vhost twiddling not configured for requested pair.");
121            return 1;
122        }
123
124        $req->header("Host", $alt_host);
125        $svc->adopt_base_client($cb);
126        return 1;
127    }
128
129    # returns 1 if done with client, 0 if no action taken
130    my $map_using = sub {
131        my ($match_on, $force) = @_;
132
133        my $map_name = $maps->{$match_on};
134        my $svc = $map_name ? Perlbal->service($map_name) : undef;
135
136        return 0 unless $svc || $force;
137
138        unless ($svc) {
139            $cb->_simple_response(404, "Not Found (no configured vhost)");
140            return 1;
141        }
142
143        $svc->adopt_base_client($cb);
144        return 1;
145    };
146
147    #  foo.site.com  should match:
148    #      foo.site.com
149    #    *.foo.site.com  -- this one's questionable, but might as well?
150    #        *.site.com
151    #        *.com
152    #        *
153
154    # if no vhost, just try the * mapping
155    return $map_using->("*", 1) unless $vhost;
156
157    # Strip off the :portnumber, if any
158    $vhost =~ s/:\d+$//;
159
160    # try the literal mapping
161    return if $map_using->($vhost);
162
163    # and now try wildcard mappings, removing one part of the domain
164    # at a time until we find something, or end up at "*"
165
166    # first wildcard, prepending the "*."
167    my $wild = "*.$vhost";
168    return if $map_using->($wild);
169
170    # now peel away subdomains
171    while ($wild =~ s/^\*\.[\w\-\_]+/*/) {
172        return if $map_using->($wild);
173    }
174
175    # last option: use the "*" wildcard
176    return $map_using->("*", 1);
177}
178
1791;
Note: See TracBrowser for help on using the browser.