Changeset 1308

Show
Ignore:
Timestamp:
10/16/09 01:54:12 (6 weeks ago)
Author:
dormando
Message:

Upgrade perlbal mogdep to 1.73 and update MANIFEST

Location:
trunk/server
Files:
11 added
24 modified

Legend:

Unmodified
Added
Removed
  • trunk/server/MANIFEST

    r1279 r1308  
    3939lib/mogdeps/Perlbal/Plugin/Stats.pm 
    4040lib/mogdeps/Perlbal/Plugin/Vhosts.pm 
     41lib/mogdeps/Perlbal/Plugin/AtomInject.pm 
     42lib/mogdeps/Perlbal/Plugin/AtomStream.pm 
     43lib/mogdeps/Perlbal/Plugin/Cgilike.pm 
     44lib/mogdeps/Perlbal/Plugin/EchoService.pm 
     45lib/mogdeps/Perlbal/Plugin/Include.pm 
     46lib/mogdeps/Perlbal/Plugin/LazyCDN.pm 
     47lib/mogdeps/Perlbal/Plugin/MaxContentLength.pm 
     48lib/mogdeps/Perlbal/Plugin/Palimg.pm 
     49lib/mogdeps/Perlbal/Plugin/Redirect.pm 
     50lib/mogdeps/Perlbal/Plugin/Vpaths.pm 
    4151lib/mogdeps/Perlbal/Pool.pm 
    4252lib/mogdeps/Perlbal/ReproxyManager.pm 
     
    4959lib/mogdeps/Perlbal/UploadListener.pm 
    5060lib/mogdeps/Perlbal/Util.pm 
     61lib/mogdeps/Perlbal/SocketSSL.pm 
    5162lib/mogdeps/Sys/Syscall.pm 
    5263lib/MogileFS/Class.pm 
  • trunk/server/lib/mogdeps/Perlbal.pm

    r1087 r1308  
    11#!/usr/bin/perl 
    22# 
    3 # Copyright 2004, Danga Interactice, Inc. 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     3# Copyright 2004, Danga Interactive, Inc. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55# 
    66 
     
    1515=head1 COPYRIGHT AND LICENSE 
    1616 
    17 Copyright 2004, Danga Interactice, Inc. 
    18 Copyright 2005-2006, Six Apart, Ltd. 
     17Copyright 2004, Danga Interactive, Inc. 
     18Copyright 2005-2007, Six Apart, Ltd. 
    1919 
    2020You can use and redistribute Perlbal under the same terms as Perl itself. 
     
    2626BEGIN { 
    2727    # keep track of anonymous subs' origins: 
    28     $^P = 0x200; 
     28    $^P |= 0x200; 
    2929} 
    3030 
     
    3434 
    3535use vars qw($VERSION); 
    36 $VERSION = '1.59'; 
     36$VERSION = '1.73'; 
    3737 
    3838use constant DEBUG => $ENV{PERLBAL_DEBUG} || 0; 
     
    5454# incremented every second by a timer: 
    5555$Perlbal::tick_time = time(); 
     56 
     57# Set to 1 when we open syslog, and 0 when we close it 
     58$Perlbal::syslog_open = 0; 
    5659 
    5760use Getopt::Long; 
     
    9093our(%plugins);   # plugin => 1 (shows loaded plugins) 
    9194our($last_error); 
     95our $service_autonumber = 1; # used to generate names for anonymous services created with Perlbal->create_service() 
    9296our $vivify_pools = 1; # if on, allow automatic creation of pools 
    9397our $foreground = 1; # default to foreground 
     
    9599our $reqs = 0; # total number of requests we've done 
    96100our $starttime = time(); # time we started 
     101our $pidfile = '';  # full path, default to not writing pidfile 
     102# used by pidfile (only makes sense before run started) 
     103# don't rely on this variable, it might change. 
     104our $run_started = 0;   
    97105our ($lastutime, $laststime, $lastreqs) = (0, 0, 0); # for deltas 
    98106 
     
    186194    my $class = shift; 
    187195    return $service{$_[0]}; 
     196} 
     197 
     198sub create_service { 
     199    my $class = shift; 
     200    my $name = shift; 
     201     
     202    unless (defined($name)) { 
     203        $name = "____auto_".($service_autonumber++); 
     204    } 
     205 
     206    croak("service '$name' already exists") if $service{$name}; 
     207    croak("pool '$name' already exists") if $pool{$name}; 
     208 
     209    # Create the new service and return it 
     210    return $service{$name} = Perlbal::Service->new($name); 
    188211} 
    189212 
     
    241264    # expand variables 
    242265    $cmd =~ s/\$\{(.+?)\}/_expand_config_var($1)/eg; 
     266    $cmd =~ s/\$(\w+)/$ENV{$1}/g; 
    243267 
    244268    $out ||= sub {}; 
     
    381405 
    382406sub MANAGE_shutdown { 
    383     my $mc = shift->parse(qr/^shutdown( graceful)?$/); 
     407    my $mc = shift->parse(qr/^shutdown(\s?graceful)?\s?(\d+)?$/); 
    384408 
    385409    # immediate shutdown 
     
    415439    }); 
    416440 
     441    # If requested, register a callback to kill the perlbal process after a specified number of seconds 
     442    if (my $timeout = $mc->arg(2)) { 
     443        Perlbal::Socket::register_callback($timeout, sub { exit(0); }); 
     444    } 
     445 
    417446    # so they know something happened 
    418447    return $mc->ok; 
     448} 
     449 
     450sub MANAGE_mime { 
     451    my $mc = shift->parse(qr/^mime(?:\s+(\w+)(?:\s+(\w+))?(?:\s+(\S+))?)?$/); 
     452    my ($cmd, $arg1, $arg2) = ($mc->arg(1), $mc->arg(2), $mc->arg(3)); 
     453 
     454    if (!$cmd || $cmd eq 'list') { 
     455        foreach my $key (sort keys %$Perlbal::ClientHTTPBase::MimeType) { 
     456            $mc->out("$key $Perlbal::ClientHTTPBase::MimeType->{$key}"); 
     457        } 
     458        $mc->end; 
     459    } elsif ($cmd eq 'set') { 
     460        if (!$arg1 || !$arg2) { 
     461            return $mc->err("Usage: set <ext> <mime>"); 
     462        } 
     463 
     464        $Perlbal::ClientHTTPBase::MimeType->{$arg1} = $arg2; 
     465        return $mc->out("$arg1 set to $arg2."); 
     466    } elsif ($cmd eq 'remove') { 
     467        if (delete $Perlbal::ClientHTTPBase::MimeType->{$arg1}) { 
     468            return $mc->out("$arg1 removed."); 
     469        } else { 
     470            return $mc->err("$arg1 not a defined extension."); 
     471        } 
     472    } else { 
     473        return $mc->err("Usage: list, remove <ext>, add <ext> <mime>"); 
     474    } 
    419475} 
    420476 
     
    632688        foreach (sort { $a <=> $b } keys %$sf) { 
    633689            my $sock = $sf->{$_}; 
    634             my $age = $now - $sock->{create_time}; 
     690            my $age; 
     691            eval { 
     692                $age = $now - $sock->{create_time}; 
     693            }; 
     694            $age ||= 0; 
    635695            $mc->out(sprintf("%5d %5ds %s", $_, $age, $sock->as_string)); 
    636696        } 
     
    708768    my %states; # { "Class" => { "State" => int count; } } 
    709769    foreach my $sock (values %$sf) { 
     770        next unless $sock->can('state'); 
    710771        my $state = $sock->state; 
    711772        next unless defined $state; 
     
    735796        next unless $svc->{role} eq 'reverse_proxy'; 
    736797 
    737         my ($age, $count) = (0, scalar(@{$svc->{waiting_clients}})); 
    738         my Perlbal::ClientProxy $oldest = $svc->{waiting_clients}->[0]; 
    739         $age = $now - $oldest->{last_request_time} if defined $oldest; 
    740         $mc->out("$svc->{name}-normal.age $age"); 
    741         $mc->out("$svc->{name}-normal.count $count"); 
    742  
    743         ($age, $count) = (0, scalar(@{$svc->{waiting_clients_highpri}})); 
    744         $oldest = $svc->{waiting_clients_highpri}->[0]; 
    745         $age = $now - $oldest->{last_request_time} if defined $oldest; 
    746         $mc->out("$svc->{name}-highpri.age $age"); 
    747         $mc->out("$svc->{name}-highpri.count $count"); 
     798        my %queues = ( 
     799            normal  => 'waiting_clients', 
     800            highpri => 'waiting_clients_highpri', 
     801            lowpri  => 'waiting_clients_lowpri', 
     802        ); 
     803 
     804        while (my ($queue_name, $clients_key) = each %queues) { 
     805            my $age = 0; 
     806            my $count = @{$svc->{$clients_key}}; 
     807            my Perlbal::ClientProxy $oldest = $svc->{$clients_key}->[0]; 
     808            $age = $now - $oldest->{last_request_time} if defined $oldest; 
     809            $mc->out("$svc->{name}-$queue_name.age $age"); 
     810            $mc->out("$svc->{name}-$queue_name.count $count"); 
     811        } 
    748812    } 
    749813    $mc->end; 
     
    767831    my $mc = shift->parse(qr/^leaks(?:\s+(.+))?$/); 
    768832    return $mc->err("command disabled without \$ENV{PERLBAL_DEBUG} set") 
    769         unless $ENV{PERBAL_DEBUG}; 
     833        unless $ENV{PERLBAL_DEBUG}; 
    770834 
    771835    my $what = $mc->arg(1); 
     
    891955    } 
    892956 
     957    if ($key eq "pidfile") { 
     958        return $mc->err("pidfile must be configured at startup, before Perlbal::run is called") if  $run_started; 
     959        return $mc->err("Expected full pathname to pidfile") unless $val; 
     960        $pidfile = $val; 
     961        return $mc->ok; 
     962    } 
     963 
     964    if ($key eq "crash_backtrace") { 
     965        return $mc->err("Expected 1 or 0") unless $val eq '1' || $val eq '0'; 
     966        if ($val) { 
     967            $SIG{__DIE__} = sub { Carp::confess(@_) }; 
     968        } else { 
     969            $SIG{__DIE__} = undef; 
     970        } 
     971        return $mc->ok; 
     972    } 
     973 
    893974    return $mc->err("unknown server option '$val'"); 
     975} 
     976 
     977sub MANAGE_dumpconfig { 
     978    my $mc = shift; 
     979 
     980    while (my ($name, $pool) = each %pool) { 
     981        $mc->out("CREATE POOL $name"); 
     982 
     983        if ($pool->can("dumpconfig")) { 
     984            foreach my $line ($pool->dumpconfig) { 
     985                $mc->out("  $line"); 
     986            } 
     987        } else { 
     988            my $class = ref($pool); 
     989            $mc->out("  # Pool class '$class' is unable to dump config."); 
     990        } 
     991    } continue { 
     992        $mc->out(""); 
     993    } 
     994 
     995    while (my ($name, $service) = each %service) { 
     996        $mc->out("CREATE SERVICE $name"); 
     997 
     998        if ($service->can("dumpconfig")) { 
     999            foreach my $line ($service->dumpconfig) { 
     1000                $mc->out("  $line"); 
     1001            } 
     1002        } else { 
     1003            my $class = ref($service); 
     1004            $mc->out("  # Service class '$class' is unable to dump config."); 
     1005        } 
     1006 
     1007        my $state = $service->{enabled} ? "ENABLE" : "DISABLE"; 
     1008        $mc->out("$state $name"); 
     1009    } continue { 
     1010        $mc->out(""); 
     1011    } 
     1012 
     1013    return $mc->ok 
    8941014} 
    8951015 
     
    9081028        return $mc->err("service '$name' already exists") if $service{$name}; 
    9091029        return $mc->err("pool '$name' already exists") if $pool{$name}; 
    910         $service{$name} = Perlbal::Service->new($name); 
     1030        Perlbal->create_service($name); 
    9111031        $mc->{ctx}{last_created} = $name; 
    9121032        return $mc->ok; 
     
    10901210sub load_config { 
    10911211    my ($file, $writer) = @_; 
    1092     open (F, $file) or die "Error opening config file ($file): $!\n"; 
     1212    open (my $fh, $file) or die "Error opening config file ($file): $!\n"; 
    10931213    my $ctx = Perlbal::CommandContext->new; 
    10941214    $ctx->verbose(0); 
    1095     while (my $line = <F>) { 
    1096         $line =~ s/\$(\w+)/$ENV{$1}/g; 
     1215    while (my $line = <$fh>) { 
    10971216        return 0 unless run_manage_command($line, $writer, $ctx); 
    10981217    } 
    1099     close(F); 
     1218    close($fh); 
    11001219    return 1; 
    11011220} 
     
    11181237        unless $sess_id = POSIX::setsid(); 
    11191238 
    1120     ## Prevent possibility of acquiring a controling terminal 
     1239    ## Prevent possibility of acquiring a controlling terminal 
    11211240    $SIG{'HUP'} = 'IGNORE'; 
    11221241    if ($pid = fork) { exit 0; } 
     
    11391258} 
    11401259 
     1260# For other apps using Danga::Socket that want to embed Perlbal, this can be called 
     1261# directly to start it up. You can call this as many times as you like; it'll 
     1262# only actually do what it does the first time it's called. 
     1263sub initialize { 
     1264    unless ($run_started) { 
     1265        $run_started = 1; 
     1266 
     1267        # number of AIO threads.  the number of outstanding requests isn't 
     1268        # affected by this 
     1269        IO::AIO::min_parallel(3)    if $Perlbal::OPTMOD_IO_AIO; 
     1270 
     1271        # register IO::AIO pipe which gets written to from threads 
     1272        # doing blocking IO 
     1273        if ($Perlbal::OPTMOD_IO_AIO) { 
     1274            Perlbal::Socket->AddOtherFds(IO::AIO::poll_fileno() => 
     1275                                         \&IO::AIO::poll_cb); 
     1276        } 
     1277 
     1278        # The fact that this only runs the first time someone calls initialize() 
     1279        # means that some things which depend on it might be unreliable when 
     1280        # used in an embedded perlbal if there is a race for multiple components 
     1281        # to call initialize(). 
     1282        run_global_hook("pre_event_loop"); 
     1283    } 
     1284} 
     1285 
     1286# This is the function to call if you want Perlbal to be in charge of the event loop. 
     1287# It won't return until Perlbal is somehow told to exit. 
    11411288sub run { 
     1289 
    11421290    # setup for logging 
    11431291    Sys::Syslog::openlog('perlbal', 'pid', 'daemon') if $Perlbal::SYSLOG_AVAILABLE; 
     1292    $Perlbal::syslog_open = 1; 
    11441293    Perlbal::log('info', 'beginning run'); 
    1145  
    1146     # number of AIO threads.  the number of outstanding requests isn't 
    1147     # affected by this 
    1148     IO::AIO::min_parallel(3)    if $Perlbal::OPTMOD_IO_AIO; 
    1149  
    1150     # register IO::AIO pipe which gets written to from threads 
    1151     # doing blocking IO 
    1152     if ($Perlbal::OPTMOD_IO_AIO) { 
    1153         Perlbal::Socket->AddOtherFds(IO::AIO::poll_fileno() => 
    1154                                      \&IO::AIO::poll_cb); 
    1155     } 
    1156  
     1294    my $pidfile_written = 0; 
     1295    $pidfile_written = _write_pidfile( $pidfile ) if $pidfile; 
     1296 
     1297    Perlbal::initialize(); 
    11571298 
    11581299    Danga::Socket->SetLoopTimeout(1000); 
     
    11631304    }); 
    11641305 
    1165     run_global_hook("pre_event_loop"); 
    1166  
    11671306    # begin the overall loop to try to capture if Perlbal dies at some point 
    11681307    # so we can have a log of it 
     
    11791318        $clean_exit = 0; 
    11801319    } 
     1320 
     1321    # Note: This will only actually remove the pidfile on 'shutdown graceful' 
     1322    # A more reliable approach might be to have a pidfile object which fires 
     1323    # removal on DESTROY. 
     1324    _remove_pidfile( $pidfile ) if $pidfile_written; 
     1325 
    11811326    Perlbal::log('info', 'ending run'); 
     1327    $Perlbal::syslog_open = 0; 
    11821328    Sys::Syslog::closelog() if $Perlbal::SYSLOG_AVAILABLE; 
    11831329 
     
    11891335    if ($foreground) { 
    11901336        # syslog acts like printf so we have to use printf and append a \n 
    1191         shift; # ignore the first parameter (info, warn, critical, etc) 
     1337        shift; # ignore the first parameter (info, warn, crit, etc) 
    11921338        printf(shift(@_) . "\n", @_); 
    11931339    } else { 
    11941340        # just pass the parameters to syslog 
    1195         Sys::Syslog::syslog(@_) if $Perlbal::SYSLOG_AVAILABLE; 
    1196     } 
    1197 } 
     1341        Sys::Syslog::syslog(@_) if $Perlbal::syslog_open; 
     1342    } 
     1343} 
     1344 
     1345 
     1346sub _write_pidfile { 
     1347    my $file = shift; 
     1348 
     1349    my $fh; 
     1350    unless (open($fh, ">$file")) { 
     1351        Perlbal::log('info', "couldn't create pidfile '$file': $!" ); 
     1352        return 0; 
     1353    } 
     1354    unless ((print $fh "$$\n") && close($fh)) { 
     1355        Perlbal::log('info', "couldn't write into pidfile '$file': $!" ); 
     1356        _remove_pidfile($file); 
     1357        return 0; 
     1358    } 
     1359    return 1; 
     1360} 
     1361 
     1362 
     1363sub _remove_pidfile { 
     1364    my $file = shift; 
     1365     
     1366    unlink $file; 
     1367    return 1; 
     1368} 
     1369 
    11981370 
    11991371# Local Variables: 
  • trunk/server/lib/mogdeps/Perlbal/AIO.pm

    r1087 r1308  
    11# AIO abstraction layer 
    22# 
    3 # Copyright 2004, Danga Interactice, Inc. 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     3# Copyright 2004, Danga Interactive, Inc. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55 
    66package Perlbal::AIO; 
  • trunk/server/lib/mogdeps/Perlbal/BackendHTTP.pm

    r1087 r1308  
    22# HTTP connection to backend node 
    33# 
    4 # Copyright 2004, Danga Interactice, Inc. 
    5 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2004, Danga Interactive, Inc. 
     5# Copyright 2005-2007, Six Apart, Ltd. 
    66# 
    77 
     
    3939            'generation', # int; counts what generation we were spawned in 
    4040            'buffered_upload_mode', # bool; if on, we're doing a buffered upload transmit 
     41 
     42            'scratch' # for plugins 
    4143            ); 
    4244use Socket qw(PF_INET IPPROTO_TCP SOCK_STREAM SOL_SOCKET SO_ERROR 
     
    6264#       reportto => object obeying reportto interface 
    6365sub new { 
    64     my ($class, $svc, $ip, $port, $opts) = @_; 
     66    my Perlbal::BackendHTTP $self = shift; 
     67    my ($svc, $ip, $port, $opts) = @_; 
    6568    $opts ||= {}; 
    6669 
     
    7275        return undef; 
    7376    } 
     77    my $inet_aton = Socket::inet_aton($ip); 
     78    unless ($inet_aton) { 
     79        Perlbal::log('crit', "inet_aton failed creating socket for $ip"); 
     80        return undef; 
     81    } 
    7482 
    7583    IO::Handle::blocking($sock, 0); 
    76     connect $sock, Socket::sockaddr_in($port, Socket::inet_aton($ip)); 
    77  
    78     my $self = fields::new($class); 
     84    connect $sock, Socket::sockaddr_in($port, $inet_aton); 
     85 
     86    $self = fields::new($self) unless ref $self; 
    7987    $self->SUPER::new($sock); 
    8088 
     
    105113    $self->init; 
    106114 
    107     bless $self, ref $class || $class; 
    108115    $self->watch_write(1); 
    109116    return $self; 
     
    167174 
    168175    $self->init; 
    169     bless $self, ref $class || $class; 
    170176    $self->watch_write(1); 
    171177    return $self; 
     
    265271    if ($svc->trusted_ip($client_ip)) { 
    266272        # yes, we trust our upstream, so just append our client's IP 
    267         # to the existing list of forwarded IPs 
    268         my @ips = split /,\s*/, ($hds->header("X-Forwarded-For") || ''); 
    269         $hds->header("X-Forwarded-For", join ", ", @ips, $client_ip); 
     273        # to the existing list of forwarded IPs, if we're a blind proxy 
     274        # then don't append our IP to the end of the list. 
     275        unless ($svc->{blind_proxy}) { 
     276            my @ips = split /,\s*/, ($hds->header("X-Forwarded-For") || ''); 
     277            $hds->header("X-Forwarded-For", join ", ", @ips, $client_ip); 
     278        } 
    270279    } else { 
    271280        # no, don't trust upstream (untrusted client), so remove all their 
     
    343352            !$self->{has_attention} && !defined $NoVerify{$self->{ipport}}) { 
    344353 
     354            return if $self->{service}->run_hook('backend_write_verify', $self); 
     355 
    345356            # the backend should be able to answer this incredibly quickly. 
    346             $self->write("OPTIONS * HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"); 
     357            $self->write("OPTIONS " . $self->{service}->{verify_backend_path} . " HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"); 
    347358            $self->watch_read(1); 
    348359            $self->{waiting_options} = 1; 
     
    368379} 
    369380 
     381sub verify_success { 
     382    my Perlbal::BackendHTTP $self = shift; 
     383    $self->{waiting_options} = 0; 
     384    $self->{has_attention} = 1; 
     385    $NodeStats{$self->{ipport}}->{verifies}++; 
     386    $self->next_request(1); # initial 
     387    return; 
     388} 
     389 
    370390sub verify_failure { 
    371391    my Perlbal::BackendHTTP $self = shift; 
     
    378398sub event_read_waiting_options { # : void 
    379399    my Perlbal::BackendHTTP $self = shift; 
     400 
     401    if (defined $self->{service}) { 
     402        return if $self->{service}->run_hook('backend_readable_verify', $self); 
     403    } 
     404 
    380405    if ($self->{content_length_remain}) { 
    381406        # the HTTP/1.1 spec says OPTIONS responses can have content-lengths, 
     
    394419    # if present: 
    395420    if ($self->{res_headers} && ! $self->{content_length_remain}) { 
    396         # other setup to mark being done with options checking 
    397         $self->{waiting_options} = 0; 
    398         $self->{has_attention} = 1; 
    399         $NodeStats{$self->{ipport}}->{verifies}++; 
    400         $self->next_request(1); # initial 
     421        $self->verify_success; 
    401422    } 
    402423    return; 
     
    454475    if ((my $rep = $hd->header('X-REPROXY-FILE')) && $self->may_reproxy) { 
    455476        # make the client begin the async IO while we move on 
     477        $self->next_request; 
    456478        $client->start_reproxy_file($rep, $hd); 
     479        return; 
     480    } elsif ((my $urls = $hd->header('X-REPROXY-URL')) && $self->may_reproxy) { 
    457481        $self->next_request; 
    458         return; 
    459     } elsif ((my $urls = $hd->header('X-REPROXY-URL')) && $self->may_reproxy) { 
    460482        $self->{service}->add_to_reproxy_url_cache($rqhd, $hd) 
    461483            if $reproxy_cache_for; 
    462         $client->start_reproxy_uri($self->{res_headers}, $urls); 
     484        $client->start_reproxy_uri($hd, $urls); 
     485        return; 
     486    } elsif ((my $svcname = $hd->header('X-REPROXY-SERVICE')) && $self->may_reproxy) { 
    463487        $self->next_request; 
    464         return; 
    465     } elsif ((my $svcname = $hd->header('X-REPROXY-SERVICE')) && $self->may_reproxy) { 
    466488        $self->{client} = undef; 
    467         $client->start_reproxy_service($self->{res_headers}, $svcname); 
    468         $self->next_request; 
     489        $client->start_reproxy_service($hd, $svcname); 
    469490        return; 
    470491    } elsif ($res_code == 500 && 
     
    500521        # also update the response code, in case of 206 partial content 
    501522        my $rescode = $hd->response_code; 
    502         $thd->code($rescode) if $rescode == 206 || $rescode == 416; 
     523        if ($rescode == 206 || $rescode == 416) { 
     524            $thd->code($rescode); 
     525            $thd->header('Accept-Ranges', $hd->header('Accept-Ranges')) if $hd->header('Accept-Ranges'); 
     526            $thd->header('Content-Range', $hd->header('Content-Range')) if $hd->header('Content-Range'); 
     527        }  
    503528        $thd->code(200) if $thd->response_code == 204;  # upgrade HTTP No Content (204) to 200 OK. 
    504529    } 
  • trunk/server/lib/mogdeps/Perlbal/ClientHTTP.pm

    r1087 r1308  
    33#  most functionality is implemented in the base class. 
    44# 
    5 # Copyright 2004, Danga Interactice, Inc. 
    6 # Copyright 2005-2006, Six Apart, Ltd. 
     5# Copyright 2004, Danga Interactive, Inc. 
     6# Copyright 2005-2007, Six Apart, Ltd. 
    77# 
    88 
     
    1313 
    1414use base "Perlbal::ClientHTTPBase"; 
     15use Perlbal::Util; 
    1516 
    1617use fields ('put_in_progress', # 1 when we're currently waiting for an async job to return 
     
    4748    my $class = shift; 
    4849    my Perlbal::ClientHTTPBase $cb = shift;    # base object 
    49     bless $cb, $class; 
     50    Perlbal::Util::rebless($cb, $class); 
    5051    $cb->init; 
    5152 
     
    102103    my $hd = $self->{req_headers}; 
    103104 
     105    $self->check_req_headers; 
     106 
    104107    # fully formed request received 
    105108    $self->{requests}++; 
     
    143146    # bigger than any specified max put size 
    144147    return $self->send_response(400, "Content-length of $clen is invalid.") 
    145         if !$clen || 
     148        if ! defined($clen) || 
     149        $clen < 0 || 
    146150        ($self->{service}->{max_put_size} && 
    147151         $clen > $self->{service}->{max_put_size}); 
    148152 
    149     # if we have some data already from a header over-read, note it 
    150     if (defined $self->{read_ahead} && $self->{read_ahead} > 0) { 
     153    # if we are supposed to read data and have some data already from a header over-read, note it 
     154    if ($clen && defined $self->{read_ahead} && $self->{read_ahead} > 0) { 
    151155        $self->{content_length_remain} -= $self->{read_ahead}; 
    152156    } 
     
    225229 
    226230    my $disk_path = $self->{service}->{docroot} . '/' . $path; 
    227     $self->start_put_open($disk_path, $filename); 
    228231 
    229232    $self->{chunked_upload_state} = Perlbal::ChunkedUploadState->new(%{{ 
     
    265268        }, 
    266269    }}); 
     270 
     271    $self->start_put_open($disk_path, $filename); 
    267272 
    268273    return 1; 
     
    393398        $self->{put_fh_filename} = "$path/$file"; 
    394399 
     400        # We just opened the file, haven't read_ahead any bytes, are expecting 0 bytes for read and we're 
     401        # not in chunked mode, so close the file immediately, we're done. 
     402        unless ($self->{read_ahead} || $self->{content_length_remain} || $self->{chunked_upload_state}) { 
     403            # FIXME this should be done through AIO 
     404            $self->put_close; 
     405            return; 
     406        } 
     407 
    395408        $self->put_writeout; 
    396409    }); 
  • trunk/server/lib/mogdeps/Perlbal/ClientHTTPBase.pm

    r1087 r1308  
    88# for another request from the user 
    99# 
    10 # Copyright 2004, Danga Interactice, Inc. 
    11 # Copyright 2005-2006, Six Apart, Ltd. 
     10# Copyright 2004, Danga Interactive, Inc. 
     11# Copyright 2005-2007, Six Apart, Ltd. 
    1212 
    1313package Perlbal::ClientHTTPBase; 
     
    4040 
    4141use Fcntl ':mode'; 
    42 use Errno qw( EPIPE ECONNRESET ); 
     42use Errno qw(EPIPE ECONNRESET); 
    4343use POSIX (); 
    4444 
    45 # ghetto hard-coding.  should let siteadmin define or something. 
    46 # maybe console/config command:  AddMime <ext> <mime-type>  (apache-style?) 
     45# hard-code defaults can be changed with MIME management command 
    4746our $MimeType = {qw( 
    4847                    css  text/css 
     
    5554                    mp3  audio/mpeg 
    5655                    mpg  video/mpeg 
     56                    pdf  application/pdf 
    5757                    png  image/png 
    5858                    tif   image/tiff 
     
    6565# ClientHTTPBase 
    6666sub new { 
    67     my ($class, $service, $sock, $selector_svc) = @_; 
    68  
    69     my $self = $class; 
    70     $self = fields::new($class) unless ref $self; 
     67 
     68    my Perlbal::ClientHTTPBase $self = shift; 
     69    my ($service, $sock, $selector_svc) = @_; 
     70    $self = fields::new($self) unless ref $self; 
    7171    $self->SUPER::new($sock);       # init base fields 
    7272 
     
    8080    $self->state('reading_headers'); 
    8181 
    82     bless $self, ref $class || $class; 
    8382    $self->watch_read(1); 
    8483    return $self; 
     
    125124    if ($do_keepalive) { 
    126125        print "  doing keep-alive to client\n" if Perlbal::DEBUG >= 3; 
    127         my $timeout = $self->max_idle_time; 
     126        my $timeout = $self->{service}->{persist_client_timeout}; 
    128127        $reshd->header('Connection', 'keep-alive'); 
    129128        $reshd->header('Keep-Alive', $timeout ? "timeout=$timeout, max=100" : undef); 
     
    137136        $reshd->header('Keep-Alive', undef); 
    138137    } 
     138} 
     139 
     140# overridden here from Perlbal::Socket to use the service value 
     141sub max_idle_time { 
     142    return $_[0]->{service}->{persist_client_timeout}; 
     143} 
     144 
     145# Called when this client is entering a persist_wait state, but before we are returned to base. 
     146sub persist_wait { 
     147     
    139148} 
    140149 
     
    189198    $self->state('persist_wait'); 
    190199 
     200    $self->persist_wait; 
     201 
    191202    if (my $selector_svc = $self->{selector_svc}) { 
    192         $selector_svc->return_to_base($self); 
     203        if (! $selector_svc->run_hook('return_to_base', $self)){ 
     204            $selector_svc->return_to_base($self); 
     205        } 
    193206    } 
    194207 
     
    210223        $self->{reproxy_file_offset} = 0; 
    211224        $self->{reproxy_file_size} = $size; 
    212         # call hook that we're reproxying a file 
    213         return $fh if $self->{service}->run_hook("start_send_file", $self); 
    214         # turn on writes (the hook might not have wanted us to) 
    215         $self->watch_write(1); 
    216         return $fh; 
     225 
     226        my $is_ssl_webserver = ( $self->{service}->{listener}->{sslopts} && 
     227                               ( $self->{service}->{role} eq 'web_server') ); 
     228 
     229        unless ($is_ssl_webserver) { 
     230            # call hook that we're reproxying a file 
     231            return $fh if $self->{service}->run_hook("start_send_file", $self); 
     232            # turn on writes (the hook might not have wanted us to) 
     233            $self->watch_write(1); 
     234            return $fh; 
     235        } else { # use aio_read for ssl webserver instead of sendfile 
     236 
     237            print "webserver in ssl mode, sendfile disabled!\n" 
     238                if $Perlbal::DEBUG >= 3; 
     239 
     240            # turn off writes 
     241            $self->watch_write(0); 
     242            #create filehandle for reading 
     243            my $data = ''; 
     244            Perlbal::AIO::aio_read($self->reproxy_fh, 0, 2048, $data, sub { 
     245                # got data? undef is error 
     246                return $self->_simple_response(500) unless $_[0] > 0; 
     247 
     248                # seek into the file now so sendfile starts further in 
     249                my $ld = length $data; 
     250                sysseek($self->{reproxy_fh}, $ld, &POSIX::SEEK_SET); 
     251                $self->{reproxy_file_offset} = $ld; 
     252                # reenable writes after we get data 
     253                $self->tcp_cork(1); # by setting reproxy_file_offset above, 
     254                                    # it won't cork, so we cork it 
     255                $self->write($data); 
     256                $self->watch_write(1); 
     257            }); 
     258            return 1; 
     259        } 
    217260    } 
    218261 
     
    222265sub event_read { 
    223266    my Perlbal::ClientHTTPBase $self = shift; 
     267 
     268    $self->{alive_time} = $Perlbal::tick_time; 
    224269 
    225270    # see if we have headers? 
     
    236281    # handle it yet.  must wait for the selector (which has as much 
    237282    # time as it wants) to route as to our subclass, which can then 
    238     # renable reads. 
     283    # re-enable reads. 
    239284    $self->watch_read(0); 
    240285 
     
    257302} 
    258303 
     304sub reproxy_file_done { 
     305    my Perlbal::ClientHTTPBase $self = shift; 
     306    return if $self->{service}->run_hook('reproxy_fh_finished', $self); 
     307    # close the sendfile fd 
     308    CORE::close($self->{reproxy_fh}); 
     309    $self->{reproxy_fh} = undef; 
     310    if (my $cb = $self->{post_sendfile_cb}) { 
     311        $cb->(); 
     312    } else { 
     313        $self->http_response_sent; 
     314    } 
     315} 
     316 
    259317# client is ready for more of its file.  so sendfile some more to it. 
    260318# (called by event_write when we're actually in this mode) 
     
    264322    my $remain = $self->{reproxy_file_size} - $self->{reproxy_file_offset}; 
    265323    $self->tcp_cork(1) if $self->{reproxy_file_offset} == 0; 
     324    $self->watch_write(0); 
     325 
     326    if ($self->{service}->{listener}->{sslopts}) { # SSL (sendfile does not do SSL) 
     327        return if $self->{closed}; 
     328        if ($remain <= 0) { #done 
     329            print "REPROXY SSL done\n" if Perlbal::DEBUG >= 2; 
     330            $self->reproxy_file_done; 
     331            return; 
     332        } 
     333        # queue up next read 
     334        Perlbal::AIO::set_file_for_channel($self->{reproxy_file}); 
     335        my $len = $remain > 4096 ? 4096 : $remain; # buffer size 
     336        my $buffer = ''; 
     337        Perlbal::AIO::aio_read( 
     338            $self->{reproxy_fh}, 
     339            $self->{reproxy_file_offset}, 
     340            $len, 
     341            $buffer, 
     342            sub { 
     343                return if $self->{closed}; 
     344                # we have buffer to send 
     345                my $rv = $_[0]; # arg is result of sysread 
     346                if (!defined($rv) || $rv <= 0) { # read error 
     347                    # sysseek is called after sysread so $! not valid 
     348                    $self->close('sysread_error'); 
     349                    print STDERR "Error w/ reproxy sysread\n"; 
     350                    return; 
     351                } 
     352                $self->{reproxy_file_offset} += $rv; 
     353                $self->tcp_cork(1); # by setting reproxy_file_offset above, 
     354                                    # it won't cork, so we cork it 
     355                $self->write($buffer); # start socket send 
     356                $self->watch_write(1); 
     357            }  
     358        ); 
     359        return; 
     360    } 
    266361 
    267362    # cap at 128k sendfiles 
    268363    my $to_send = $remain > 128 * 1024 ? 128 * 1024 : $remain; 
    269  
    270     $self->watch_write(0); 
    271364 
    272365    my $postread = sub { 
     
    289382 
    290383        if ($sent >= $remain) { 
    291             return if $self->{service}->run_hook('reproxy_fh_finished', $self); 
    292  
    293             # close the sendfile fd 
    294             CORE::close($self->{reproxy_fh}); 
    295  
    296             $self->{reproxy_fh} = undef; 
    297             if (my $cb = $self->{post_sendfile_cb}) { 
    298                 $cb->(); 
    299             } else { 
    300                 $self->http_response_sent; 
    301             } 
     384            $self->reproxy_file_done; 
    302385        } else { 
    303386            $self->watch_write(1); 
     
    533616    return $self->_simple_response(403, "Multiple file serving isn't enabled") unless $svc->{enable_concatenate_get}; 
    534617    return $self->_simple_response(403, "Too many files requested") if @multiple_files > 100; 
     618    return $self->_simple_response(403, "Bogus filenames") if grep { m!(?:\A|/)\.\./! } @multiple_files; 
    535619 
    536620    my $remain = @multiple_files + 1;  # 1 for the base directory 
     
    585669    } 
    586670 
    587     # What is -f _ doing here? don't we detect the existance of all files above in the loop? 
     671    # What is -f _ doing here? don't we detect the existence of all files above in the loop? 
    588672    my $not_mod = $ims eq $lastmod && -f _; 
    589673 
     
    651735    }; 
    652736    $self->{post_sendfile_cb}->(); 
     737} 
     738 
     739sub check_req_headers { 
     740    my Perlbal::ClientHTTPBase $self = shift; 
     741    my Perlbal::HTTPHeaders $hds = $self->{req_headers}; 
     742 
     743    if ($self->{service}->trusted_ip($self->peer_ip_string)) { 
     744        my @ips = split /,\s*/, ($hds->header("X-Forwarded-For") || ''); 
     745 
     746        # This list may be empty, and that's OK, in that case we should unset the 
     747        # observed_ip_string, so no matter what we'll use the 0th element, whether 
     748        # it happens to be an ip string, or undef. 
     749        $self->observed_ip_string($ips[0]); 
     750    } 
     751 
     752    return; 
    653753} 
    654754 
     
    765865} 
    766866 
    767 # FIXME: let this be configurable? 
    768 sub max_idle_time { 30; } 
    769  
    770867sub event_err {  my $self = shift; $self->close('error'); } 
    771868sub event_hup {  my $self = shift; $self->close('hup'); } 
    772869 
     870sub _sock_port { 
     871    my $name = $_[0]; 
     872    my $port = eval { (Socket::sockaddr_in($name))[0] }; 
     873    return $port unless $@; 
     874    # fallback to IPv6: 
     875    return (Socket6::unpack_sockaddr_in($name))[0]; 
     876} 
     877 
    773878sub as_string { 
    774879    my Perlbal::ClientHTTPBase $self = shift; 
     
    776881    my $ret = $self->SUPER::as_string; 
    777882    my $name = $self->{sock} ? getsockname($self->{sock}) : undef; 
    778     my $lport = $name ? (Socket::sockaddr_in($name))[0] : undef; 
     883    my $lport = $name ? _sock_port($name) : undef; 
     884    my $observed = $self->observed_ip_string; 
    779885    $ret .= ": localport=$lport" if $lport; 
     886    $ret .= "; observed_ip=$observed" if defined $observed; 
    780887    $ret .= "; reqs=$self->{requests}"; 
    781888    $ret .= "; $self->{state}"; 
  • trunk/server/lib/mogdeps/Perlbal/ClientManage.pm

    r1087 r1308  
    1717# ClientManage 
    1818sub new { 
    19     my ($class, $service, $sock) = @_; 
    20     my $self = $class->SUPER::new($sock); 
     19    my Perlbal::ClientManage $self = shift; 
     20    my ($service, $sock) = @_; 
     21    $self = fields::new($self) unless ref $self; 
     22    $self->SUPER::new($sock); 
    2123    $self->{service} = $service; 
    2224    $self->{buf} = "";   # what we've read so far, not forming a complete line 
     
    2527    $self->{ctx}->verbose(1); 
    2628 
    27     bless $self, ref $class || $class; 
    2829    $self->watch_read(1); 
    2930    return $self; 
     
    112113            my Perlbal::Service $svc = Perlbal->service($sname); 
    113114            next unless $svc; 
    114             $body .= "<li><a href='/service?$sname'>$sname</a> - $svc->{role} ($svc->{listen})</li>\n"; 
     115            my $listen = $svc->{listen} ? " ($svc->{listen})" : ""; 
     116            $body .= "<li><a href='/service?$sname'>$sname</a> - $svc->{role}$listen</li>\n"; 
    115117        } 
    116118        $body .= "</ul></li>"; 
  • trunk/server/lib/mogdeps/Perlbal/ClientProxy.pm

    r1087 r1308  
    22# HTTP Connection from a reverse proxy client 
    33# 
    4 # Copyright 2004, Danga Interactice, Inc. 
    5 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2004, Danga Interactive, Inc. 
     5# Copyright 2005-2007, Six Apart, Ltd. 
    66# 
    77package Perlbal::ClientProxy; 
     
    1212 
    1313use Perlbal::ChunkedUploadState; 
     14use Perlbal::Util; 
    1415 
    1516use fields ( 
     
    6061# ClientProxy 
    6162sub new { 
    62     my ($class, $service, $sock) = @_; 
    63  
    64     my $self = $class; 
    65     $self = fields::new($class) unless ref $self; 
    66     $self->SUPER::new($service, $sock);       # init base fields 
     63    my Perlbal::ClientProxy $self = shift; 
     64    my ($service, $sock) = @_; 
     65    $self = fields::new($self) unless ref $self; 
     66    $self->SUPER::new($service,  $sock ); 
    6767 
    6868    Perlbal::objctor($self); 
    69     bless $self, ref $class || $class; 
    7069 
    7170    $self->init; 
     
    7776    my $class = shift; 
    7877    my Perlbal::ClientHTTPBase $cb = shift; 
    79     bless $cb, $class; 
     78    Perlbal::Util::rebless($cb, $class); 
    8079    $cb->init; 
    8180    $cb->watch_read(1); 
     
    232231        $extra_hdr .= "Range: $range\r\n"; 
    233232    } 
    234  
    235     my $host = $datref->[0]; 
    236     $extra_hdr .= "Host: $host\r\n" if $host; 
     233    if (my $host = $self->{req_headers}->header("Host")) { 
     234        $extra_hdr .= "Host: $host\r\n"; 
     235    } 
    237236 
    238237    my $req_method = $self->{req_headers}->request_method eq 'HEAD' ? 'HEAD' : 'GET'; 
     
    318317 
    319318        # if the thing we're reproxying is indeed a file, advertise that 
    320         # we support byteranges on it 
     319        # we support byte ranges on it 
    321320        if (-f _) { 
    322321            $hd->header("Accept-Ranges", "bytes"); 
     
    428427    print "ClientProxy::backend_finished\n" if Perlbal::DEBUG >= 3; 
    429428 
    430     # mark ourselves as having responded (presumeably if we're here, 
     429    # mark ourselves as having responded (presumably if we're here, 
    431430    # the backend has responded already) 
    432431    $self->{responded} = 1; 
     
    440439 
    441440    # if we get here (and we do, rarely, in practice) then that means 
    442     # the backend read was empty/disconected (or otherwise messed up), 
     441    # the backend read was empty/disconnected (or otherwise messed up), 
    443442    # and the only thing we can really do is close the client down. 
    444443    $self->close("backend_finished_while_unread_data"); 
     444} 
     445 
     446# Called when this client is entering a persist_wait state, but before we are returned to base. 
     447sub persist_wait { 
     448    my Perlbal::ClientProxy $self = $_[0]; 
     449    # We're in keepalive, and just completed a proxy request 
     450    $self->{service}->run_hooks('end_proxy_request', $self); 
    445451} 
    446452 
     
    506512    my $reason = shift; 
    507513 
     514    warn sprintf( 
     515                    "Perlbal::ClientProxy closed %s%s.\n", 
     516                    ( $self->{closed} ? "again " : "" ), 
     517                    (defined $reason ? "saying '$reason'" : "for an unknown reason") 
     518    ) if Perlbal::DEBUG >= 2; 
     519 
    508520    # don't close twice 
    509521    return if $self->{closed}; 
     
    544556    print "ClientProxy::event_write\n" if Perlbal::DEBUG >= 3; 
    545557 
    546     $self->SUPER::event_write; 
    547  
    548558    # obviously if we're writing the backend has processed our request 
    549559    # and we are responding/have responded to the user, so mark it so 
    550560    $self->{responded} = 1; 
     561 
     562    # will eventually, finally reset the whole object on completion 
     563    $self->SUPER::event_write; 
    551564 
    552565    # trigger our backend to keep reading, if it's still connected 
     
    731744    my $req_hd = $self->{req_headers}; 
    732745 
     746    unless ($req_hd) { 
     747        $self->close("handle_request without headers"); 
     748        return; 
     749    } 
     750 
     751    $self->check_req_headers; 
     752 
    733753    my $svc = $self->{service}; 
    734754    # give plugins a chance to force us to bail 
     
    742762        # subtract out read_size, which is the amount of data that was 
    743763        # extra in the packet with the header that's part of the body. 
    744         $self->{request_body_length} = 
     764        my $length = $self->{request_body_length} = 
    745765            $self->{content_length_remain} = 
    746766            $req_hd->content_length; 
     767 
     768        if (defined $length && $length < 0) { 
     769            $self->_simple_response(400, "Invalid request: Content-Length < 0"); 
     770            $self->close("negative_content_length"); 
     771            return; 
     772        } 
     773 
    747774        $self->{unread_data_waiting} = 1 if $self->{content_length_remain}; 
    748775    } 
     
    10031030    my $clen = $self->{req_headers}->content_length; 
    10041031    if ($clen != $self->{buoutpos}) { 
    1005         Perlbal::log('critical', "Content length of $clen declared but $self->{buoutpos} bytes written to disk"); 
     1032        Perlbal::log('crit', "Content length of $clen declared but $self->{buoutpos} bytes written to disk"); 
    10061033        return $self->_simple_response(500); 
    10071034    } 
     
    10091036    # reset our position so we start reading from the right spot 
    10101037    $self->{buoutpos} = 0; 
    1011     sysseek($self->{bufh}, 0, 0); 
     1038    sysseek($self->{bufh}, 0, 0) if ($self->{bufh}); # But only if it exists at all 
    10121039 
    10131040    # notify that we want the backend so we get the ball rolling 
     
    10231050    my $clen = $self->{request_body_length}; 
    10241051 
    1025     my $sent = Perlbal::Socket::sendfile($be->{fd}, fileno($self->{bufh}), $clen - $self->{buoutpos}); 
    1026     if ($sent < 0) { 
    1027         return $self->close("epipe") if $! == EPIPE; 
    1028         return $self->close("connreset") if $! == ECONNRESET; 
    1029         print STDERR "Error w/ sendfile: $!\n"; 
    1030         return $self->close('sendfile_error'); 
    1031     } 
    1032     $self->{buoutpos} += $sent; 
     1052    if ($self->{buoutpos} < $clen) { 
     1053        my $sent = Perlbal::Socket::sendfile($be->{fd}, fileno($self->{bufh}), $clen - $self->{buoutpos}); 
     1054        if ($sent < 0) { 
     1055            return $self->close("epipe") if $! == EPIPE; 
     1056            return $self->close("connreset") if $! == ECONNRESET; 
     1057            print STDERR "Error w/ sendfile: $!\n"; 
     1058            return $self->close('sendfile_error'); 
     1059        } 
     1060        $self->{buoutpos} += $sent; 
     1061    } 
    10331062 
    10341063    # if we're done, purge the file and move on 
     
    10621091            # throw errors back to the user 
    10631092            if (! $self->{bufh}) { 
    1064                 Perlbal::log('critical', "Failure to open $fn for buffered upload output"); 
     1093                Perlbal::log('crit', "Failure to open $fn for buffered upload output"); 
    10651094                return $self->_simple_response(500); 
    10661095            } 
     
    10971126 
    10981127        # check for error 
    1099         unless ($bytes) { 
    1100             Perlbal::log('critical', "Error writing buffered upload: $!.  Tried to do $len bytes at $self->{buoutpos}."); 
     1128        unless ($bytes > 0) { 
     1129            Perlbal::log('crit', "Error writing buffered upload: $!.  Tried to do $len bytes at $self->{buoutpos}."); 
    11011130            return $self->_simple_response(500); 
    11021131        } 
     
    11061135 
    11071136        # now check if we wrote less than we had in this chunk of buffer.  if that's 
    1108         # the case then we need to reenqueue the part of the chunk that wasn't 
     1137        # the case then we need to re-enqueue the part of the chunk that wasn't 
    11091138        # written out and update as appropriate. 
    11101139        if ($bytes < $len) { 
     
    11441173    my Perlbal::ClientProxy $self = shift; 
    11451174 
     1175    # Main reason for failure below is a 0-length chunked upload, where the file is never created. 
     1176    return unless $self->{bufh}; 
     1177 
    11461178    # FIXME: it's reported that sometimes the two now-in-eval blocks 
    11471179    # fail, hence the eval blocks and warnings.  the FIXME is to 
     
    11571189 
    11581190    eval { 
    1159         # now asyncronously unlink the file 
     1191        # now asynchronously unlink the file 
    11601192        Perlbal::AIO::aio_unlink($self->{bufilename}, sub { 
    11611193            if ($!) { 
  • trunk/server/lib/mogdeps/Perlbal/CommandContext.pm

    r1087 r1308  
    22# can be less verbose when in config files 
    33# 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55# 
    66 
  • trunk/server/lib/mogdeps/Perlbal/HTTPHeaders.pm

    r1087 r1308  
    22# HTTP header class (both request and response) 
    33# 
    4 # Copyright 2004, Danga Interactice, Inc. 
    5 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2004, Danga Interactive, Inc. 
     5# Copyright 2005-2007, Six Apart, Ltd. 
    66# 
    77 
     
    1010use warnings; 
    1111no  warnings qw(deprecated); 
     12 
     13use Perlbal; 
    1214 
    1315use fields ( 
     
    3032    204 => 'No Content', 
    3133    206 => 'Partial Content', 
     34    301 => 'Permanent Redirect', 
     35    302 => 'Found', 
    3236    304 => 'Not Modified', 
    3337    400 => 'Bad request', 
     
    104108        # check for valid response line 
    105109        return fail("Bogus response line") unless 
    106             $self->{responseLine} =~ m!^HTTP\/(\d+)\.(\d+)\s+(\d+)\s+(.+)$!; 
     110            $self->{responseLine} =~ m!^HTTP\/(\d+)\.(\d+)\s+(\d+)\s+(.*)$!; 
    107111 
    108112        my ($ver_ma, $ver_mi, $code) = ($1, $2, $3); 
     
    223227} 
    224228 
     229sub set_request_uri { 
     230    my Perlbal::HTTPHeaders $self = shift; 
     231    return unless $self->{requestLine}; 
     232 
     233    my $uri = shift; 
     234 
     235    return unless defined $uri and length $uri; 
     236 
     237    my $ver = $self->{ver}; 
     238 
     239    if ($ver == 0.9) { 
     240        $self->{requestLine} = sprintf("%s %s", $self->{method}, $uri); 
     241    } else { 
     242        $self->{requestLine} = sprintf("%s %s HTTP/%s", $self->{method}, $uri, $ver); 
     243    } 
     244 
     245    return $self->{uri} = $uri; 
     246} 
     247 
    225248sub version_number { 
    226249    my Perlbal::HTTPHeaders $self = $_[0]; 
     
    243266 
    244267    return $self->{headers}{$key} = shift; 
     268} 
     269 
     270sub headers_list { 
     271    my Perlbal::HTTPHeaders $self = shift; 
     272    return [$self->{headers} ? keys %{ $self->{headers} } : ()]; 
    245273} 
    246274 
     
    407435# returns (status, range_start, range_end) when given a size 
    408436# status = 200 - invalid or non-existent range header.  serve normally. 
    409 # status = 206 - parsable range is good.  serve partial content. 
     437# status = 206 - parseable range is good.  serve partial content. 
    410438# status = 416 - Range is unsatisfiable 
    411439sub range { 
  • trunk/server/lib/mogdeps/Perlbal/ManageCommand.pm

    r1087 r1308  
    22# to a command should be done through this instance (out, err, ok, etc) 
    33# 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55# 
    66 
  • trunk/server/lib/mogdeps/Perlbal/Plugin/AccessControl.pm

    r1087 r1308  
    3535                              (?:\s+(\S+))?                  # optional arg2 
    3636                              $/x, 
    37                               "usage: ACCESS [<service>] <cmd> <arg1> [<arg2>]"); 
     37                              "usage: ACCESS <cmd> <arg1> [<arg2>]"); 
    3838        my ($cmd, $arg1, $arg2) = $mc->args; 
    3939 
     
    8787sub unload { 
    8888    my $class = shift; 
    89     Perlbal::unregister_global_hook('manage_command.vhost'); 
     89    Perlbal::unregister_global_hook('manage_command.access'); 
    9090    return 1; 
    9191} 
     
    9494sub register { 
    9595    my ($class, $svc) = @_; 
     96 
     97    my $use_observed_ip; 
    9698 
    9799    $svc->register_hook('AccessControl', 'start_http_request', sub { 
     
    126128            my $rule = shift; 
    127129            if ($rule->[1] eq "ip") { 
    128                 my $peer_ip = $client->peer_ip_string; 
     130                my $peer_ip; 
     131                $peer_ip = $client->observed_ip_string if $use_observed_ip; 
     132                $peer_ip ||= $client->peer_ip_string; 
     133 
    129134                return $peer_ip eq $rule->[2]; 
    130135            } 
    131136 
    132137            if ($rule->[1] eq "netmask") { 
    133                 my $peer_ip = $client->peer_ip_string; 
     138                my $peer_ip; 
     139                $peer_ip = $client->observed_ip_string if $use_observed_ip; 
     140                $peer_ip ||= $client->peer_ip_string; 
     141 
    134142                return eval { $rule->[2]->match($peer_ip); }; 
    135143            } 
    136  
    137144        }; 
    138145 
     
    148155    }); 
    149156 
     157    # Allow AccessControl users to specify that they would like to use the observed IP as 
     158    # opposed to the real IP for ACL checking. 
     159    $svc->register_setter('AccessControl', 'use_observed_ip', sub { 
     160        my ($out, $what, $val) = @_; 
     161        return 0 unless $what; 
     162 
     163        $use_observed_ip = $val; 
     164 
     165        $out->("OK") if $out; 
     166 
     167        return 1; 
     168    }); 
     169 
     170 
    150171    return 1; 
    151172} 
     
    157178} 
    158179 
     180sub dumpconfig { 
     181    my ($class, $svc) = @_; 
     182 
     183    my @return; 
     184 
     185    my $cfg = $svc->{extra_config}->{_access} ||= {}; 
     186    my $rules = $cfg->{rules} || []; 
     187 
     188    foreach my $rule (@$rules) { 
     189        my $action = uc $rule->[0]; 
     190        my $type   = uc $rule->[1]; 
     191        my $value  = $rule->[2]; 
     192        push @return, "ACCESS $action $type $value"; 
     193    } 
     194 
     195    my $default_action = $cfg->{deny_default} ? "DENY" : "ALLOW"; 
     196    push @return, "ACCESS POLICY $default_action"; 
     197 
     198    return @return; 
     199} 
     200 
    1592011; 
  • trunk/server/lib/mogdeps/Perlbal/Plugin/Highpri.pm

    r1087 r1308  
    3939        my $temp; 
    4040        unless ($val eq 'undef' || $val eq 'none' || $val eq 'null') { 
    41             # verify this regex works?  do it in an eval because qr will die 
     41            # verify this regexp works?  do it in an eval because qr will die 
    4242            # if we give it something invalid 
    4343            eval { 
  • trunk/server/lib/mogdeps/Perlbal/Plugin/Stats.pm

    r1087 r1308  
    2828 
    2929    # simple events we count are done here.  when the hook on the left side is called, 
    30     # we simply increment the count of the stat ont he right side. 
     30    # we simply increment the count of the stat on the right side. 
    3131    my %simple = qw( 
    3232        start_send_file         files_sent 
     
    4444    $svc->register_hook('Stats', 'backend_client_assigned', sub { 
    4545        my Perlbal::BackendHTTP $be = shift; 
    46         $sobj->{pending}->{"$be->{client}"} = [ gettimeofday() ]; 
    47         ($be->{client}->{high_priority} ? $sobj->{proxy_requests_highpri} : $sobj->{proxy_requests})++; 
     46        my Perlbal::ClientProxy $cp = $be->{client}; 
     47        $sobj->{pending}->{"$cp"} = [ gettimeofday() ]; 
     48        ($cp->{high_priority} ? $sobj->{proxy_requests_highpri} : $sobj->{proxy_requests})++; 
    4849        return 0; 
    4950    }); 
     
    5152        my Perlbal::BackendHTTP $be = shift; 
    5253        my Perlbal::ClientProxy $obj = $be->{client}; 
    53         my $ot = $sobj->{pending}->{"$obj"}; 
     54        my $ot = delete $sobj->{pending}->{"$obj"}; 
    5455        return 0 unless defined $ot; 
    5556 
  • trunk/server/lib/mogdeps/Perlbal/Plugin/Vhosts.pm

    r1087 r1308  
    44 
    55# 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 reuqest 
     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 
    77#   PUTing a large file to a selector, seeing if it is put correctly to the PUT-enabled web_server proxy 
    8 #   obvious cases:  non-existant domains, default domains (*), proper matching (foo.brad.lj before *.brad.lj) 
     8#   obvious cases:  non-existent domains, default domains (*), proper matching (foo.brad.lj before *.brad.lj) 
    99# 
    1010 
     
    7676} 
    7777 
     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 
    7894# call back from Service via ClientHTTPBase's event_read calling service->select_new_service(Perlbal::ClientHTTPBase) 
    7995sub vhost_selector { 
     
    84100 
    85101    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 
    86106    my $uri = $req->request_uri; 
    87107    my $maps = $cb->{service}{extra_config}{_vhosts} ||= {}; 
  • trunk/server/lib/mogdeps/Perlbal/Pool.pm

    r1087 r1308  
    33###################################################################### 
    44# 
    5 # Copyright 2004, Danga Interactice, Inc. 
    6 # Copyright 2005-2006, Six Apart, Ltd. 
     5# Copyright 2004, Danga Interactive, Inc. 
     6# Copyright 2005-2007, Six Apart, Ltd. 
    77# 
    88 
     
    9595} 
    9696 
     97sub dumpconfig { 
     98    my Perlbal::Pool $self = shift; 
     99    my $name = $self->{name}; 
     100 
     101    my @return; 
     102 
     103    if (my $nodefile = $self->{'nodefile'}) { 
     104        push @return, "SET nodefile = $nodefile"; 
     105    } else { 
     106        foreach my $node (@{$self->{nodes}}) { 
     107            my ($ip, $port) = @$node; 
     108            push @return, "POOL ADD $name $ip:$port"; 
     109        } 
     110    } 
     111    return @return; 
     112} 
     113 
    97114# returns string of balance method 
    98115sub balance_method { 
  • trunk/server/lib/mogdeps/Perlbal/ReproxyManager.pm

    r1087 r1308  
    11# HTTP connection to non-pool backend nodes (probably fast event-based webservers) 
    22# 
    3 # Copyright 2004, Danga Interactice, Inc. 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     3# Copyright 2004, Danga Interactive, Inc. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55# 
    66 
  • trunk/server/lib/mogdeps/Perlbal/Service.pm

    r1087 r1308  
    33###################################################################### 
    44# 
    5 # Copyright 2004, Danga Interactice, Inc. 
    6 # Copyright 2005-2006, Six Apart, Ltd. 
     5# Copyright 2004, Danga Interactive, Inc. 
     6# Copyright 2005-2007, Six Apart, Ltd. 
    77# 
    88 
     
    1414use Perlbal::BackendHTTP; 
    1515use Perlbal::Cache; 
     16use Perlbal::Util; 
    1617 
    1718use fields ( 
     
    2728            'listen',             # scalar IP:port of where we're listening for new connections 
    2829            'docroot',            # document root for webserver role 
    29             'dirindexing',        # bool: direcotry indexing?  (for webserver role)  not async. 
     30            'dirindexing',        # bool: directory indexing?  (for webserver role)  not async. 
    3031            'index_files',        # arrayref of filenames to try for index files 
    3132            'enable_concatenate_get',   # bool:  if user can request concatenated files 
     
    4142            'persist_backend', # bool: persistent connections for backends 
    4243            'verify_backend',  # bool: get attention of backend before giving it clients (using OPTIONS) 
     44            'verify_backend_path', # path to check with the OPTIONS request (default *) 
    4345            'max_backend_uses',  # max requests to send per kept-alive backend (default 0 = unlimited) 
    4446            'connect_ahead',           # scalar: number of spare backends to connect to in advance all the time 
     
    5153            'trusted_upstream_proxies', # Net::Netmask object containing netmasks for trusted upstreams 
    5254            'always_trusted', # bool; if true, always trust upstreams 
     55            'blind_proxy', # bool: if true, do not modify X-Forwarded-For, X-Host, or X-Forwarded-Host headers 
    5356            'enable_reproxy', # bool; if true, advertise that server will reproxy files and/or URLs 
    5457            'reproxy_cache_maxsize', # int; maximum number of reproxy results to be cached. (0 is disabled and default) 
    5558            'client_sndbuf_size',    # int: bytes for SO_SNDBUF 
    5659            'server_process' ,       # scalar: path to server process (executable) 
     60            'persist_client_timeout',  # int: keep-alive timeout in seconds for clients (default is 30) 
    5761 
    5862            # Internal state: 
     
    6064            'waiting_clients_highpri', # arrayref of high-priority clients waiting for backendhttp conns 
    6165            'waiting_clients_lowpri',  # arrayref of low-priority clients waiting for backendhttp conns 
    62             'waiting_client_count',    # number of clients waiting for backendds 
     66            'waiting_client_count',    # number of clients waiting for backends 
    6367            'waiting_client_map'  ,    # map of clientproxy fd -> 1 (if they're waiting for a conn) 
    6468            'pending_connects',        # hashref of "ip:port" -> $time (only one pending connect to backend at a time) 
     
    7781            'buffer_backend_connect', # 0 for of, else, number of bytes to buffer before we ask for a backend 
    7882            'selector',    # CODE ref, or undef, for role 'selector' services 
     83            'default_service', # Perlbal::Service; name of a service a selector should default to 
    7984            'buffer_uploads', # bool; enable/disable the buffered uploads to disk system 
    8085            'buffer_uploads_path', # string; path to store buffered upload files 
     
    129134        check_role => "*", 
    130135        des => "The ip:port to listen on.  For a service to work, you must either make it listen, or make another selector service map to a non-listening service.", 
    131         check_type => ["regexp", qr/^\d+\.\d+\.\d+\.\d+:\d+$/, "Expecting IP:port of form a.b.c.d:port."], 
     136        check_type => ["regexp", qr/^(\d+\.\d+\.\d+\.\d+:)?\d+$/, 
     137                       "Listen argument must be ip:port or port. " . 
     138                       "e.g. 192.168.0.1:80 or 81"], 
    132139        setter => sub { 
    133140            my ($self, $val, $set, $mc) = @_; 
     
    169176        default => 0, 
    170177        check_type => "bool", 
     178        check_role => "reverse_proxy", 
     179    }, 
     180     
     181    'verify_backend_path' => { 
     182        des => "What path the OPTIONS request sent by verify_backend should use.  Default is '*'.", 
     183        default => '*', 
    171184        check_role => "reverse_proxy", 
    172185    }, 
     
    316329 
    317330    'enable_concatenate_get' => { 
    318         des => "Enable Perlbal's multiple-files-in-one-request mode, where a client have use a comma-separated list of files to return, always in text/plain.  Useful for webapps which have dozens/hundreds of tiny css/js files, and don't trust browsers/etc to do pipelining.  Decreases overall roundtrip latency a bunch, but requires app to be modified to support it.  See t/17-concat.t test for details.", 
     331        des => "Enable Perlbal's multiple-files-in-one-request mode, where a client have use a comma-separated list of files to return, always in text/plain.  Useful for web apps which have dozens/hundreds of tiny css/js files, and don't trust browsers/etc to do pipelining.  Decreases overall round-trip latency a bunch, but requires app to be modified to support it.  See t/17-concat.t test for details.", 
    319332        default => 0, 
    320333        check_role => "web_server", 
     
    339352        default => 0, 
    340353        check_type => "bool", 
     354        check_role => "*", 
     355    }, 
     356 
     357    'blind_proxy' => { 
     358        des => "Flag to disable any modification of X-Forwarded-For, X-Host, and X-Forwarded-Host headers.", 
     359        default => 0, 
     360        check_type => "bool", 
    341361        check_role => "reverse_proxy", 
    342362    }, 
     
    354374    'trusted_upstream_proxies' => { 
    355375        des => "A Net::Netmask filter (e.g. 10.0.0.0/24, see Net::Netmask) that determines whether upstream clients are trusted or not, where trusted means their X-Forwarded-For/etc headers are not munged.", 
    356         check_role => "reverse_proxy", 
     376        check_role => "*", 
    357377        check_type => sub { 
    358378            my ($self, $val, $errref) = @_; 
     
    366386            return 0; 
    367387        }, 
    368  
     388        setter => sub { 
     389            my ($self, $val, $set, $mc) = @_; 
     390            # Do nothing here, we don't want the default setter because we've 
     391            # already set the value in the type_check step. 
     392            return $mc->ok; 
     393        }, 
    369394    }, 
    370395 
     
    372397        check_role => "web_server", 
    373398        default => "index.html", 
    374         des => "Comma-seperated list of filenames to load when a user visits a directory URL, listed in order of preference.", 
     399        des => "Comma-separated list of filenames to load when a user visits a directory URL, listed in order of preference.", 
    375400        setter => sub { 
    376401            my ($self, $val, $set, $mc) = @_; 
    377402            $self->{index_files} = [ split(/[\s,]+/, $val) ]; 
    378403            return $mc->ok; 
     404        }, 
     405        dumper => sub { 
     406            my ($self, $val) = @_; 
     407            return join(', ', @$val); 
     408        }, 
     409    }, 
     410 
     411    'default_service' => { 
     412        des => "Name of previously-created service to default requests that aren't matched by a selector plugin to.", 
     413        check_role => "selector", 
     414        check_type => sub { 
     415            my ($self, $val, $errref) = @_; 
     416 
     417            my $svc = Perlbal->service($val); 
     418            unless ($svc) { 
     419                $$errref = "Service '$svc' not found"; 
     420                return 0; 
     421            } 
     422 
     423            $self->{default_service} = $svc; 
     424            return 1; 
     425        },  
     426        setter => sub { 
     427            # override default so we don't set it to the text 
     428            return $_[3]->ok; 
    379429        }, 
    380430    }, 
     
    403453            return $mc->ok; 
    404454        }, 
     455        dumper => sub { 
     456            my ($self, $val) = @_; 
     457            return $val->name; 
     458        } 
    405459    }, 
    406460 
     
    417471    }, 
    418472 
     473    'persist_client_timeout' => { 
     474        des => "Timeout in seconds for HTTP keep-alives to the end user (default is 30)", 
     475        check_type => "int", 
     476        default => 30, 
     477        check_role => "*", 
     478    }, 
     479     
    419480    'buffer_uploads_path' => { 
    420481        des => "Directory root for storing files used to buffer uploads.", 
     
    565626 
    566627    return $self; 
     628} 
     629 
     630# handy instance method to run some manage commands in the context of this service, 
     631# without needing to worry about its name. 
     632# This is intended as an internal API thing, so any output that would have been 
     633# generated is just eaten. 
     634sub run_manage_commands { 
     635    my ($self, $cmd_block) = @_; 
     636 
     637    my $ctx = Perlbal::CommandContext->new; 
     638    $ctx->{last_created} = $self->name; 
     639    return Perlbal::run_manage_commands($cmd_block, undef, $ctx); 
     640} 
     641 
     642# here's an alternative version of the above that runs a single command 
     643sub run_manage_command { 
     644    my ($self, $cmd) = @_; 
     645 
     646    my $ctx = Perlbal::CommandContext->new; 
     647    $ctx->{last_created} = $self->name; 
     648    return Perlbal::run_manage_command($cmd, undef, $ctx); 
     649} 
     650 
     651sub dumpconfig { 
     652    my $self = shift; 
     653 
     654    my @return; 
     655 
     656    my %my_tunables = %$tunables; 
     657 
     658    my $dump = sub { 
     659        my $setting = shift; 
     660    }; 
     661 
     662    foreach my $skip (qw(role listen pool)) { 
     663        delete $my_tunables{$skip}; 
     664    } 
     665 
     666    my $role = $self->{role}; 
     667 
     668    foreach my $setting ("role", "listen", "pool", sort keys %my_tunables) { 
     669        my $attrs = $tunables->{$setting}; 
     670        my $value = $self->{$setting}; 
     671 
     672        my $check_role = $attrs->{check_role}; 
     673        my $check_type = $attrs->{check_type}; 
     674        my $default    = $attrs->{default}; 
     675        my $required   = $attrs->{required}; 
     676 
     677        next if ($check_role && $check_role ne '*' && $check_role ne $role); 
     678 
     679        if ($check_type && $check_type eq 'size') { 
     680            $default = $1               if $default =~ /^(\d+)b$/i; 
     681            $default = $1 * 1024        if $default =~ /^(\d+)k$/i; 
     682            $default = $1 * 1024 * 1024 if $default =~ /^(\d+)m$/i; 
     683        } 
     684 
     685        if (!$required) { 
     686            next unless defined $value; 
     687            next if (defined $default && $value eq $default); 
     688        } 
     689 
     690        if (my $dumper = $attrs->{dumper}) { 
     691            $value = $dumper->($self, $value); 
     692        } 
     693 
     694        if ($check_type && $check_type eq 'bool') { 
     695            $value = 'on' if $value; 
     696        } 
     697 
     698        push @return, "SET $setting = $value"; 
     699    } 
     700 
     701    my $plugins = $self->{plugins}; 
     702 
     703    foreach my $plugin (keys %$plugins) { 
     704        local $@; 
     705 
     706        my $class = "Perlbal::Plugin::$plugin"; 
     707        my $cv = $class->can('dumpconfig'); 
     708 
     709        if ($cv) { 
     710            eval { push @return, $class->dumpconfig($self) }; 
     711            if ($@) { 
     712                push @return, "# Plugin '$plugin' threw an exception while being dumped."; 
     713            } 
     714        } else { 
     715            push @return, "# Plugin '$plugin' isn't capable of dumping config."; 
     716        } 
     717    } 
     718 
     719    return @return; 
    567720} 
    568721 
     
    11911344            if (my $sp = $self->{server_process}) { 
    11921345                warn "To create = $to_create...\n"; 
    1193                 warn "  spawing $sp\n"; 
     1346                warn "  spawning $sp\n"; 
    11941347                my $be = Perlbal::BackendHTTP->new_process($self, $sp); 
    11951348                return; 
     
    12601413    return $mc->err("no header provided") unless $key; 
    12611414    return $mc->err("no value provided")  unless $val || $mode eq 'remove'; 
     1415    return $mc->err("only valid on reverse_proxy services") unless $self->{role} eq 'reverse_proxy'; 
    12621416 
    12631417    if ($mode eq 'insert') { 
     
    12871441sub selector { 
    12881442    my Perlbal::Service $self = shift; 
    1289     $self->{selector} = shift if @_; 
     1443    if (@_) { 
     1444        my $ref = shift; 
     1445        $self->{selector} = sub { 
     1446            my $cb = shift; 
     1447 
     1448            # try to give it to our defined selector 
     1449            my $res = $ref->($cb); 
     1450 
     1451            # if that failed and we have a default, then give it to them 
     1452            if (!$res && $self->{default_service}) { 
     1453                $self->{default_service}->adopt_base_client($cb); 
     1454                return 1; 
     1455            } 
     1456 
     1457            return $res; 
     1458        }; 
     1459    } 
    12901460    return $self->{selector}; 
    12911461} 
     
    13041474        Perlbal::ClientProxy->new_from_base($cb); 
    13051475        return; 
     1476    } elsif ($self->{'role'} eq "selector") { 
     1477        $self->selector()->($cb); 
     1478        return; 
    13061479    } else { 
    13071480        $cb->_simple_response(500, "Can't map to service type $self->{'role'}"); 
     
    13161489 
    13171490    $cb->{service} = $self; 
    1318     bless $cb, "Perlbal::ClientHTTPBase"; 
     1491    Perlbal::Util::rebless($cb, "Perlbal::ClientHTTPBase"); 
    13191492 
    13201493    # the read/watch events are reset by ClientHTTPBase's http_response_sent (our caller) 
  • trunk/server/lib/mogdeps/Perlbal/Socket.pm

    r1087 r1308  
    11# Base class for all socket types 
    22# 
    3 # Copyright 2004, Danga Interactice, Inc. 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     3# Copyright 2004, Danga Interactive, Inc. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55 
    66package Perlbal::Socket; 
     
    3333 
    3434            'ditch_leading_rn', # if true, the next header parsing will ignore a leading \r\n 
     35 
     36            'observed_ip_string', # if defined, contains the observed IP string of the peer 
     37                                  # we're serving. this is intended for hoding the value of 
     38                                  # the X-Forwarded-For and using it to govern ACLs. 
    3539            ); 
    3640 
     
    107111 
    108112            # however, the grep turned our weak references back into strong ones, so 
    109             # we have to reweaken them 
     113            # we have to re-weaken them 
    110114            weaken($_) foreach @created_objects; 
    111115 
     
    132136    my $now = time; 
    133137 
    134     my %max_age;  # classname -> max age (0 means forever) 
    135138    my @to_close; 
    136139    while (my $k = each %$sf) { 
    137140        my Perlbal::Socket $v = $sf->{$k}; 
    138         my $ref = ref $v; 
    139         unless (defined $max_age{$ref}) { 
    140             # eval because not all Danga::Socket connections in Perlbal 
    141             # must be Perlbal::Socket-derived 
    142             $max_age{$ref} = eval { $ref->max_idle_time } || 0; 
    143         } 
    144         next unless $max_age{$ref}; 
    145         if ($v->{alive_time} < $now - $max_age{$ref}) { 
     141 
     142        my $max_age = eval { $v->max_idle_time } || 0; 
     143        next unless $max_age; 
     144 
     145        if ($v->{alive_time} < $now - $max_age) { 
    146146            push @to_close, $v; 
    147147        } 
     
    220220    $self->{headers_string} .= $$bref; 
    221221    my $idx = index($self->{headers_string}, "\r\n\r\n"); 
    222  
    223     # can't find the header delimiter? 
     222    my $delim_len = 4; 
     223 
     224    # can't find the header delimiter? check for LFLF header delimiter. 
     225    if ($idx == -1) { 
     226        $idx = index($self->{headers_string}, "\n\n"); 
     227        $delim_len = 2; 
     228    } 
     229    # still can't find the header delimiter? 
    224230    if ($idx == -1) { 
    225231 
     
    244250    print "  pre-parsed headers: [$hstr]\n" if Perlbal::DEBUG >= 3; 
    245251 
    246     my $extra = substr($self->{headers_string}, $idx+4); 
     252    my $extra = substr($self->{headers_string}, $idx+$delim_len); 
    247253    if (my $len = length($extra)) { 
    248254        print "  pushing back $len bytes after header\n" if Perlbal::DEBUG >= 3; 
     
    296302} 
    297303 
     304### METHOD: write() 
     305### Overridden from Danga::Socket to update our alive time on successful writes 
     306### Stops sockets from being closed on long-running write operations 
     307sub write { 
     308    my $self = shift; 
     309 
     310    my $ret; 
     311    if ($ret = $self->SUPER::write(@_)) { 
     312        # Mark this socket alive so we don't time out 
     313        $self->{alive_time} = $Perlbal::tick_time; 
     314    } 
     315     
     316    return $ret; 
     317} 
     318 
    298319### METHOD: close() 
    299320### Set our state when we get closed. 
     
    314335} 
    315336 
     337sub observed_ip_string { 
     338    my Perlbal::Socket $self = shift; 
     339 
     340    if (@_) { 
     341        return $self->{observed_ip_string} = $_[0]; 
     342    } else { 
     343        return $self->{observed_ip_string}; 
     344    } 
     345} 
     346 
    316347sub as_string_html { 
    317348    my Perlbal::Socket $self = shift; 
  • trunk/server/lib/mogdeps/Perlbal/TCPListener.pm

    r1087 r1308  
    22# TCP listener on a given port 
    33# 
    4 # Copyright 2004, Danga Interactice, Inc. 
    5 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2004, Danga Interactive, Inc. 
     5# Copyright 2005-2007, Six Apart, Ltd. 
    66 
    77 
     
    1212 
    1313use base "Perlbal::Socket"; 
    14 use fields qw(service hostport); 
     14use fields ('service', 
     15            'hostport', 
     16            'sslopts', 
     17            'v6',  # bool: IPv6 libraries are available 
     18            ); 
    1519use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF); 
     20 
     21BEGIN { 
     22    eval { require Perlbal::SocketSSL }; 
     23    if (Perlbal::DEBUG > 0 && $@) { warn "SSL support failed on load: $@\n" } 
     24} 
    1625 
    1726# TCPListener 
    1827sub new { 
    19     my ($class, $hostport, $service, $opts) = @_; 
     28    my Perlbal::TCPListener $self = shift; 
     29    my ($hostport, $service, $opts) = @_; 
     30 
     31    $self = fields::new($self) unless ref $self; 
    2032    $opts ||= {}; 
    2133 
    22     my $sockclass = $opts->{ssl} ? "IO::Socket::SSL" : "IO::Socket::INET"; 
    23     my $sock = eval { 
    24         $sockclass->new( 
    25                         LocalAddr => $hostport, 
    26                         Proto => IPPROTO_TCP, 
    27                         Listen => 1024, 
    28                         ReuseAddr => 1, 
    29                         ($opts->{ssl} ? %{$opts->{ssl}} : ()), 
    30                         ); 
    31     }; 
     34    # Were ipv4 or ipv6 explicitly mentioned by syntax? 
     35    my $force_v4 = 0; 
     36    my $force_v6 = 0; 
     37 
     38    my @args; 
     39    if ($hostport =~ /^\d+$/) { 
     40        @args = ('LocalPort' => $hostport); 
     41    } elsif ($hostport =~ /^\d+\.\d+\.\d+\.\d+:/) { 
     42        $force_v4 = 1; 
     43        @args = ('LocalAddr' => $hostport); 
     44    } 
     45 
     46    my $v6_errors = ""; 
     47 
     48    my $can_v6 = 0; 
     49    if (!$force_v4) { 
     50        eval "use Danga::Socket 1.61; 1; "; 
     51        if ($@) { 
     52            $v6_errors = "Danga::Socket 1.61 required for IPv6 support."; 
     53        } elsif (!eval { require IO::Socket::INET6; 1 }) { 
     54            $v6_errors = "IO::Socket::INET6 required for IPv6 support."; 
     55        } else { 
     56            $can_v6 = 1; 
     57        } 
     58    } 
     59 
     60    my $socket_class = $can_v6 ? "IO::Socket::INET6" : "IO::Socket::INET"; 
     61    $self->{v6} = $can_v6; 
     62 
     63    my $sock = $socket_class->new( 
     64                                  @args, 
     65                                  Proto => IPPROTO_TCP, 
     66                                  Listen => 1024, 
     67                                  ReuseAddr => 1, 
     68                                  ); 
    3269 
    3370    return Perlbal::error("Error creating listening socket: " . ($@ || $!)) 
     
    4683    } 
    4784 
    48     my $self = $class->SUPER::new($sock); 
     85    $self->SUPER::new($sock); 
    4986    $self->{service} = $service; 
    5087    $self->{hostport} = $hostport; 
    51     bless $self, ref $class || $class; 
     88    $self->{sslopts} = $opts->{ssl}; 
    5289    $self->watch_read(1); 
    5390    return $self; 
     
    6097    # accept as many connections as we can 
    6198    while (my ($psock, $peeraddr) = $self->{sock}->accept) { 
    62         my $service_role = $self->{service}->role; 
    63  
    64         if (Perlbal::DEBUG >= 1) { 
    65             my ($pport, $pipr) = Socket::sockaddr_in($peeraddr); 
    66             my $pip = Socket::inet_ntoa($pipr); 
    67             print "Got new conn: $psock ($pip:$pport) for $service_role\n"; 
    68         } 
    69  
    7099        IO::Handle::blocking($psock, 0); 
    71100 
     
    74103        } 
    75104 
    76         if ($service_role eq "reverse_proxy") { 
    77             Perlbal::ClientProxy->new($self->{service}, $psock); 
    78         } elsif ($service_role eq "management") { 
    79             Perlbal::ClientManage->new($self->{service}, $psock); 
    80         } elsif ($service_role eq "web_server") { 
    81             Perlbal::ClientHTTP->new($self->{service}, $psock); 
    82         } elsif ($service_role eq "selector") { 
    83             # will be cast to a more specific class later... 
    84             Perlbal::ClientHTTPBase->new($self->{service}, $psock, $self->{service}); 
    85         } elsif (my $creator = Perlbal::Service::get_role_creator($service_role)) { 
    86             # was defined by a plugin, so we want to return one of these 
    87             $creator->($self->{service}, $psock); 
     105        if (Perlbal::DEBUG >= 1) { 
     106            my ($pport, $pipr) = $self->{v6} ? 
     107                Socket6::unpack_sockaddr_in6($peeraddr) : 
     108                Socket::sockaddr_in($peeraddr); 
     109            my $pip = $self->{v6} ? 
     110                "[" . Socket6::inet_ntop(Socket6::AF_INET6(), $pipr) . "]" : 
     111                Socket::inet_ntoa($pipr); 
     112            print "Got new conn: $psock ($pip:$pport) for " . $self->{service}->role . "\n"; 
    88113        } 
    89114 
     115        # SSL promotion if necessary 
     116        if ($self->{sslopts}) { 
     117            # try to upgrade to SSL, this does no IO it just re-blesses 
     118            # and prepares the SSL engine for handling us later 
     119            IO::Socket::SSL->start_SSL( 
     120                                       $psock, 
     121                                       SSL_server => 1, 
     122                                       SSL_startHandshake => 0, 
     123                                       %{ $self->{sslopts} }, 
     124                                       ); 
     125            print "  .. socket upgraded to SSL!\n" if Perlbal::DEBUG >= 1; 
     126 
     127            # safety checking to ensure we got upgraded 
     128            return $psock->close 
     129                unless ref $psock eq 'IO::Socket::SSL'; 
     130 
     131            # class into new package and run with it 
     132            my $sslsock = new Perlbal::SocketSSL($psock, $self); 
     133            $sslsock->try_accept; 
     134 
     135            # all done from our point of view 
     136            next; 
     137        } 
     138 
     139        # puts this socket into the right class 
     140        $self->class_new_socket($psock); 
     141    } 
     142} 
     143 
     144sub class_new_socket { 
     145    my Perlbal::TCPListener $self = shift; 
     146    my $psock = shift; 
     147 
     148    my $service_role = $self->{service}->role; 
     149    if ($service_role eq "reverse_proxy") { 
     150        Perlbal::ClientProxy->new($self->{service}, $psock); 
     151    } elsif ($service_role eq "management") { 
     152        Perlbal::ClientManage->new($self->{service}, $psock); 
     153    } elsif ($service_role eq "web_server") { 
     154        Perlbal::ClientHTTP->new($self->{service}, $psock); 
     155    } elsif ($service_role eq "selector") { 
     156        # will be cast to a more specific class later... 
     157        Perlbal::ClientHTTPBase->new($self->{service}, $psock, $self->{service}); 
     158    } elsif (my $creator = Perlbal::Service::get_role_creator($service_role)) { 
     159        # was defined by a plugin, so we want to return one of these 
     160        $creator->($self->{service}, $psock); 
    90161    } 
    91162} 
     
    113184} 
    114185 
    115  
    1161861; 
    117187 
  • trunk/server/lib/mogdeps/Perlbal/Test.pm

    r1087 r1308  
     1#!/usr/bin/perl -w 
     2 
    13package Perlbal::Test; 
     4 
     5=head1 NAME 
     6 
     7Perlbal::Test - Test harness for perlbal server 
     8 
     9=head1 SYNOPSIS 
     10 
     11#  my $msock = Perlbal::Test::start_server(); 
     12 
     13=head1 DESCRIPTION 
     14 
     15Perlbal::Test provides access to a perlbal server running on the 
     16local host, for testing purposes. 
     17 
     18The server can be an already-existing server, a child process, or 
     19the current process. 
     20 
     21Various functions are provided to interact with the server. 
     22 
     23=head1 FUNCTIONS 
     24 
     25=cut 
     26 
    227use strict; 
    328use POSIX qw( :sys_wait_h ); 
    429use IO::Socket::INET; 
     30use Socket qw(MSG_NOSIGNAL IPPROTO_TCP TCP_NODELAY SOL_SOCKET); 
    531use HTTP::Response; 
    632 
     
    935@ISA = qw(Exporter); 
    1036@EXPORT = qw(ua start_server foreach_aio manage filecontent tempdir new_port 
     37             manage_multi 
    1138             mgmt_port wait_on_child dump_res resp_from_sock msock); 
    1239 
     
    1845our $free_port = 60000; 
    1946 
    20 sub mgmt_port { return $mgmt_port; } 
     47=head1 I<mgmt_port()> 
     48 
     49Return the current management port number. 
     50 
     51=cut 
     52 
     53sub mgmt_port { 
     54        return $mgmt_port; 
     55} 
    2156 
    2257END { 
    2358    manage("shutdown") if $i_am_parent; 
    2459} 
     60 
     61=head1 I<dump_res($http_response)> 
     62 
     63Return a readable string formatted from an HTTP::Response object. 
     64Only the first 80 characters of returned content are returned. 
     65 
     66=cut 
    2567 
    2668sub dump_res { 
     
    4284} 
    4385 
     86=head1 I<tempdir()> 
     87 
     88Return a newly created temporary directory. The directory will be 
     89removed automatically upon program exit. 
     90 
     91=cut 
     92 
    4493sub tempdir { 
    4594    require File::Temp; 
     
    4796} 
    4897 
     98=head1 I<new_port()> 
     99 
     100Return the next free port number in the series. Port numbers are assigned 
     101starting at 60000. 
     102 
     103=cut 
     104 
    49105sub new_port { 
    50     return $free_port++;  # FIXME: make it somehow detect if port is in use? 
    51 } 
     106    test_port() ? return $free_port++ : return new_port($free_port++); 
     107} 
     108 
     109=head1 I<test_port()> 
     110 
     111Return 1 if the port is free to use for listening on $free_port else return 0. 
     112 
     113=cut 
     114 
     115sub test_port { 
     116    my $sock = IO::Socket::INET->new(LocalPort => $free_port) or return 0; 
     117    $sock->close(); 
     118    return 1; 
     119} 
     120 
     121=head1 I<filecontent($file>> 
     122 
     123Return a string containing the contents of the file $file. If $file 
     124cannot be opened, then return undef. 
     125 
     126=cut 
    52127 
    53128sub filecontent { 
     
    60135} 
    61136 
     137=head1 I<foreach_aio($callback)> 
     138 
     139Set the server into each AIO mode (none, ioaio) and call the specified 
     140callback function with the mode name as argument. 
     141 
     142=cut 
     143 
    62144sub foreach_aio (&) { 
    63145    my $cb = shift; 
    64146 
    65     foreach my $mode (qw(none linux ioaio)) { 
     147    foreach my $mode (qw(none ioaio)) { 
    66148        my $line = manage("SERVER aio_mode = $mode"); 
    67149        next unless $line; 
     
    70152} 
    71153 
     154=head1 I<manage($cmd, %opts)> 
     155 
     156Send a command $cmd to the server, and return the response line from 
     157the server. 
     158 
     159Optional arguments are: 
     160 
     161  quiet_failure => 1 
     162 
     163Output a warning if the response indicated an error, 
     164unless $opts{quiet_failure} is true, or the command 
     165was 'shutdown' (which doesn't return a response). 
     166 
     167=cut 
     168 
    72169sub manage { 
    73170    my $cmd = shift; 
     171    my %opts = @_; 
     172 
    74173    print $msock "$cmd\r\n"; 
    75174    my $res = <$msock>; 
     175 
     176    if (!$res || $res =~ /^ERR/) { 
     177        # Make the result visible in failure cases, unless 
     178        # the command was 'shutdown'... cause that never 
     179        # returns anything. 
     180        warn "Manage command failed: '$cmd' '$res'\n" 
     181            unless($opts{quiet_failure} || $cmd eq 'shutdown'); 
     182 
     183        return 0; 
     184    } 
     185    return $res; 
     186} 
     187 
     188=head1 I<manage_multi($cmd)> 
     189 
     190Send a command $cmd to the server, and return a multi-line 
     191response. Return the number zero if there was an error or 
     192no response. 
     193 
     194=cut 
     195 
     196sub manage_multi { 
     197    my $cmd = shift; 
     198 
     199    print $msock "$cmd\r\n"; 
     200    my $res; 
     201    while (<$msock>) { 
     202        last if /^\./; 
     203        last if /^ERROR/; 
     204        $res .= $_; 
     205    } 
    76206    return 0 if !$res || $res =~ /^ERR/; 
    77207    return $res; 
    78208} 
     209 
     210=head1 I<start_server($conf)> 
     211 
     212Optionally start a perlbal server and return a socket connected to its 
     213management port. 
     214 
     215The argument $conf is a string specifying initial configuration 
     216commands. 
     217 
     218If the environment variable TEST_PERLBAL_FOREGROUND is set to a true 
     219value then a server will be started in the foreground, in which case 
     220this function does not return. When the server function finishes, 
     221exit() will be called to terminate the process. 
     222 
     223If the environment variable TEST_PERLBAL_USE_EXISTING is set to a true 
     224value then a socket will be returned which is connected to an existing 
     225server's management port. 
     226 
     227Otherwise, a child process is forked and a socket is returned which is 
     228connected to the child's management port. 
     229 
     230The management port is assigned automatically, a new port number each 
     231time this function is called. The starting port number is 60000. 
     232 
     233=cut 
    79234 
    80235sub start_server { 
     
    117272} 
    118273 
     274# Start a perlbal server running and tell it to listen on the specified 
     275# management port number. This function does not return. 
     276 
    119277sub _start_perbal_server { 
    120278    my ($conf, $mgmt_port) = @_; 
     
    140298} 
    141299 
    142 # get the manager socket 
     300 
     301=head1 I<msock()> 
     302 
     303Return a reference to the socket connected to the server's management 
     304port. 
     305 
     306=cut 
     307 
    143308sub msock { 
    144309    return $msock; 
    145310} 
     311 
     312 
     313=head1 I<ua()> 
     314 
     315Return a new instance of LWP::UserAgent. 
     316 
     317=cut 
    146318 
    147319sub ua { 
     
    150322    return LWP::UserAgent->new; 
    151323} 
     324 
     325=head1 I<wait_on_child($pid, $port)> 
     326 
     327Return a socket which is connected to a child process. 
     328 
     329$pid specifies the child process id, and $port is the port number on 
     330which the child is listening. 
     331 
     332Several attempts are made; if the child dies or a connection cannot 
     333be made within 5 seconds then this function dies with an error message. 
     334 
     335=cut 
    152336 
    153337sub wait_on_child { 
     
    167351} 
    168352 
     353=head1 I<resp_from_sock($sock)> 
     354 
     355Read an HTTP response from a socket and return it 
     356as an HTTP::Response object 
     357 
     358In scalar mode, return only the $http_response object. 
     359 
     360In array mode, return an array of ($http_response, $firstline) where 
     361$firstline is the first line read from the socket, for example: 
     362 
     363"HTTP/1.1 200 OK" 
     364 
     365=cut 
     366 
    169367sub resp_from_sock { 
    170368    my $sock = shift; 
  • trunk/server/lib/mogdeps/Perlbal/Test/WebClient.pm

    r1087 r1308  
    2929    my $self = shift; 
    3030    if (@_) { 
     31        $self->{_sock} = undef; 
    3132        return $self->{server} = shift; 
    3233    } else { 
     
    3940    my $self = shift; 
    4041    if (@_) { 
     42        $self->{_sock} = undef; 
    4143        return $self->{host} = shift; 
    4244    } else { 
  • trunk/server/lib/mogdeps/Perlbal/UploadListener.pm

    r1087 r1308  
    22# Listen for UDP upload status packets 
    33# 
    4 # Copyright 2005-2006, Six Apart, Ltd. 
     4# Copyright 2005-2007, Six Apart, Ltd. 
    55 
    66 
     
    2727    return Perlbal::error("Error creating listening socket: " . ($@ || $!)) 
    2828        unless $sock; 
    29  
    30     my $self = $class->SUPER::new($sock); 
     29    my $self = fields::new($class); 
     30    $self->SUPER::new($sock); 
    3131    $self->{service} = $service; 
    3232    $self->{hostport} = $hostport; 
    33     bless $self, ref $class || $class; 
    3433    $self->watch_read(1); 
    3534    return $self; 
  • trunk/server/lib/mogdeps/Perlbal/Util.pm

    r1087 r1308  
    99sub durl { 
    1010    my ($txt) = @_; 
    11     $txt =~ tr/+/ /; 
    1211    $txt =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; 
    1312    return $txt; 
     13} 
     14 
     15=head2 C< rebless > 
     16 
     17Safely re-bless a locked (use fields) hash into another package. Note 
     18that for our convenience elsewhere the set of allowable keys for the 
     19re-blessed hash will be the union of the keys allowed by its old package 
     20and those allowed for the package into which it is blessed. 
     21 
     22=cut 
     23 
     24BEGIN { 
     25    if ($] >= 5.010) { 
     26        eval q{ 
     27            use Hash::Util qw(legal_ref_keys unlock_ref_keys lock_ref_keys) 
     28        }; 
     29        *rebless = sub { 
     30            my ($obj, $pkg) = @_; 
     31            my @keys = legal_ref_keys($obj); 
     32            unlock_ref_keys($obj); 
     33            bless $obj, $pkg; 
     34            lock_ref_keys($obj, @keys, 
     35                          legal_ref_keys(fields::new($pkg))); 
     36            return $obj; 
     37        }; 
     38    } 
     39    else { 
     40        *rebless = sub { 
     41            my ($obj, $pkg) = @_; 
     42            return bless $obj, $pkg; 
     43        }; 
     44    } 
    1445} 
    1546