Changeset 1308
- Timestamp:
- 10/16/09 01:54:12 (6 weeks ago)
- Location:
- trunk/server
- Files:
-
- 11 added
- 24 modified
-
MANIFEST (modified) (2 diffs)
-
lib/mogdeps/Perlbal.pm (modified) (23 diffs)
-
lib/mogdeps/Perlbal/AIO.pm (modified) (1 diff)
-
lib/mogdeps/Perlbal/BackendHTTP.pm (modified) (13 diffs)
-
lib/mogdeps/Perlbal/ClientHTTP.pm (modified) (8 diffs)
-
lib/mogdeps/Perlbal/ClientHTTPBase.pm (modified) (19 diffs)
-
lib/mogdeps/Perlbal/ClientManage.pm (modified) (3 diffs)
-
lib/mogdeps/Perlbal/ClientProxy.pm (modified) (20 diffs)
-
lib/mogdeps/Perlbal/CommandContext.pm (modified) (1 diff)
-
lib/mogdeps/Perlbal/HTTPHeaders.pm (modified) (7 diffs)
-
lib/mogdeps/Perlbal/ManageCommand.pm (modified) (1 diff)
-
lib/mogdeps/Perlbal/Plugin/AccessControl.pm (modified) (6 diffs)
-
lib/mogdeps/Perlbal/Plugin/AtomInject.pm (added)
-
lib/mogdeps/Perlbal/Plugin/AtomStream.pm (added)
-
lib/mogdeps/Perlbal/Plugin/Cgilike.pm (added)
-
lib/mogdeps/Perlbal/Plugin/EchoService.pm (added)
-
lib/mogdeps/Perlbal/Plugin/Highpri.pm (modified) (1 diff)
-
lib/mogdeps/Perlbal/Plugin/Include.pm (added)
-
lib/mogdeps/Perlbal/Plugin/LazyCDN.pm (added)
-
lib/mogdeps/Perlbal/Plugin/MaxContentLength.pm (added)
-
lib/mogdeps/Perlbal/Plugin/Palimg.pm (added)
-
lib/mogdeps/Perlbal/Plugin/Redirect.pm (added)
-
lib/mogdeps/Perlbal/Plugin/Stats.pm (modified) (3 diffs)
-
lib/mogdeps/Perlbal/Plugin/Vhosts.pm (modified) (3 diffs)
-
lib/mogdeps/Perlbal/Plugin/Vpaths.pm (added)
-
lib/mogdeps/Perlbal/Pool.pm (modified) (2 diffs)
-
lib/mogdeps/Perlbal/ReproxyManager.pm (modified) (1 diff)
-
lib/mogdeps/Perlbal/Service.pm (modified) (22 diffs)
-
lib/mogdeps/Perlbal/Socket.pm (modified) (8 diffs)
-
lib/mogdeps/Perlbal/SocketSSL.pm (added)
-
lib/mogdeps/Perlbal/TCPListener.pm (modified) (6 diffs)
-
lib/mogdeps/Perlbal/Test.pm (modified) (11 diffs)
-
lib/mogdeps/Perlbal/Test/WebClient.pm (modified) (2 diffs)
-
lib/mogdeps/Perlbal/UploadListener.pm (modified) (2 diffs)
-
lib/mogdeps/Perlbal/Util.pm (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/server/MANIFEST
r1279 r1308 39 39 lib/mogdeps/Perlbal/Plugin/Stats.pm 40 40 lib/mogdeps/Perlbal/Plugin/Vhosts.pm 41 lib/mogdeps/Perlbal/Plugin/AtomInject.pm 42 lib/mogdeps/Perlbal/Plugin/AtomStream.pm 43 lib/mogdeps/Perlbal/Plugin/Cgilike.pm 44 lib/mogdeps/Perlbal/Plugin/EchoService.pm 45 lib/mogdeps/Perlbal/Plugin/Include.pm 46 lib/mogdeps/Perlbal/Plugin/LazyCDN.pm 47 lib/mogdeps/Perlbal/Plugin/MaxContentLength.pm 48 lib/mogdeps/Perlbal/Plugin/Palimg.pm 49 lib/mogdeps/Perlbal/Plugin/Redirect.pm 50 lib/mogdeps/Perlbal/Plugin/Vpaths.pm 41 51 lib/mogdeps/Perlbal/Pool.pm 42 52 lib/mogdeps/Perlbal/ReproxyManager.pm … … 49 59 lib/mogdeps/Perlbal/UploadListener.pm 50 60 lib/mogdeps/Perlbal/Util.pm 61 lib/mogdeps/Perlbal/SocketSSL.pm 51 62 lib/mogdeps/Sys/Syscall.pm 52 63 lib/MogileFS/Class.pm -
trunk/server/lib/mogdeps/Perlbal.pm
r1087 r1308 1 1 #!/usr/bin/perl 2 2 # 3 # Copyright 2004, Danga Interacti ce, Inc.4 # Copyright 2005-200 6, Six Apart, Ltd.3 # Copyright 2004, Danga Interactive, Inc. 4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 # 6 6 … … 15 15 =head1 COPYRIGHT AND LICENSE 16 16 17 Copyright 2004, Danga Interacti ce, Inc.18 Copyright 2005-200 6, Six Apart, Ltd.17 Copyright 2004, Danga Interactive, Inc. 18 Copyright 2005-2007, Six Apart, Ltd. 19 19 20 20 You can use and redistribute Perlbal under the same terms as Perl itself. … … 26 26 BEGIN { 27 27 # keep track of anonymous subs' origins: 28 $^P = 0x200;28 $^P |= 0x200; 29 29 } 30 30 … … 34 34 35 35 use vars qw($VERSION); 36 $VERSION = '1. 59';36 $VERSION = '1.73'; 37 37 38 38 use constant DEBUG => $ENV{PERLBAL_DEBUG} || 0; … … 54 54 # incremented every second by a timer: 55 55 $Perlbal::tick_time = time(); 56 57 # Set to 1 when we open syslog, and 0 when we close it 58 $Perlbal::syslog_open = 0; 56 59 57 60 use Getopt::Long; … … 90 93 our(%plugins); # plugin => 1 (shows loaded plugins) 91 94 our($last_error); 95 our $service_autonumber = 1; # used to generate names for anonymous services created with Perlbal->create_service() 92 96 our $vivify_pools = 1; # if on, allow automatic creation of pools 93 97 our $foreground = 1; # default to foreground … … 95 99 our $reqs = 0; # total number of requests we've done 96 100 our $starttime = time(); # time we started 101 our $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. 104 our $run_started = 0; 97 105 our ($lastutime, $laststime, $lastreqs) = (0, 0, 0); # for deltas 98 106 … … 186 194 my $class = shift; 187 195 return $service{$_[0]}; 196 } 197 198 sub 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); 188 211 } 189 212 … … 241 264 # expand variables 242 265 $cmd =~ s/\$\{(.+?)\}/_expand_config_var($1)/eg; 266 $cmd =~ s/\$(\w+)/$ENV{$1}/g; 243 267 244 268 $out ||= sub {}; … … 381 405 382 406 sub MANAGE_shutdown { 383 my $mc = shift->parse(qr/^shutdown( graceful)?$/);407 my $mc = shift->parse(qr/^shutdown(\s?graceful)?\s?(\d+)?$/); 384 408 385 409 # immediate shutdown … … 415 439 }); 416 440 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 417 446 # so they know something happened 418 447 return $mc->ok; 448 } 449 450 sub 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 } 419 475 } 420 476 … … 632 688 foreach (sort { $a <=> $b } keys %$sf) { 633 689 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; 635 695 $mc->out(sprintf("%5d %5ds %s", $_, $age, $sock->as_string)); 636 696 } … … 708 768 my %states; # { "Class" => { "State" => int count; } } 709 769 foreach my $sock (values %$sf) { 770 next unless $sock->can('state'); 710 771 my $state = $sock->state; 711 772 next unless defined $state; … … 735 796 next unless $svc->{role} eq 'reverse_proxy'; 736 797 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 } 748 812 } 749 813 $mc->end; … … 767 831 my $mc = shift->parse(qr/^leaks(?:\s+(.+))?$/); 768 832 return $mc->err("command disabled without \$ENV{PERLBAL_DEBUG} set") 769 unless $ENV{PER BAL_DEBUG};833 unless $ENV{PERLBAL_DEBUG}; 770 834 771 835 my $what = $mc->arg(1); … … 891 955 } 892 956 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 893 974 return $mc->err("unknown server option '$val'"); 975 } 976 977 sub 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 894 1014 } 895 1015 … … 908 1028 return $mc->err("service '$name' already exists") if $service{$name}; 909 1029 return $mc->err("pool '$name' already exists") if $pool{$name}; 910 $service{$name} = Perlbal::Service->new($name);1030 Perlbal->create_service($name); 911 1031 $mc->{ctx}{last_created} = $name; 912 1032 return $mc->ok; … … 1090 1210 sub load_config { 1091 1211 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"; 1093 1213 my $ctx = Perlbal::CommandContext->new; 1094 1214 $ctx->verbose(0); 1095 while (my $line = <F>) { 1096 $line =~ s/\$(\w+)/$ENV{$1}/g; 1215 while (my $line = <$fh>) { 1097 1216 return 0 unless run_manage_command($line, $writer, $ctx); 1098 1217 } 1099 close( F);1218 close($fh); 1100 1219 return 1; 1101 1220 } … … 1118 1237 unless $sess_id = POSIX::setsid(); 1119 1238 1120 ## Prevent possibility of acquiring a control ing terminal1239 ## Prevent possibility of acquiring a controlling terminal 1121 1240 $SIG{'HUP'} = 'IGNORE'; 1122 1241 if ($pid = fork) { exit 0; } … … 1139 1258 } 1140 1259 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. 1263 sub 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. 1141 1288 sub run { 1289 1142 1290 # setup for logging 1143 1291 Sys::Syslog::openlog('perlbal', 'pid', 'daemon') if $Perlbal::SYSLOG_AVAILABLE; 1292 $Perlbal::syslog_open = 1; 1144 1293 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(); 1157 1298 1158 1299 Danga::Socket->SetLoopTimeout(1000); … … 1163 1304 }); 1164 1305 1165 run_global_hook("pre_event_loop");1166 1167 1306 # begin the overall loop to try to capture if Perlbal dies at some point 1168 1307 # so we can have a log of it … … 1179 1318 $clean_exit = 0; 1180 1319 } 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 1181 1326 Perlbal::log('info', 'ending run'); 1327 $Perlbal::syslog_open = 0; 1182 1328 Sys::Syslog::closelog() if $Perlbal::SYSLOG_AVAILABLE; 1183 1329 … … 1189 1335 if ($foreground) { 1190 1336 # syslog acts like printf so we have to use printf and append a \n 1191 shift; # ignore the first parameter (info, warn, crit ical, etc)1337 shift; # ignore the first parameter (info, warn, crit, etc) 1192 1338 printf(shift(@_) . "\n", @_); 1193 1339 } else { 1194 1340 # 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 1346 sub _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 1363 sub _remove_pidfile { 1364 my $file = shift; 1365 1366 unlink $file; 1367 return 1; 1368 } 1369 1198 1370 1199 1371 # Local Variables: -
trunk/server/lib/mogdeps/Perlbal/AIO.pm
r1087 r1308 1 1 # AIO abstraction layer 2 2 # 3 # Copyright 2004, Danga Interacti ce, Inc.4 # Copyright 2005-200 6, Six Apart, Ltd.3 # Copyright 2004, Danga Interactive, Inc. 4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 6 6 package Perlbal::AIO; -
trunk/server/lib/mogdeps/Perlbal/BackendHTTP.pm
r1087 r1308 2 2 # HTTP connection to backend node 3 3 # 4 # Copyright 2004, Danga Interacti ce, Inc.5 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2004, Danga Interactive, Inc. 5 # Copyright 2005-2007, Six Apart, Ltd. 6 6 # 7 7 … … 39 39 'generation', # int; counts what generation we were spawned in 40 40 'buffered_upload_mode', # bool; if on, we're doing a buffered upload transmit 41 42 'scratch' # for plugins 41 43 ); 42 44 use Socket qw(PF_INET IPPROTO_TCP SOCK_STREAM SOL_SOCKET SO_ERROR … … 62 64 # reportto => object obeying reportto interface 63 65 sub new { 64 my ($class, $svc, $ip, $port, $opts) = @_; 66 my Perlbal::BackendHTTP $self = shift; 67 my ($svc, $ip, $port, $opts) = @_; 65 68 $opts ||= {}; 66 69 … … 72 75 return undef; 73 76 } 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 } 74 82 75 83 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; 79 87 $self->SUPER::new($sock); 80 88 … … 105 113 $self->init; 106 114 107 bless $self, ref $class || $class;108 115 $self->watch_write(1); 109 116 return $self; … … 167 174 168 175 $self->init; 169 bless $self, ref $class || $class;170 176 $self->watch_write(1); 171 177 return $self; … … 265 271 if ($svc->trusted_ip($client_ip)) { 266 272 # 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 } 270 279 } else { 271 280 # no, don't trust upstream (untrusted client), so remove all their … … 343 352 !$self->{has_attention} && !defined $NoVerify{$self->{ipport}}) { 344 353 354 return if $self->{service}->run_hook('backend_write_verify', $self); 355 345 356 # 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"); 347 358 $self->watch_read(1); 348 359 $self->{waiting_options} = 1; … … 368 379 } 369 380 381 sub 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 370 390 sub verify_failure { 371 391 my Perlbal::BackendHTTP $self = shift; … … 378 398 sub event_read_waiting_options { # : void 379 399 my Perlbal::BackendHTTP $self = shift; 400 401 if (defined $self->{service}) { 402 return if $self->{service}->run_hook('backend_readable_verify', $self); 403 } 404 380 405 if ($self->{content_length_remain}) { 381 406 # the HTTP/1.1 spec says OPTIONS responses can have content-lengths, … … 394 419 # if present: 395 420 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; 401 422 } 402 423 return; … … 454 475 if ((my $rep = $hd->header('X-REPROXY-FILE')) && $self->may_reproxy) { 455 476 # make the client begin the async IO while we move on 477 $self->next_request; 456 478 $client->start_reproxy_file($rep, $hd); 479 return; 480 } elsif ((my $urls = $hd->header('X-REPROXY-URL')) && $self->may_reproxy) { 457 481 $self->next_request; 458 return;459 } elsif ((my $urls = $hd->header('X-REPROXY-URL')) && $self->may_reproxy) {460 482 $self->{service}->add_to_reproxy_url_cache($rqhd, $hd) 461 483 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) { 463 487 $self->next_request; 464 return;465 } elsif ((my $svcname = $hd->header('X-REPROXY-SERVICE')) && $self->may_reproxy) {466 488 $self->{client} = undef; 467 $client->start_reproxy_service($self->{res_headers}, $svcname); 468 $self->next_request; 489 $client->start_reproxy_service($hd, $svcname); 469 490 return; 470 491 } elsif ($res_code == 500 && … … 500 521 # also update the response code, in case of 206 partial content 501 522 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 } 503 528 $thd->code(200) if $thd->response_code == 204; # upgrade HTTP No Content (204) to 200 OK. 504 529 } -
trunk/server/lib/mogdeps/Perlbal/ClientHTTP.pm
r1087 r1308 3 3 # most functionality is implemented in the base class. 4 4 # 5 # Copyright 2004, Danga Interacti ce, Inc.6 # Copyright 2005-200 6, Six Apart, Ltd.5 # Copyright 2004, Danga Interactive, Inc. 6 # Copyright 2005-2007, Six Apart, Ltd. 7 7 # 8 8 … … 13 13 14 14 use base "Perlbal::ClientHTTPBase"; 15 use Perlbal::Util; 15 16 16 17 use fields ('put_in_progress', # 1 when we're currently waiting for an async job to return … … 47 48 my $class = shift; 48 49 my Perlbal::ClientHTTPBase $cb = shift; # base object 49 bless $cb, $class;50 Perlbal::Util::rebless($cb, $class); 50 51 $cb->init; 51 52 … … 102 103 my $hd = $self->{req_headers}; 103 104 105 $self->check_req_headers; 106 104 107 # fully formed request received 105 108 $self->{requests}++; … … 143 146 # bigger than any specified max put size 144 147 return $self->send_response(400, "Content-length of $clen is invalid.") 145 if !$clen || 148 if ! defined($clen) || 149 $clen < 0 || 146 150 ($self->{service}->{max_put_size} && 147 151 $clen > $self->{service}->{max_put_size}); 148 152 149 # if we have some data already from a header over-read, note it150 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) { 151 155 $self->{content_length_remain} -= $self->{read_ahead}; 152 156 } … … 225 229 226 230 my $disk_path = $self->{service}->{docroot} . '/' . $path; 227 $self->start_put_open($disk_path, $filename);228 231 229 232 $self->{chunked_upload_state} = Perlbal::ChunkedUploadState->new(%{{ … … 265 268 }, 266 269 }}); 270 271 $self->start_put_open($disk_path, $filename); 267 272 268 273 return 1; … … 393 398 $self->{put_fh_filename} = "$path/$file"; 394 399 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 395 408 $self->put_writeout; 396 409 }); -
trunk/server/lib/mogdeps/Perlbal/ClientHTTPBase.pm
r1087 r1308 8 8 # for another request from the user 9 9 # 10 # Copyright 2004, Danga Interacti ce, Inc.11 # Copyright 2005-200 6, Six Apart, Ltd.10 # Copyright 2004, Danga Interactive, Inc. 11 # Copyright 2005-2007, Six Apart, Ltd. 12 12 13 13 package Perlbal::ClientHTTPBase; … … 40 40 41 41 use Fcntl ':mode'; 42 use Errno qw( EPIPE ECONNRESET);42 use Errno qw(EPIPE ECONNRESET); 43 43 use POSIX (); 44 44 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 47 46 our $MimeType = {qw( 48 47 css text/css … … 55 54 mp3 audio/mpeg 56 55 mpg video/mpeg 56 pdf application/pdf 57 57 png image/png 58 58 tif image/tiff … … 65 65 # ClientHTTPBase 66 66 sub 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; 71 71 $self->SUPER::new($sock); # init base fields 72 72 … … 80 80 $self->state('reading_headers'); 81 81 82 bless $self, ref $class || $class;83 82 $self->watch_read(1); 84 83 return $self; … … 125 124 if ($do_keepalive) { 126 125 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}; 128 127 $reshd->header('Connection', 'keep-alive'); 129 128 $reshd->header('Keep-Alive', $timeout ? "timeout=$timeout, max=100" : undef); … … 137 136 $reshd->header('Keep-Alive', undef); 138 137 } 138 } 139 140 # overridden here from Perlbal::Socket to use the service value 141 sub 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. 146 sub persist_wait { 147 139 148 } 140 149 … … 189 198 $self->state('persist_wait'); 190 199 200 $self->persist_wait; 201 191 202 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 } 193 206 } 194 207 … … 210 223 $self->{reproxy_file_offset} = 0; 211 224 $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 } 217 260 } 218 261 … … 222 265 sub event_read { 223 266 my Perlbal::ClientHTTPBase $self = shift; 267 268 $self->{alive_time} = $Perlbal::tick_time; 224 269 225 270 # see if we have headers? … … 236 281 # handle it yet. must wait for the selector (which has as much 237 282 # time as it wants) to route as to our subclass, which can then 238 # re nable reads.283 # re-enable reads. 239 284 $self->watch_read(0); 240 285 … … 257 302 } 258 303 304 sub 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 259 317 # client is ready for more of its file. so sendfile some more to it. 260 318 # (called by event_write when we're actually in this mode) … … 264 322 my $remain = $self->{reproxy_file_size} - $self->{reproxy_file_offset}; 265 323 $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 } 266 361 267 362 # cap at 128k sendfiles 268 363 my $to_send = $remain > 128 * 1024 ? 128 * 1024 : $remain; 269 270 $self->watch_write(0);271 364 272 365 my $postread = sub { … … 289 382 290 383 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; 302 385 } else { 303 386 $self->watch_write(1); … … 533 616 return $self->_simple_response(403, "Multiple file serving isn't enabled") unless $svc->{enable_concatenate_get}; 534 617 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; 535 619 536 620 my $remain = @multiple_files + 1; # 1 for the base directory … … 585 669 } 586 670 587 # What is -f _ doing here? don't we detect the exist ance 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? 588 672 my $not_mod = $ims eq $lastmod && -f _; 589 673 … … 651 735 }; 652 736 $self->{post_sendfile_cb}->(); 737 } 738 739 sub 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; 653 753 } 654 754 … … 765 865 } 766 866 767 # FIXME: let this be configurable?768 sub max_idle_time { 30; }769 770 867 sub event_err { my $self = shift; $self->close('error'); } 771 868 sub event_hup { my $self = shift; $self->close('hup'); } 772 869 870 sub _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 773 878 sub as_string { 774 879 my Perlbal::ClientHTTPBase $self = shift; … … 776 881 my $ret = $self->SUPER::as_string; 777 882 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; 779 885 $ret .= ": localport=$lport" if $lport; 886 $ret .= "; observed_ip=$observed" if defined $observed; 780 887 $ret .= "; reqs=$self->{requests}"; 781 888 $ret .= "; $self->{state}"; -
trunk/server/lib/mogdeps/Perlbal/ClientManage.pm
r1087 r1308 17 17 # ClientManage 18 18 sub 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); 21 23 $self->{service} = $service; 22 24 $self->{buf} = ""; # what we've read so far, not forming a complete line … … 25 27 $self->{ctx}->verbose(1); 26 28 27 bless $self, ref $class || $class;28 29 $self->watch_read(1); 29 30 return $self; … … 112 113 my Perlbal::Service $svc = Perlbal->service($sname); 113 114 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"; 115 117 } 116 118 $body .= "</ul></li>"; -
trunk/server/lib/mogdeps/Perlbal/ClientProxy.pm
r1087 r1308 2 2 # HTTP Connection from a reverse proxy client 3 3 # 4 # Copyright 2004, Danga Interacti ce, Inc.5 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2004, Danga Interactive, Inc. 5 # Copyright 2005-2007, Six Apart, Ltd. 6 6 # 7 7 package Perlbal::ClientProxy; … … 12 12 13 13 use Perlbal::ChunkedUploadState; 14 use Perlbal::Util; 14 15 15 16 use fields ( … … 60 61 # ClientProxy 61 62 sub 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 ); 67 67 68 68 Perlbal::objctor($self); 69 bless $self, ref $class || $class;70 69 71 70 $self->init; … … 77 76 my $class = shift; 78 77 my Perlbal::ClientHTTPBase $cb = shift; 79 bless $cb, $class;78 Perlbal::Util::rebless($cb, $class); 80 79 $cb->init; 81 80 $cb->watch_read(1); … … 232 231 $extra_hdr .= "Range: $range\r\n"; 233 232 } 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 } 237 236 238 237 my $req_method = $self->{req_headers}->request_method eq 'HEAD' ? 'HEAD' : 'GET'; … … 318 317 319 318 # if the thing we're reproxying is indeed a file, advertise that 320 # we support byte ranges on it319 # we support byte ranges on it 321 320 if (-f _) { 322 321 $hd->header("Accept-Ranges", "bytes"); … … 428 427 print "ClientProxy::backend_finished\n" if Perlbal::DEBUG >= 3; 429 428 430 # mark ourselves as having responded (presum eably if we're here,429 # mark ourselves as having responded (presumably if we're here, 431 430 # the backend has responded already) 432 431 $self->{responded} = 1; … … 440 439 441 440 # if we get here (and we do, rarely, in practice) then that means 442 # the backend read was empty/discon ected (or otherwise messed up),441 # the backend read was empty/disconnected (or otherwise messed up), 443 442 # and the only thing we can really do is close the client down. 444 443 $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. 447 sub 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); 445 451 } 446 452 … … 506 512 my $reason = shift; 507 513 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 508 520 # don't close twice 509 521 return if $self->{closed}; … … 544 556 print "ClientProxy::event_write\n" if Perlbal::DEBUG >= 3; 545 557 546 $self->SUPER::event_write;547 548 558 # obviously if we're writing the backend has processed our request 549 559 # and we are responding/have responded to the user, so mark it so 550 560 $self->{responded} = 1; 561 562 # will eventually, finally reset the whole object on completion 563 $self->SUPER::event_write; 551 564 552 565 # trigger our backend to keep reading, if it's still connected … … 731 744 my $req_hd = $self->{req_headers}; 732 745 746 unless ($req_hd) { 747 $self->close("handle_request without headers"); 748 return; 749 } 750 751 $self->check_req_headers; 752 733 753 my $svc = $self->{service}; 734 754 # give plugins a chance to force us to bail … … 742 762 # subtract out read_size, which is the amount of data that was 743 763 # 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} = 745 765 $self->{content_length_remain} = 746 766 $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 747 774 $self->{unread_data_waiting} = 1 if $self->{content_length_remain}; 748 775 } … … 1003 1030 my $clen = $self->{req_headers}->content_length; 1004 1031 if ($clen != $self->{buoutpos}) { 1005 Perlbal::log('crit ical', "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"); 1006 1033 return $self->_simple_response(500); 1007 1034 } … … 1009 1036 # reset our position so we start reading from the right spot 1010 1037 $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 1012 1039 1013 1040 # notify that we want the backend so we get the ball rolling … … 1023 1050 my $clen = $self->{request_body_length}; 1024 1051 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 } 1033 1062 1034 1063 # if we're done, purge the file and move on … … 1062 1091 # throw errors back to the user 1063 1092 if (! $self->{bufh}) { 1064 Perlbal::log('crit ical', "Failure to open $fn for buffered upload output");1093 Perlbal::log('crit', "Failure to open $fn for buffered upload output"); 1065 1094 return $self->_simple_response(500); 1066 1095 } … … 1097 1126 1098 1127 # check for error 1099 unless ($bytes ) {1100 Perlbal::log('crit ical', "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}."); 1101 1130 return $self->_simple_response(500); 1102 1131 } … … 1106 1135 1107 1136 # now check if we wrote less than we had in this chunk of buffer. if that's 1108 # the case then we need to re enqueue the part of the chunk that wasn't1137 # the case then we need to re-enqueue the part of the chunk that wasn't 1109 1138 # written out and update as appropriate. 1110 1139 if ($bytes < $len) { … … 1144 1173 my Perlbal::ClientProxy $self = shift; 1145 1174 1175 # Main reason for failure below is a 0-length chunked upload, where the file is never created. 1176 return unless $self->{bufh}; 1177 1146 1178 # FIXME: it's reported that sometimes the two now-in-eval blocks 1147 1179 # fail, hence the eval blocks and warnings. the FIXME is to … … 1157 1189 1158 1190 eval { 1159 # now async ronously unlink the file1191 # now asynchronously unlink the file 1160 1192 Perlbal::AIO::aio_unlink($self->{bufilename}, sub { 1161 1193 if ($!) { -
trunk/server/lib/mogdeps/Perlbal/CommandContext.pm
r1087 r1308 2 2 # can be less verbose when in config files 3 3 # 4 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 # 6 6 -
trunk/server/lib/mogdeps/Perlbal/HTTPHeaders.pm
r1087 r1308 2 2 # HTTP header class (both request and response) 3 3 # 4 # Copyright 2004, Danga Interacti ce, Inc.5 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2004, Danga Interactive, Inc. 5 # Copyright 2005-2007, Six Apart, Ltd. 6 6 # 7 7 … … 10 10 use warnings; 11 11 no warnings qw(deprecated); 12 13 use Perlbal; 12 14 13 15 use fields ( … … 30 32 204 => 'No Content', 31 33 206 => 'Partial Content', 34 301 => 'Permanent Redirect', 35 302 => 'Found', 32 36 304 => 'Not Modified', 33 37 400 => 'Bad request', … … 104 108 # check for valid response line 105 109 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+(.*)$!; 107 111 108 112 my ($ver_ma, $ver_mi, $code) = ($1, $2, $3); … … 223 227 } 224 228 229 sub 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 225 248 sub version_number { 226 249 my Perlbal::HTTPHeaders $self = $_[0]; … … 243 266 244 267 return $self->{headers}{$key} = shift; 268 } 269 270 sub headers_list { 271 my Perlbal::HTTPHeaders $self = shift; 272 return [$self->{headers} ? keys %{ $self->{headers} } : ()]; 245 273 } 246 274 … … 407 435 # returns (status, range_start, range_end) when given a size 408 436 # status = 200 - invalid or non-existent range header. serve normally. 409 # status = 206 - pars able range is good. serve partial content.437 # status = 206 - parseable range is good. serve partial content. 410 438 # status = 416 - Range is unsatisfiable 411 439 sub range { -
trunk/server/lib/mogdeps/Perlbal/ManageCommand.pm
r1087 r1308 2 2 # to a command should be done through this instance (out, err, ok, etc) 3 3 # 4 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 # 6 6 -
trunk/server/lib/mogdeps/Perlbal/Plugin/AccessControl.pm
r1087 r1308 35 35 (?:\s+(\S+))? # optional arg2 36 36 $/x, 37 "usage: ACCESS [<service>]<cmd> <arg1> [<arg2>]");37 "usage: ACCESS <cmd> <arg1> [<arg2>]"); 38 38 my ($cmd, $arg1, $arg2) = $mc->args; 39 39 … … 87 87 sub unload { 88 88 my $class = shift; 89 Perlbal::unregister_global_hook('manage_command. vhost');89 Perlbal::unregister_global_hook('manage_command.access'); 90 90 return 1; 91 91 } … … 94 94 sub register { 95 95 my ($class, $svc) = @_; 96 97 my $use_observed_ip; 96 98 97 99 $svc->register_hook('AccessControl', 'start_http_request', sub { … … 126 128 my $rule = shift; 127 129 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 129 134 return $peer_ip eq $rule->[2]; 130 135 } 131 136 132 137 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 134 142 return eval { $rule->[2]->match($peer_ip); }; 135 143 } 136 137 144 }; 138 145 … … 148 155 }); 149 156 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 150 171 return 1; 151 172 } … … 157 178 } 158 179 180 sub 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 159 201 1; -
trunk/server/lib/mogdeps/Perlbal/Plugin/Highpri.pm
r1087 r1308 39 39 my $temp; 40 40 unless ($val eq 'undef' || $val eq 'none' || $val eq 'null') { 41 # verify this regex works? do it in an eval because qr will die41 # verify this regexp works? do it in an eval because qr will die 42 42 # if we give it something invalid 43 43 eval { -
trunk/server/lib/mogdeps/Perlbal/Plugin/Stats.pm
r1087 r1308 28 28 29 29 # 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 on the right side.30 # we simply increment the count of the stat on the right side. 31 31 my %simple = qw( 32 32 start_send_file files_sent … … 44 44 $svc->register_hook('Stats', 'backend_client_assigned', sub { 45 45 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})++; 48 49 return 0; 49 50 }); … … 51 52 my Perlbal::BackendHTTP $be = shift; 52 53 my Perlbal::ClientProxy $obj = $be->{client}; 53 my $ot = $sobj->{pending}->{"$obj"};54 my $ot = delete $sobj->{pending}->{"$obj"}; 54 55 return 0 unless defined $ot; 55 56 -
trunk/server/lib/mogdeps/Perlbal/Plugin/Vhosts.pm
r1087 r1308 4 4 5 5 # things to test: 6 # one persistent connection, first to a docs plugin, then to web proxy... see if it returns us to our base class after end of re uqest6 # one persistent connection, first to a docs plugin, then to web proxy... see if it returns us to our base class after end of request 7 7 # PUTing a large file to a selector, seeing if it is put correctly to the PUT-enabled web_server proxy 8 # obvious cases: non-exist ant 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) 9 9 # 10 10 … … 76 76 } 77 77 78 sub 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 78 94 # call back from Service via ClientHTTPBase's event_read calling service->select_new_service(Perlbal::ClientHTTPBase) 79 95 sub vhost_selector { … … 84 100 85 101 my $vhost = $req->header("Host"); 102 103 # Browsers and the Apache API considers 'www.example.com.' == 'www.example.com' 104 $vhost and $vhost =~ s/\.$//; 105 86 106 my $uri = $req->request_uri; 87 107 my $maps = $cb->{service}{extra_config}{_vhosts} ||= {}; -
trunk/server/lib/mogdeps/Perlbal/Pool.pm
r1087 r1308 3 3 ###################################################################### 4 4 # 5 # Copyright 2004, Danga Interacti ce, Inc.6 # Copyright 2005-200 6, Six Apart, Ltd.5 # Copyright 2004, Danga Interactive, Inc. 6 # Copyright 2005-2007, Six Apart, Ltd. 7 7 # 8 8 … … 95 95 } 96 96 97 sub 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 97 114 # returns string of balance method 98 115 sub balance_method { -
trunk/server/lib/mogdeps/Perlbal/ReproxyManager.pm
r1087 r1308 1 1 # HTTP connection to non-pool backend nodes (probably fast event-based webservers) 2 2 # 3 # Copyright 2004, Danga Interacti ce, Inc.4 # Copyright 2005-200 6, Six Apart, Ltd.3 # Copyright 2004, Danga Interactive, Inc. 4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 # 6 6 -
trunk/server/lib/mogdeps/Perlbal/Service.pm
r1087 r1308 3 3 ###################################################################### 4 4 # 5 # Copyright 2004, Danga Interacti ce, Inc.6 # Copyright 2005-200 6, Six Apart, Ltd.5 # Copyright 2004, Danga Interactive, Inc. 6 # Copyright 2005-2007, Six Apart, Ltd. 7 7 # 8 8 … … 14 14 use Perlbal::BackendHTTP; 15 15 use Perlbal::Cache; 16 use Perlbal::Util; 16 17 17 18 use fields ( … … 27 28 'listen', # scalar IP:port of where we're listening for new connections 28 29 'docroot', # document root for webserver role 29 'dirindexing', # bool: direc otry indexing? (for webserver role) not async.30 'dirindexing', # bool: directory indexing? (for webserver role) not async. 30 31 'index_files', # arrayref of filenames to try for index files 31 32 'enable_concatenate_get', # bool: if user can request concatenated files … … 41 42 'persist_backend', # bool: persistent connections for backends 42 43 '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 *) 43 45 'max_backend_uses', # max requests to send per kept-alive backend (default 0 = unlimited) 44 46 'connect_ahead', # scalar: number of spare backends to connect to in advance all the time … … 51 53 'trusted_upstream_proxies', # Net::Netmask object containing netmasks for trusted upstreams 52 54 '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 53 56 'enable_reproxy', # bool; if true, advertise that server will reproxy files and/or URLs 54 57 'reproxy_cache_maxsize', # int; maximum number of reproxy results to be cached. (0 is disabled and default) 55 58 'client_sndbuf_size', # int: bytes for SO_SNDBUF 56 59 'server_process' , # scalar: path to server process (executable) 60 'persist_client_timeout', # int: keep-alive timeout in seconds for clients (default is 30) 57 61 58 62 # Internal state: … … 60 64 'waiting_clients_highpri', # arrayref of high-priority clients waiting for backendhttp conns 61 65 'waiting_clients_lowpri', # arrayref of low-priority clients waiting for backendhttp conns 62 'waiting_client_count', # number of clients waiting for backend ds66 'waiting_client_count', # number of clients waiting for backends 63 67 'waiting_client_map' , # map of clientproxy fd -> 1 (if they're waiting for a conn) 64 68 'pending_connects', # hashref of "ip:port" -> $time (only one pending connect to backend at a time) … … 77 81 'buffer_backend_connect', # 0 for of, else, number of bytes to buffer before we ask for a backend 78 82 'selector', # CODE ref, or undef, for role 'selector' services 83 'default_service', # Perlbal::Service; name of a service a selector should default to 79 84 'buffer_uploads', # bool; enable/disable the buffered uploads to disk system 80 85 'buffer_uploads_path', # string; path to store buffered upload files … … 129 134 check_role => "*", 130 135 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"], 132 139 setter => sub { 133 140 my ($self, $val, $set, $mc) = @_; … … 169 176 default => 0, 170 177 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 => '*', 171 184 check_role => "reverse_proxy", 172 185 }, … … 316 329 317 330 '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 web apps 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.", 319 332 default => 0, 320 333 check_role => "web_server", … … 339 352 default => 0, 340 353 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", 341 361 check_role => "reverse_proxy", 342 362 }, … … 354 374 'trusted_upstream_proxies' => { 355 375 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 => "*", 357 377 check_type => sub { 358 378 my ($self, $val, $errref) = @_; … … 366 386 return 0; 367 387 }, 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 }, 369 394 }, 370 395 … … 372 397 check_role => "web_server", 373 398 default => "index.html", 374 des => "Comma-sep erated 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.", 375 400 setter => sub { 376 401 my ($self, $val, $set, $mc) = @_; 377 402 $self->{index_files} = [ split(/[\s,]+/, $val) ]; 378 403 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; 379 429 }, 380 430 }, … … 403 453 return $mc->ok; 404 454 }, 455 dumper => sub { 456 my ($self, $val) = @_; 457 return $val->name; 458 } 405 459 }, 406 460 … … 417 471 }, 418 472 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 419 480 'buffer_uploads_path' => { 420 481 des => "Directory root for storing files used to buffer uploads.", … … 565 626 566 627 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. 634 sub 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 643 sub 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 651 sub 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; 567 720 } 568 721 … … 1191 1344 if (my $sp = $self->{server_process}) { 1192 1345 warn "To create = $to_create...\n"; 1193 warn " spaw ing $sp\n";1346 warn " spawning $sp\n"; 1194 1347 my $be = Perlbal::BackendHTTP->new_process($self, $sp); 1195 1348 return; … … 1260 1413 return $mc->err("no header provided") unless $key; 1261 1414 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'; 1262 1416 1263 1417 if ($mode eq 'insert') { … … 1287 1441 sub selector { 1288 1442 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 } 1290 1460 return $self->{selector}; 1291 1461 } … … 1304 1474 Perlbal::ClientProxy->new_from_base($cb); 1305 1475 return; 1476 } elsif ($self->{'role'} eq "selector") { 1477 $self->selector()->($cb); 1478 return; 1306 1479 } else { 1307 1480 $cb->_simple_response(500, "Can't map to service type $self->{'role'}"); … … 1316 1489 1317 1490 $cb->{service} = $self; 1318 bless $cb, "Perlbal::ClientHTTPBase";1491 Perlbal::Util::rebless($cb, "Perlbal::ClientHTTPBase"); 1319 1492 1320 1493 # the read/watch events are reset by ClientHTTPBase's http_response_sent (our caller) -
trunk/server/lib/mogdeps/Perlbal/Socket.pm
r1087 r1308 1 1 # Base class for all socket types 2 2 # 3 # Copyright 2004, Danga Interacti ce, Inc.4 # Copyright 2005-200 6, Six Apart, Ltd.3 # Copyright 2004, Danga Interactive, Inc. 4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 6 6 package Perlbal::Socket; … … 33 33 34 34 '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. 35 39 ); 36 40 … … 107 111 108 112 # however, the grep turned our weak references back into strong ones, so 109 # we have to re weaken them113 # we have to re-weaken them 110 114 weaken($_) foreach @created_objects; 111 115 … … 132 136 my $now = time; 133 137 134 my %max_age; # classname -> max age (0 means forever)135 138 my @to_close; 136 139 while (my $k = each %$sf) { 137 140 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) { 146 146 push @to_close, $v; 147 147 } … … 220 220 $self->{headers_string} .= $$bref; 221 221 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? 224 230 if ($idx == -1) { 225 231 … … 244 250 print " pre-parsed headers: [$hstr]\n" if Perlbal::DEBUG >= 3; 245 251 246 my $extra = substr($self->{headers_string}, $idx+ 4);252 my $extra = substr($self->{headers_string}, $idx+$delim_len); 247 253 if (my $len = length($extra)) { 248 254 print " pushing back $len bytes after header\n" if Perlbal::DEBUG >= 3; … … 296 302 } 297 303 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 307 sub 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 298 319 ### METHOD: close() 299 320 ### Set our state when we get closed. … … 314 335 } 315 336 337 sub 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 316 347 sub as_string_html { 317 348 my Perlbal::Socket $self = shift; -
trunk/server/lib/mogdeps/Perlbal/TCPListener.pm
r1087 r1308 2 2 # TCP listener on a given port 3 3 # 4 # Copyright 2004, Danga Interacti ce, Inc.5 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2004, Danga Interactive, Inc. 5 # Copyright 2005-2007, Six Apart, Ltd. 6 6 7 7 … … 12 12 13 13 use base "Perlbal::Socket"; 14 use fields qw(service hostport); 14 use fields ('service', 15 'hostport', 16 'sslopts', 17 'v6', # bool: IPv6 libraries are available 18 ); 15 19 use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF); 20 21 BEGIN { 22 eval { require Perlbal::SocketSSL }; 23 if (Perlbal::DEBUG > 0 && $@) { warn "SSL support failed on load: $@\n" } 24 } 16 25 17 26 # TCPListener 18 27 sub 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; 20 32 $opts ||= {}; 21 33 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 ); 32 69 33 70 return Perlbal::error("Error creating listening socket: " . ($@ || $!)) … … 46 83 } 47 84 48 my $self = $class->SUPER::new($sock);85 $self->SUPER::new($sock); 49 86 $self->{service} = $service; 50 87 $self->{hostport} = $hostport; 51 bless $self, ref $class || $class;88 $self->{sslopts} = $opts->{ssl}; 52 89 $self->watch_read(1); 53 90 return $self; … … 60 97 # accept as many connections as we can 61 98 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 70 99 IO::Handle::blocking($psock, 0); 71 100 … … 74 103 } 75 104 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"; 88 113 } 89 114 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 144 sub 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); 90 161 } 91 162 } … … 113 184 } 114 185 115 116 186 1; 117 187 -
trunk/server/lib/mogdeps/Perlbal/Test.pm
r1087 r1308 1 #!/usr/bin/perl -w 2 1 3 package Perlbal::Test; 4 5 =head1 NAME 6 7 Perlbal::Test - Test harness for perlbal server 8 9 =head1 SYNOPSIS 10 11 # my $msock = Perlbal::Test::start_server(); 12 13 =head1 DESCRIPTION 14 15 Perlbal::Test provides access to a perlbal server running on the 16 local host, for testing purposes. 17 18 The server can be an already-existing server, a child process, or 19 the current process. 20 21 Various functions are provided to interact with the server. 22 23 =head1 FUNCTIONS 24 25 =cut 26 2 27 use strict; 3 28 use POSIX qw( :sys_wait_h ); 4 29 use IO::Socket::INET; 30 use Socket qw(MSG_NOSIGNAL IPPROTO_TCP TCP_NODELAY SOL_SOCKET); 5 31 use HTTP::Response; 6 32 … … 9 35 @ISA = qw(Exporter); 10 36 @EXPORT = qw(ua start_server foreach_aio manage filecontent tempdir new_port 37 manage_multi 11 38 mgmt_port wait_on_child dump_res resp_from_sock msock); 12 39 … … 18 45 our $free_port = 60000; 19 46 20 sub mgmt_port { return $mgmt_port; } 47 =head1 I<mgmt_port()> 48 49 Return the current management port number. 50 51 =cut 52 53 sub mgmt_port { 54 return $mgmt_port; 55 } 21 56 22 57 END { 23 58 manage("shutdown") if $i_am_parent; 24 59 } 60 61 =head1 I<dump_res($http_response)> 62 63 Return a readable string formatted from an HTTP::Response object. 64 Only the first 80 characters of returned content are returned. 65 66 =cut 25 67 26 68 sub dump_res { … … 42 84 } 43 85 86 =head1 I<tempdir()> 87 88 Return a newly created temporary directory. The directory will be 89 removed automatically upon program exit. 90 91 =cut 92 44 93 sub tempdir { 45 94 require File::Temp; … … 47 96 } 48 97 98 =head1 I<new_port()> 99 100 Return the next free port number in the series. Port numbers are assigned 101 starting at 60000. 102 103 =cut 104 49 105 sub 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 111 Return 1 if the port is free to use for listening on $free_port else return 0. 112 113 =cut 114 115 sub 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 123 Return a string containing the contents of the file $file. If $file 124 cannot be opened, then return undef. 125 126 =cut 52 127 53 128 sub filecontent { … … 60 135 } 61 136 137 =head1 I<foreach_aio($callback)> 138 139 Set the server into each AIO mode (none, ioaio) and call the specified 140 callback function with the mode name as argument. 141 142 =cut 143 62 144 sub foreach_aio (&) { 63 145 my $cb = shift; 64 146 65 foreach my $mode (qw(none linuxioaio)) {147 foreach my $mode (qw(none ioaio)) { 66 148 my $line = manage("SERVER aio_mode = $mode"); 67 149 next unless $line; … … 70 152 } 71 153 154 =head1 I<manage($cmd, %opts)> 155 156 Send a command $cmd to the server, and return the response line from 157 the server. 158 159 Optional arguments are: 160 161 quiet_failure => 1 162 163 Output a warning if the response indicated an error, 164 unless $opts{quiet_failure} is true, or the command 165 was 'shutdown' (which doesn't return a response). 166 167 =cut 168 72 169 sub manage { 73 170 my $cmd = shift; 171 my %opts = @_; 172 74 173 print $msock "$cmd\r\n"; 75 174 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 190 Send a command $cmd to the server, and return a multi-line 191 response. Return the number zero if there was an error or 192 no response. 193 194 =cut 195 196 sub 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 } 76 206 return 0 if !$res || $res =~ /^ERR/; 77 207 return $res; 78 208 } 209 210 =head1 I<start_server($conf)> 211 212 Optionally start a perlbal server and return a socket connected to its 213 management port. 214 215 The argument $conf is a string specifying initial configuration 216 commands. 217 218 If the environment variable TEST_PERLBAL_FOREGROUND is set to a true 219 value then a server will be started in the foreground, in which case 220 this function does not return. When the server function finishes, 221 exit() will be called to terminate the process. 222 223 If the environment variable TEST_PERLBAL_USE_EXISTING is set to a true 224 value then a socket will be returned which is connected to an existing 225 server's management port. 226 227 Otherwise, a child process is forked and a socket is returned which is 228 connected to the child's management port. 229 230 The management port is assigned automatically, a new port number each 231 time this function is called. The starting port number is 60000. 232 233 =cut 79 234 80 235 sub start_server { … … 117 272 } 118 273 274 # Start a perlbal server running and tell it to listen on the specified 275 # management port number. This function does not return. 276 119 277 sub _start_perbal_server { 120 278 my ($conf, $mgmt_port) = @_; … … 140 298 } 141 299 142 # get the manager socket 300 301 =head1 I<msock()> 302 303 Return a reference to the socket connected to the server's management 304 port. 305 306 =cut 307 143 308 sub msock { 144 309 return $msock; 145 310 } 311 312 313 =head1 I<ua()> 314 315 Return a new instance of LWP::UserAgent. 316 317 =cut 146 318 147 319 sub ua { … … 150 322 return LWP::UserAgent->new; 151 323 } 324 325 =head1 I<wait_on_child($pid, $port)> 326 327 Return a socket which is connected to a child process. 328 329 $pid specifies the child process id, and $port is the port number on 330 which the child is listening. 331 332 Several attempts are made; if the child dies or a connection cannot 333 be made within 5 seconds then this function dies with an error message. 334 335 =cut 152 336 153 337 sub wait_on_child { … … 167 351 } 168 352 353 =head1 I<resp_from_sock($sock)> 354 355 Read an HTTP response from a socket and return it 356 as an HTTP::Response object 357 358 In scalar mode, return only the $http_response object. 359 360 In 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 169 367 sub resp_from_sock { 170 368 my $sock = shift; -
trunk/server/lib/mogdeps/Perlbal/Test/WebClient.pm
r1087 r1308 29 29 my $self = shift; 30 30 if (@_) { 31 $self->{_sock} = undef; 31 32 return $self->{server} = shift; 32 33 } else { … … 39 40 my $self = shift; 40 41 if (@_) { 42 $self->{_sock} = undef; 41 43 return $self->{host} = shift; 42 44 } else { -
trunk/server/lib/mogdeps/Perlbal/UploadListener.pm
r1087 r1308 2 2 # Listen for UDP upload status packets 3 3 # 4 # Copyright 2005-200 6, Six Apart, Ltd.4 # Copyright 2005-2007, Six Apart, Ltd. 5 5 6 6 … … 27 27 return Perlbal::error("Error creating listening socket: " . ($@ || $!)) 28 28 unless $sock; 29 30 my $self = $class->SUPER::new($sock);29 my $self = fields::new($class); 30 $self->SUPER::new($sock); 31 31 $self->{service} = $service; 32 32 $self->{hostport} = $hostport; 33 bless $self, ref $class || $class;34 33 $self->watch_read(1); 35 34 return $self; -
trunk/server/lib/mogdeps/Perlbal/Util.pm
r1087 r1308 9 9 sub durl { 10 10 my ($txt) = @_; 11 $txt =~ tr/+/ /;12 11 $txt =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; 13 12 return $txt; 13 } 14 15 =head2 C< rebless > 16 17 Safely re-bless a locked (use fields) hash into another package. Note 18 that for our convenience elsewhere the set of allowable keys for the 19 re-blessed hash will be the union of the keys allowed by its old package 20 and those allowed for the package into which it is blessed. 21 22 =cut 23 24 BEGIN { 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 } 14 45 } 15 46
