package Plack::Handler::FCGI; use strict; use warnings; use constant RUNNING_IN_HELL => $^O eq 'MSWin32'; use Scalar::Util qw(blessed); use Plack::Util; use FCGI; use HTTP::Status qw(status_message); use URI; use URI::Escape; sub new { my $class = shift; my $self = bless {@_}, $class; $self->{leave_umask} ||= 0; $self->{keep_stderr} ||= 0; $self->{nointr} ||= 0; $self->{daemonize} ||= $self->{detach}; # compatibility $self->{nproc} ||= 1 unless blessed $self->{manager}; $self->{pid} ||= $self->{pidfile}; # compatibility $self->{listen} ||= [ ":$self->{port}" ] if $self->{port}; # compatibility $self->{backlog} ||= 100; $self->{manager} = 'FCGI::ProcManager' unless exists $self->{manager}; $self; } sub run { my ($self, $app) = @_; my $running_on_server_starter = exists $ENV{SERVER_STARTER_PORT}; my $sock = 0; if (-S STDIN) { # running from web server. Do nothing # Note it should come before listen check because of plackup's default } elsif ($running_on_server_starter) { # Runing under Server::Starter require Server::Starter; my %socks = %{Server::Starter::server_ports()}; if (scalar(keys(%socks)) > 1) { die "More than one socket are specified by Server::Starter"; } $sock = (values %socks)[0]; } elsif ($self->{listen}) { my $old_umask = umask; unless ($self->{leave_umask}) { umask(0); } $sock = FCGI::OpenSocket( $self->{listen}->[0], $self->{backlog} ) or die "failed to open FastCGI socket: $!"; unless ($self->{leave_umask}) { umask($old_umask); } } elsif (!RUNNING_IN_HELL) { die "STDIN is not a socket: specify a listen location"; } @{$self}{qw(stdin stdout stderr)} = (IO::Handle->new, IO::Handle->new, IO::Handle->new); my %env; my $request = FCGI::Request( $self->{stdin}, $self->{stdout}, $self->{stderr}, \%env, $sock, ($self->{nointr} ? 0 : &FCGI::FAIL_ACCEPT_ON_INTR), ); my $proc_manager; if ($self->{listen} or $running_on_server_starter) { $self->daemon_fork if $self->{daemonize}; if ($self->{manager}) { if (blessed $self->{manager}) { for (qw(nproc pid proc_title)) { die "Don't use '$_' when passing in a 'manager' object" if $self->{$_}; } $proc_manager = $self->{manager}; } else { Plack::Util::load_class($self->{manager}); $proc_manager = $self->{manager}->new({ n_processes => $self->{nproc}, pid_fname => $self->{pid}, (exists $self->{proc_title} ? (pm_title => $self->{proc_title}) : ()), }); } # detach *before* the ProcManager inits $self->daemon_detach if $self->{daemonize}; } elsif ($self->{daemonize}) { $self->daemon_detach; } } elsif (blessed $self->{manager}) { $proc_manager = $self->{manager}; } $proc_manager && $proc_manager->pm_manage; while ($request->Accept >= 0) { $proc_manager && $proc_manager->pm_pre_dispatch; my $env = { %env, 'psgi.version' => [1,1], 'psgi.url_scheme' => ($env{HTTPS}||'off') =~ /^(?:on|1)$/i ? 'https' : 'http', 'psgi.input' => $self->{stdin}, 'psgi.errors' => ($self->{keep_stderr} ? \*STDERR : $self->{stderr}), 'psgi.multithread' => Plack::Util::FALSE, 'psgi.multiprocess' => defined $proc_manager, 'psgi.run_once' => Plack::Util::FALSE, 'psgi.streaming' => Plack::Util::TRUE, 'psgi.nonblocking' => Plack::Util::FALSE, 'psgix.harakiri' => defined $proc_manager, 'psgix.cleanup' => 1, 'psgix.cleanup.handlers' => [], }; delete $env->{HTTP_CONTENT_TYPE}; delete $env->{HTTP_CONTENT_LENGTH}; # lighttpd munges multiple slashes in PATH_INFO into one. Try recovering it my $uri = URI->new("http://localhost" . $env->{REQUEST_URI}); $env->{PATH_INFO} = uri_unescape($uri->path); $env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E//; # root access for mod_fastcgi if (!exists $env->{PATH_INFO}) { $env->{PATH_INFO} = ''; } # typical fastcgi_param from nginx might get empty values for my $key (qw(CONTENT_TYPE CONTENT_LENGTH)) { no warnings; delete $env->{$key} if exists $env->{$key} && $env->{$key} eq ''; } if (defined(my $HTTP_AUTHORIZATION = $env->{Authorization})) { $env->{HTTP_AUTHORIZATION} = $HTTP_AUTHORIZATION; } my $res = Plack::Util::run_app $app, $env; if (ref $res eq 'ARRAY') { $self->_handle_response($res); } elsif (ref $res eq 'CODE') { $res->(sub { $self->_handle_response($_[0]); }); } else { die "Bad response $res"; } # give pm_post_dispatch the chance to do things after the client thinks # the request is done $request->Finish; $proc_manager && $proc_manager->pm_post_dispatch(); # When the fcgi-manager exits it sends a TERM signal to the workers. # However, if we're busy processing the cleanup handlers, testing # shows that the worker doesn't actually exit in that case. # Trapping the TERM signal and finshing up fixes that. my $exit_due_to_signal = 0; if ( @{ $env->{'psgix.cleanup.handlers'} || [] } ) { local $SIG{TERM} = sub { $exit_due_to_signal = 1 }; for my $handler ( @{ $env->{'psgix.cleanup.handlers'} } ) { $handler->($env); } } if ($proc_manager && $env->{'psgix.harakiri.commit'}) { $proc_manager->pm_exit("safe exit with harakiri"); } elsif ($exit_due_to_signal) { $proc_manager && $proc_manager->pm_exit("safe exit due to signal"); exit; # want to exit, even without a $proc_manager } } } sub _handle_response { my ($self, $res) = @_; $self->{stdout}->autoflush(1); binmode $self->{stdout}; my $hdrs; my $message = status_message($res->[0]); $hdrs = "Status: $res->[0] $message\015\012"; my $headers = $res->[1]; while (my ($k, $v) = splice @$headers, 0, 2) { $hdrs .= "$k: $v\015\012"; } $hdrs .= "\015\012"; print { $self->{stdout} } $hdrs; my $cb = sub { print { $self->{stdout} } $_[0] }; my $body = $res->[2]; if (defined $body) { Plack::Util::foreach($body, $cb); } else { return Plack::Util::inline_object write => $cb, close => sub { }; } } sub daemon_fork { require POSIX; fork && exit; } sub daemon_detach { my $self = shift; print "FastCGI daemon started (pid $$)\n"; open STDIN, "+&STDIN" or die $!; open STDERR, ">&STDIN" or die $!; POSIX::setsid(); } 1; __END__ =head1 NAME Plack::Handler::FCGI - FastCGI handler for Plack =head1 SYNOPSIS # Run as a standalone daemon plackup -s FCGI --listen /tmp/fcgi.sock --daemonize --nproc 10 # Run from your web server like mod_fastcgi #!/usr/bin/env plackup -s FCGI my $app = sub { ... }; # Roll your own my $server = Plack::Handler::FCGI->new( nproc => $num_proc, listen => [ $port_or_socket ], detach => 1, ); $server->run($app); =head1 DESCRIPTION This is a handler module to run any PSGI application as a standalone FastCGI daemon or a .fcgi script. =head2 OPTIONS =over 4 =item listen listen => [ '/path/to/socket' ] listen => [ ':8080' ] Listen on a socket path, hostname:port, or :port. =item port listen via TCP on port on all interfaces (Same as C<< listen => ":$port" >>) =item leave-umask Set to 1 to disable setting umask to 0 for socket open =item nointr Do not allow the listener to be interrupted by Ctrl+C =item nproc Specify a number of processes for FCGI::ProcManager =item pid Specify a filename for the pid file =item manager Specify either a FCGI::ProcManager subclass, or an actual FCGI::ProcManager-compatible object. If you do not want a FCGI::ProcManager but instead run in a single process, set this to undef. use FCGI::ProcManager::Dynamic; Plack::Handler::FCGI->new( manager => FCGI::ProcManager::Dynamic->new(...), ); =item daemonize Daemonize the process. =item proc-title Specify process title =item keep-stderr Send psgi.errors to STDERR instead of to the FCGI error stream. =item backlog Maximum length of the queue of pending connections, defaults to 100. =back =head2 EXTENSIONS Supported L. =over 4 =item psgix.cleanup push @{ $env->{'psgix.cleanup.handlers'} }, sub { warn "Did this later" } if $env->{'psgix.cleanup'}; Supports the C extension, in order to use it, just push a callback onto C<< $env->{'psgix.cleanup.handlers' >>. These callbacks are run after the C hook. =item psgix.harakiri $env->{'psgix.harakiri.commit'} = 1 if $env->{'psgix.harakiri'}; If there is a L, then C will be enabled and setting C<< $env->{'psgix.harakiri.commit'} >> to a true value will cause C<< $manager->pm_exit >> to be called after the request is finished. =back =head2 WEB SERVER CONFIGURATIONS In all cases, you will want to install L and L. You may find it most convenient to simply install L which includes both of these. =head3 nginx This is an example nginx configuration to run your FCGI daemon on a Unix domain socket and run it at the server's root URL (/). http { server { listen 3001; location / { set $script ""; set $path_info $uri; fastcgi_pass unix:/tmp/fastcgi.sock; fastcgi_param SCRIPT_NAME $script; fastcgi_param PATH_INFO $path_info; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param REQUEST_URI $request_uri; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; } } } If you want to host your application in a non-root path, then you should mangle this configuration to set the path to C and the rest of the path in C. See L for more details. =head3 Apache mod_fastcgi After installing C, you should add the C directive to your Apache config: FastCgiExternalServer /tmp/myapp.fcgi -socket /tmp/fcgi.sock ## Then set up the location that you want to be handled by fastcgi: # EITHER from a given path Alias /myapp/ /tmp/myapp.fcgi/ # OR at the root Alias / /tmp/myapp.fcgi/ Now you can use plackup to listen to the socket that you've just configured in Apache. $ plackup -s FCGI --listen /tmp/myapp.sock psgi/myapp.psgi The above describes the "standalone" method, which is usually appropriate. There are other methods, described in more detail at L (with regards to Catalyst, but which may be set up similarly for Plack). See also L for more details. =head3 lighttpd To host the app in the root path, you're recommended to use lighttpd 1.4.23 or newer with C flag like below. fastcgi.server = ( "/" => (( "socket" => "/tmp/fcgi.sock", "check-local" => "disable", "fix-root-scriptname" => "enable", )) If you use lighttpd older than 1.4.22 where you don't have C, mounting apps under the root causes wrong C and C set. Also, mounting under the empty root (C<"">) or a path that has a trailing slash would still cause weird values set even with C. In such cases you can use L to fix it. To mount in the non-root path over TCP: fastcgi.server = ( "/foo" => (( "host" = "127.0.0.1", "port" = "5000", "check-local" => "disable", )) It's recommended that your mount path does B have the trailing slash. If you I need to have one, you should consider using L to fix the wrong B values set by lighttpd. =cut =head2 Authorization Most fastcgi configuration does not pass C headers to C environment variable by default for security reasons. Authentication middleware such as L or L requires the variable to be set up. Plack::Handler::FCGI supports extracting the C environment variable when it is configured that way. Apache2 with mod_fastcgi: --pass-header Authorization mod_fcgid: FcgidPassHeader Authorization =head2 Server::Starter This plack handler supports L as a superdaemon. Simply launch plackup from start_server with a path option. The listen option is ignored when launched from Server::Starter. start_server --path=/tmp/socket -- plackup -s FCGI app.psgi =head1 SEE ALSO L =cut