FU

 view release on metacpan or  search on metacpan

FU.pm  view on Meta::CPAN

            _err_500();
        }
    }

    $REQ->{trace_end} = clock_gettime(CLOCK_MONOTONIC);
    eval {
        fu->_flush($c->{fcgi_obj} || $c->{client_sock});
        1;
    } || do {
        log_write "Error writing response: $@\n";
        $c->{client_sock} = $c->{fcgi_obj} = undef;
    };

    if (debug && $REQ->{trace_id} && $debug_info->{history} && $debug_info->{storage}) {
        require FU::DebugImpl;
        FU::DebugImpl::save();
    }

    my $proc_ms = ($REQ->{trace_end} - $REQ->{trace_start}) * 1000;
    log_write(sprintf "%.0fms%s %s-%s %d-%s\n", $proc_ms,
        $REQ->{trace_nsql} ?
            sprintf ' (sql %.0f+%.0fms, %d/%d/%d)',
            ($REQ->{trace_sqlexec}||0)*1000, ($REQ->{trace_sqlprep}||0)*1000,
            $REQ->{trace_nsqldirect}||0, $REQ->{trace_nsqlprep}||0, $REQ->{trace_nsql} : '',
        $REQ->{status}, ($REQ->{reshdr}{'content-type'}//'-') =~ s/;.+$//r,
        length($REQ->{resbody}), substr($REQ->{reshdr}{'content-encoding'}//'r', 0, 1)
    ) if FU::debug || $proc_ms > (FU::log_slow_reqs||1e10);
}


sub _run_loop($c) {
    my $stop = 0;
    my $count = 0;
    local $SIG{HUP} = 'IGNORE';
    local $SIG{TERM} = $SIG{INT} = sub { $stop = 1 };

    my sub passclient {
        FU::Util::fdpass_send(fileno($c->{supervisor_sock}), fileno($c->{client_sock}), 'f0000')
            if $c->{supervisor_sock} && $c->{client_sock};
        exit;
    }

    my sub setstate($state) {
        $0 = sprintf "%s: %s [#%d%s]", $procname, $state, $count, $c->{max_reqs} ? "/$c->{max_reqs}" : '' if $procname;
    }

    while (!$stop) {
        setstate 'idle';

        $c->{client_sock} ||= $c->{listen_sock}->accept || next;
        $c->{fcgi_obj} ||= $c->{listen_proto} eq 'fcgi' && FU::fcgi::new(fileno $c->{client_sock}, $c->{proc});

        if ($c->{monitor} && _monitor) {
            log_write "File change detected, restarting process.\n" if debug;
            passclient;
        }

        setstate 'working';
        _do_req $c;

        $c->{client_sock} = $c->{fcgi_obj} = undef if !($c->{fcgi_obj} && $c->{fcgi_obj}->keepalive);

        $count++;
        passclient if $c->{max_reqs} && $count >= $c->{max_reqs};
    }
}


sub _supervisor($c) {
    my ($rsock, $wsock) = IO::Socket->socketpair(IO::Socket::AF_UNIX(), IO::Socket::SOCK_STREAM(), IO::Socket::PF_UNSPEC());

    my %childs; # pid => 1: spawned, 2: signalled ready
    $SIG{CHLD} = sub { $wsock->syswrite('c0000',5) };
    $SIG{HUP} = $SIG{TERM} = $SIG{INT} = sub($sig,@) {
        kill 'TERM', keys %childs;
        return if $sig eq 'HUP';
        $SIG{$sig} = undef;
        kill $sig, $$;
        exit 1;
    };

    require Fcntl;
    fcntl $c->{listen_sock}, Fcntl::F_SETFD(), 0;
    fcntl $wsock, Fcntl::F_SETFD(), 0;

    $ENV{FU_MONITOR} = $c->{monitor};
    $ENV{FU_PROC} = $c->{proc};
    $ENV{FU_MAX_REQS} = $c->{max_reqs};
    $ENV{FU_DEBUG} = debug;
    $ENV{FU_SUPERVISOR_FD} = fileno $wsock;
    $ENV{FU_LISTEN_FD} = fileno $c->{listen_sock};
    $ENV{FU_LISTEN_PROTO} = $c->{listen_proto};

    my $err = 0;
    my @client_fd;
    my $msg = '';
    while (1) {
        while ((my $pid = waitpid(-1, POSIX::WNOHANG())) > 0) {
            $err = 1 if POSIX::WIFEXITED($?) && POSIX::WEXITSTATUS($?) != 0;
            if (!$err && (!$childs{$pid} || $childs{$pid} != 2)) {
                $err = 1;
                log_write "Script exited before calling FU::run()\n";
            } elsif ($?) {
                log_write "Unclean shutdown of worker PID $pid status $?\n";
            }
            delete $childs{$pid};
        }

        # Don't bother spawning more than 1 at a time while in error state
        my $spawn = !$err ? $c->{proc} - keys %childs : !@client_fd && (grep $_ == 1, values %childs) ? 0 : 1;
        for (1..$spawn) {
            my $client = @client_fd ? IO::Socket->new_from_fd(shift(@client_fd), 'r') : undef;
            my $pid = fork;
            die $! if !defined $pid;
            if (!$pid) { # child
                $SIG{CHLD} = $SIG{HUP} = $SIG{INT} = $SIG{TERM} = undef;
                $0 = sprintf '%s: starting', $procname if $procname;
                # In error state, wait with loading the script until we've received a request.
                # Otherwise we'll end up in an infinite spawning loop if the script doesn't start properly.
                $client = $c->{listen_sock}->accept() or die $! if !$client && $err;
                if ($client) {

FU.pm  view on Meta::CPAN

C<application/octet-stream>.

This method sets an appropriate C<last-modified> header and supports
conditional requests with C<if-modified-since>.

=item fu->redirect($code, $location)

Generates a HTTP redirect response and calls C<< fu->done >>. C<$code> can be
one of the following status codes or an alias:

  Status  Alias      Semantics
  ----------------------------------------
  301     perm       Permanent, method may or may not change to GET
  302     temp       Temporary, method may or may not change to GET
  303     tempget    Temporary to GET
  307     tempsame   Temporary without changing method
  308     permsame   Permanent without changing method

=back



=head1 Running the Site

When your script is done setting L</"Framework Configuration"> and registering
L</"Handlers & Routing">, it should call C<FU::run> to actually start serving
the website:

=over

=item FU::run(%options)

In normal circumstances, this function does not return.

When FU has been loaded with the C<-spawn> flag, C<%options> are read from the
environment variables or command line arguments documented below. Otherwise,
the following corresponding options can be passed instead: I<http>, I<fcgi>,
I<proc>, I<monitor>, I<max_reqs>, I<listen_sock>.

=back

Command-line options are read only when FU has been loaded with C<-spawn>, the
environment variables are always read.

=over

=item FU_HTTP=addr

=item --http=addr

Start a local web server on the given address. I<addr> can be an C<ip:port>
combination to listen on TCP, or a path (optionally prefixed with C<unix:>) to
listen on a UNIX socket. E.g.

  ./your-script.pl --http=127.0.0.1:8000
  ./your-script.pl --http=unix:/path/to/socket

B<WARNING:> The built-in HTTP server is only intended for local development
setups, it is NOT suitable for production deployments. It has no timeouts, does
not enforce limits on request size, does not support HTTPS and will never
adequately support keep-alive. You could put it behind a reverse proxy, but it
currently also lacks provisions for extracting the client IP address from the
request headers, so that's not ideal either. Much better to use FastCGI in
combination with a proper web server for internet-facing deployments.

=item FU_FCGI=addr

=item --fcgi=addr

Like the HTTP counterpart above, but listen on a FastCGI socket instead. If
this option is set, it takes precedence over the HTTP option.

Nginx and Apache will, in their default configuration, use a separate
connection per request. If you have a more esoteric setup, you should probably
be aware of the following: this implementation does not support multiplexing or
pipelining.  It does support keepalive, but this comes with a few caveats:

=over

=item * You should not attempt to keep more connections alive than the
configured number of worker processes, otherwise new connection attempts will
stall indefinitely.

=item * When using C<--monitor> mode, the file modification check is performed
I<after> each request rather than before, so clients may get a response from
stale code.

=item * When worker processes shut down, either through C<--max-reqs> or in
response to a signal, there is a possibility that an incoming request on an
existing connection gets interrupted.

=back

=item FU_PROC=n

=item --proc=n

How many worker processes to spawn, defaults to 1.

=item FU_MONITOR=0/1

=item --monitor or --no-monitor

When enabled, worker processes will monitor for file changes and automatically
restart on changes. This is immensely useful during development, but comes at a
significant cost in performance - better not enable this in production.

=item FU_MAX_REQS=n

=item FU_MAX_REQS=min:max

=item --max-reqs=n

=item --max-reqs=min:max

Worker processes can automatically restart after handling a number of requests.
Set to 0 (the default) to disable this feature. When set as C<min:max>, the
number of requests is randomized in the given range, which is useful to avoid
restarting all worker processes around the same time.

This option can be useful when your worker processes keep accumulating memory
over time. A little pruning now and then can never hurt.

=item FU_DEBUG=0/1

=item --debug or --no-debug

Set the initial value for C<FU::debug>.

=item FU_LOG_FILE=path

=item --log-file=path

Set the initial value for C<FU::Log::set_file()>.

=item LISTEN_FD=num

=item LISTEN_PROTO=http/fcgi

Listen for incoming connections on the given file descriptor instead of



( run in 0.365 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )