Feersum

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


1.503 Tue Sep 17 12:14:15 2024 -0200
        - start listening on re-forked child
        - try fix test for some older perls on freebsd

1.502 Wed Sep 03 19:10:55 2024 -0200
        - don't generate Content-Length header for 204 responses
        - acknowledge PERL_USE_UNSAFE_INC=0

1.501 Sat Aug 31 09:10:55 2024 -0200
        - http/1.1 keepalive support for chunked(streaming) responses
        - max_connection_reqs to control requests per keepalive connection
        - tweak some tests for better cpantesters matrix

1.500 Tue Aug 20 18:10:55 2024 -0200
        Features:
        - native interface: access specific parts of request
        - http/1.1 keepalive support
        - http/1.1 date header
        - defer accept, accept4

        Backward incompatibly:
        - remove adobe flash policy support

1.410 Sat Dec  5 14:32:22 2020 +0800
        Features
        - Add unix domain socket support (vividsnow++)
         # for example:

Feersum.xs  view on Meta::CPAN


    SV *poll_write_cb;
    SV *ext_guard;

    struct feer_req *req;
    ssize_t expected_cl;
    ssize_t received_cl;

    enum feer_respond_state responding;
    enum feer_receive_state receiving;
    bool is_keepalive;
    int reqs;

    unsigned int in_callback;
    unsigned int is_http11:1;
    unsigned int poll_write_cb_is_io_handle:1;
    unsigned int auto_cl:1;

    ssize_t pipelined;
};

Feersum.xs  view on Meta::CPAN

static SV* feersum_env_header(pTHX_ struct feer_req *r, SV* name);
static SV* feersum_env_addr(pTHX_ struct feer_conn *c);
static SV* feersum_env_port(pTHX_ struct feer_conn *c);
static ssize_t feersum_env_content_length(pTHX_ struct feer_conn *c);
static SV* feersum_env_io(pTHX_ struct feer_conn *c);
static void feersum_start_response
    (pTHX_ struct feer_conn *c, SV *message, AV *headers, int streaming);
static size_t feersum_write_whole_body (pTHX_ struct feer_conn *c, SV *body);
static void feersum_handle_psgi_response(
    pTHX_ struct feer_conn *c, SV *ret, bool can_recurse);
static bool feersum_set_keepalive (pTHX_ struct feer_conn *c, bool is_keepalive);
static int feersum_close_handle(pTHX_ struct feer_conn *c, bool is_writer);
static SV* feersum_conn_guard(pTHX_ struct feer_conn *c, SV *guard);

static void start_read_watcher(struct feer_conn *c);
static void stop_read_watcher(struct feer_conn *c);
static void restart_read_timer(struct feer_conn *c);
static void stop_read_timer(struct feer_conn *c);
static void start_write_watcher(struct feer_conn *c);
static void stop_write_watcher(struct feer_conn *c);

Feersum.xs  view on Meta::CPAN

static bool request_cb_is_psgi = 0;
static SV *shutdown_cb_cv = NULL;
static bool shutting_down = 0;
static int active_conns = 0;
static double read_timeout = READ_TIMEOUT;
static unsigned int max_connection_reqs = 0;

static SV *feer_server_name = NULL;
static SV *feer_server_port = NULL;
static bool is_tcp = 1;
static bool is_keepalive = KEEPALIVE_CONNECTION;

static ev_io accept_w;
static ev_prepare ep;
static ev_check   ec;
struct ev_idle    ei;

static struct rinq *request_ready_rinq = NULL;

static AV *psgi_ver;
static SV *psgi_serv10, *psgi_serv11, *crlf_sv;

Feersum.xs  view on Meta::CPAN

    SvIV_set(self,conn_fd);

    struct feer_conn *c = (struct feer_conn *)SvPVX(self);
    Zero(c, 1, struct feer_conn);

    c->self = self;
    c->fd = conn_fd;
    c->sa = sa;
    c->responding = RESPOND_NOT_STARTED;
    c->receiving = RECEIVE_HEADERS;
    c->is_keepalive = 0;
    c->reqs = 0;
    c->pipelined = 0;

    ev_io_init(&c->read_ev_io, try_conn_read, conn_fd, EV_READ);
    c->read_ev_io.data = (void *)c;

    ev_init(&c->read_ev_timer, conn_read_timeout);
    c->read_ev_timer.data = (void *)c;

    trace3("made conn fd=%d self=%p, c=%p, cur=%"Sz_uf", len=%"Sz_uf"\n",

Feersum.xs  view on Meta::CPAN

    default:
        goto try_write_cleanup;
    }

try_write_paused:
    trace3("write PAUSED %d, refcnt=%d, state=%d\n", c->fd, SvREFCNT(c->self), c->responding);
    stop_write_watcher(c);
    goto try_write_cleanup;

try_write_shutdown:
    if (likely(c->is_keepalive)) {
        trace3("write SHUTDOWN, but KEEP %d, refcnt=%d, state=%d\n", c->fd, SvREFCNT(c->self), c->responding);
        stop_write_watcher(c);
        change_responding_state(c, RESPOND_NOT_STARTED);
        change_receiving_state(c, RECEIVE_WAIT);
        if (likely(c->req)) {
            if (c->req->buf) SvREFCNT_dec(c->req->buf);
            if (likely(c->req->path)) SvREFCNT_dec(c->req->path);
            if (likely(c->req->query)) SvREFCNT_dec(c->req->query);
            if (likely(c->req->addr)) SvREFCNT_dec(c->req->addr);
            if (likely(c->req->port)) SvREFCNT_dec(c->req->port);

Feersum.xs  view on Meta::CPAN

    if (unlikely(revents & EV_ERROR && !(revents & EV_READ))) {
        trace("EV error on read, fd=%d revents=0x%08x\n", w->fd, revents);
        goto try_read_error;
    }

    if (unlikely(c->receiving == RECEIVE_SHUTDOWN))
        goto dont_read_again;

    trace("try read %d\n",w->fd);

    if (unlikely(!c->rbuf)) { // likely = optimize for keepalive requests
        trace("init rbuf for %d\n",w->fd);
        c->rbuf = newSV(READ_INIT_FACTOR*READ_BUFSZ + 1);
        SvPOK_on(c->rbuf);
    }

    ssize_t space_free = SvLEN(c->rbuf) - SvCUR(c->rbuf);
    if (unlikely(space_free < READ_BUFSZ)) { // unlikely = optimize for small
        size_t new_len = SvLEN(c->rbuf) + READ_GROW_FACTOR*READ_BUFSZ;
        trace("moar memory %d: %"Sz_uf" to %"Sz_uf"\n",
            w->fd, (Sz)SvLEN(c->rbuf), (Sz)new_len);

Feersum.xs  view on Meta::CPAN

    change_receiving_state(c, RECEIVE_SHUTDOWN);
    change_responding_state(c, RESPOND_SHUTDOWN);
    stop_read_watcher(c);
    stop_read_timer(c);
    stop_write_watcher(c);
    goto try_read_cleanup;

try_read_bad:
    trace("bad request %d\n", w->fd);
    respond_with_server_error(c, "Malformed request.\n", 0, 400);
    // TODO: when keep-alive, close conn instead of fallthrough here.
    // fallthrough:
dont_read_again:
    trace("done reading %d\n", w->fd);
    change_receiving_state(c, RECEIVE_SHUTDOWN);
    stop_read_watcher(c);
    stop_read_timer(c);
    goto try_read_cleanup;

try_read_again_reset_timer:
    trace("(reset read timer) %d\n", w->fd);

Feersum.xs  view on Meta::CPAN

    if (likely(c->responding == RESPOND_NOT_STARTED) && c->receiving >= RECEIVE_HEADERS) {
        const char *msg;
        if (c->receiving == RECEIVE_HEADERS) {
            msg = "Headers took too long.";
        }
        else {
            msg = "Timeout reading body.";
        }
        respond_with_server_error(c, msg, 0, 408);
    } else {
        trace("read timeout in keepalive conn: %d\n", c->fd);
        stop_write_watcher(c);
        stop_read_watcher(c);
        stop_read_timer(c);
        safe_close_conn(c, "close at read timeout");
        change_responding_state(c, RESPOND_SHUTDOWN);
    }

read_timeout_cleanup:
    stop_read_watcher(c);
    stop_read_timer(c);

Feersum.xs  view on Meta::CPAN

    int err_code;
    const char *err;
    struct feer_req *req = c->req;

    trace("processing headers %d minor_version=%d\n",c->fd,req->minor_version);
    bool body_is_required;
    bool next_req_follows = 0;
    bool got_content_length = 0;

    c->is_http11 = (req->minor_version == 1);
    c->is_keepalive = is_keepalive && c->is_http11;
    c->reqs++;

    change_receiving_state(c, RECEIVE_BODY);

    if (likely(str_eq("GET", 3, req->method, req->method_len))) {
        // Not supposed to have a body.  Additional bytes are either a
        // mistake, a websocket negotiation or pipelined requests under
        // HTTP/1.1
        next_req_follows = 1;
    }

Feersum.xs  view on Meta::CPAN

            else {
                err_code = 400;
                err = "invalid content-length\n";
                goto got_bad_request;
            }
        }
        else if (
            unlikely(str_case_eq("connection", 10, hdr->name, hdr->name_len)))
        {
            if (likely(c->is_http11)
                && likely(c->is_keepalive)
                && likely(str_case_eq("close", 5, hdr->value, hdr->value_len)))
            {
                c->is_keepalive = 0;
                trace("setting conn %d to close after response\n", c->fd);
            }
            else if (
                likely(!c->is_http11)
                && likely(is_keepalive)
                && str_case_eq("keep-alive", 10, hdr->value, hdr->value_len))
            {
                c->is_keepalive = 1;
                trace("setting conn %d to keep after response\n", c->fd);
            }
        }
        // TODO: support "Transfer-Encoding: chunked" bodies
    }

    if (max_connection_reqs > 0 && c->reqs >= max_connection_reqs) {
        c->is_keepalive = 0;
        trace("reached max requests per connection (%d), will close after response\n", max_connection_reqs);
    }

    if (likely(next_req_follows)) goto got_it_all; // optimize for GET
    else if (likely(got_content_length)) goto got_cl;

    if (body_is_required) {
        // Go the nginx route...
        err_code = 411;
        err = "Content-Length required\n";

Feersum.xs  view on Meta::CPAN

              c->is_http11 ? 1 : 0,
              err_code, http_code_to_msg(err_code),
              (Ssz)msg_len,
              (int)msg_len, msg);
    add_sv_to_wbuf(c, sv_2mortal(tmp));

    stop_read_watcher(c);
    stop_read_timer(c);
    change_responding_state(c, RESPOND_SHUTDOWN);
    change_receiving_state(c, RECEIVE_SHUTDOWN);
    if (c->is_keepalive) c->is_keepalive = 0;
    conn_write_ready(c);
}

INLINE_UNLESS_DEBUG bool
str_eq(const char *a, int a_len, const char *b, int b_len)
{
    if (a_len != b_len) return 0;
    if (a == b) return 1;
    int i;
    for (i=0; i<a_len && i<b_len; i++) {

Feersum.xs  view on Meta::CPAN

        add_const_to_wbuf(c, ": ", 2);
        add_sv_to_wbuf(c, *val);
        add_crlf_to_wbuf(c);
    }

    if (likely(c->is_http11)) {
        #ifdef DATE_HEADER
        generate_date_header();
        add_const_to_wbuf(c, DATE_BUF, DATE_HEADER_LENGTH);
        #endif
        if (unlikely(!c->is_keepalive))
            add_const_to_wbuf(c, "Connection: close" CRLF, 19);
    } else if (unlikely(c->is_keepalive) && !streaming)
        add_const_to_wbuf(c, "Connection: keep-alive" CRLF, 24);

    if (streaming) {
        if (c->is_http11)
            add_const_to_wbuf(c, "Transfer-Encoding: chunked" CRLFx2, 30);
        else {
            add_crlf_to_wbuf(c);
            // cant do keep-alive for streaming http/1.0 since client completes read on close
            if (unlikely(c->is_keepalive)) c->is_keepalive = 0;
        }
    }

    conn_write_ready(c);
}

static size_t
feersum_write_whole_body (pTHX_ struct feer_conn *c, SV *body)
{
    size_t RETVAL;

Feersum.xs  view on Meta::CPAN

    if (unlikely(SvTRUE(ERRSV))) {
        call_died(aTHX_ c, "psgix.io magic");
    }
    else {
        SV *io_glob   = SvRV(sv);
        GvSV(io_glob) = newRV_inc(c->self);

        // Put whatever remainder data into the socket buffer.
        // Optimizes for the websocket case.
        //
        // TODO: For keepalive support the opposite operation is required;
        // pull the data out of the socket buffer and back into feersum.
        if (likely(c->rbuf && SvOK(c->rbuf) && SvCUR(c->rbuf))) {
            STRLEN rbuf_len;
            const char *rbuf_ptr = SvPV(c->rbuf, rbuf_len);
            IO *io = GvIOp(io_glob);
            assert(io != NULL);
            PerlIO_unread(IoIFP(io), (const void *)rbuf_ptr, rbuf_len);
            sv_setpvs(c->rbuf, "");
        }

Feersum.xs  view on Meta::CPAN

        }
        trace("set timeout %f\n", new_read_timeout);
        read_timeout = new_read_timeout;
    }
    RETVAL = read_timeout;
}
    OUTPUT:
        RETVAL

void
set_keepalive (SV *self, SV *set)
    PPCODE:
{
    trace("set keepalive %d\n", SvTRUE(set));
    is_keepalive = SvTRUE(set);
}

unsigned int
max_connection_reqs (SV *self, ...)
    PROTOTYPE: $;$
    PREINIT:
        unsigned int new_max_connection_reqs = 0;
    CODE:
{
    if (items > 1) {

Feersum.xs  view on Meta::CPAN

        RETVAL

int
fileno (struct feer_conn *c)
    CODE:
        RETVAL = c->fd;
    OUTPUT:
        RETVAL

bool
is_keepalive (struct feer_conn *c)
    CODE:
        RETVAL = c->is_keepalive;
    OUTPUT:
        RETVAL

SV*
response_guard (struct feer_conn *c, ...)
    PROTOTYPE: $;$
    CODE:
        RETVAL = feersum_conn_guard(aTHX_ c, (items == 2) ? ST(1) : NULL);
    OUTPUT:
        RETVAL

MANIFEST  view on Meta::CPAN

t/51-psgi-streaming.t
t/52-psgi-iohandle.t
t/53-psgi-overloaded.t
t/54-psgix-io.t
t/55-psgi-leak.t
t/60-plack.t
t/61-plack-suite.t
t/62-plack-runner.t
t/63-plack-apps.t
t/64-unixsock.t
t/65-keepalive.t
t/66-pipelining.t
t/99-critic.t
t/99-fixme.t
t/99-manifest.t
t/99-pod-coverage.t
t/99-pod.t
t/Utils.pm
typemap
xt/50-psgi-simple-stress.t
t/perlcriticrc

eg/chat.feersum  view on Meta::CPAN

</html>
EOHTML
}

sub start_stream {
    my ($r, $client) = @_;
    my $w = $r->start_streaming(200, \@html_hdrs);
    $handles[$client] = $w;
    weaken $w;
    $timers[$client] = EV::timer 1,1,sub {
        $w->write('<!-- keep-alive -->') if $w;
    };
    $w->write(<<EOH);
<html>
<head></head>
<body>
<p>Hello! (connection $client)</p>
EOH
}

sub broadcast {

lib/Feersum.pm  view on Meta::CPAN

complete header (and also, until Feersum supports otherwise, time-outs while
waiting for a request entity body).

Any exceptions thrown in the handler will generate a warning and not
propagated.

=item C<< set_server_name_and_port($host,$port) >>

Override Feersum's notion of what SERVER_HOST and SERVER_PORT should be.

=item C<< set_keepalive($bool) >>

Override Feersum's default keepalive behavior.

=back

=cut

=head1 GRITTY DETAILS

=head2 Compile Time Options

There are a number of constants at the top of Feersum.xs.  If you change any

lib/Feersum.pm  view on Meta::CPAN

=item AUTOCORK_WRITES

Controls how response data is written to sockets.  If enabled (the default)
the event loop is used to wait until the socket is writable, otherwise a write
is performed immediately.  In either case, non-blocking writes are used.
Using the event loop is "nicer" but perhaps introduces latency, hence this
option.

=item KEEPALIVE_CONNECTION

Controls support of keepalive connections. Default is false.
If enabled or set via Feersum->set_keepalive(1), then
"Connection: keep-alive" for HTTP/1.0 and "Connection: close" for HTTP/1.1
are acknowledged.

=item READ_TIMEOUT

Controls read timeout. Default is 5.0 sec. It is also an keepalive timeout.

=item FEERSUM_IOMATRIX_SIZE

Controls the size of the main write-buffer structure in Feersum.  Making this
value lower will use slightly less memory per connection at the cost of speed
(and vice-versa for raising the value).  The effect is most noticeable when
you're app is making a lot of sparce writes.  The default of 64 generally
keeps usage under 4k per connection on full 64-bit platforms when you take
into account the other connection and request structures.

lib/Feersum/Runner.pm  view on Meta::CPAN

            Blocking => 0,
        );
        croak "couldn't bind to socket: $!" unless $sock;
    }
    $self->{sock} = $sock;
    my $f = Feersum->endjinn;
    $f->use_socket($sock);

    if (my $opts = $self->{options}) {
        $self->{$_} = delete $opts->{$_} for grep defined($opts->{$_}),
            qw/pre_fork keepalive read_timeout max_connection_reqs/;
    }
    $f->set_keepalive($_) for grep defined, delete $self->{keepalive};
    $f->read_timeout($_) for grep $_, delete $self->{read_timeout};
    $f->max_connection_reqs($_) for grep $_, delete $self->{max_connection_reqs};

    $self->{endjinn} = $f;
    return;
}

# for overriding:
sub assign_request_handler { ## no critic (RequireArgUnpacking)
    return $_[0]->{endjinn}->request_handler($_[1]);

lib/Feersum/Runner.pm  view on Meta::CPAN


Listen on this TCP socket (C<host:port> format).

=item pre_fork

Fork this many worker processes.

The fork is run immediately at startup and after the app is loaded (i.e. in
the C<run()> method).

=item keepalive

Enable/disable http keepalive requests.

=item read_timeout

Set read/keepalive timeout in seconds.

=item max_connection_reqs

Set max requests per connection in case of keepalive - 0(default) for unlimited.

=item quiet

Don't be so noisy. (default: on)

=item app_file

Load this filename as a native feersum app.

=back

picohttpparser-git/bench.c  view on Meta::CPAN

#define REQ                                                                                                                        \
    "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n"                                                \
    "Host: www.kittyhell.com\r\n"                                                                                                  \
    "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 "             \
    "Pathtraq/0.9\r\n"                                                                                                             \
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"                                                  \
    "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n"                                                                                 \
    "Accept-Encoding: gzip,deflate\r\n"                                                                                            \
    "Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n"                                                                            \
    "Keep-Alive: 115\r\n"                                                                                                          \
    "Connection: keep-alive\r\n"                                                                                                   \
    "Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; "                                                          \
    "__utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; "                                                             \
    "__utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n"             \
    "\r\n"

int main(void)
{
    const char *method;
    size_t method_len;
    const char *path;

t/05-streaming.t  view on Meta::CPAN

    };
});

is exception {
    $evh->use_socket($socket);
}, undef, 'assigned socket';

sub client {
    my $cnum = sprintf("%04d",shift);
    my $is_chunked = shift || 0;
    my $is_keepalive = shift || 0;
    $cv->begin;
    my $h; $h = simple_client GET => '/foo',
        name => $cnum,
        timeout => 15,
        proto => $is_chunked ? '1.1' : '1.0',
        headers => {
            "Accept" => "*/*",
            'X-Client' => $cnum,
        },
        $is_chunked && $is_keepalive ? (keepalive => 1) : (),
    sub {
        my ($body, $headers) = @_;
        is $headers->{Status}, 200, "$cnum got 200"
            or diag $headers->{Reason};
        if ($is_chunked) {
            is $headers->{HTTPVersion}, '1.1';
            is $headers->{'transfer-encoding'}, "chunked", "$cnum got chunked!";
            if (!$is_keepalive) {
                is $headers->{'connection'}, 'close', "$cnum conn close";
            } else {
                ok !exists($headers->{'connection'}), 'conn keep';
            }
        }
        else {
            is $headers->{HTTPVersion}, '1.0';
            ok !exists $headers->{'transfer-encoding'}, "$cnum not chunked!";
            if ($is_keepalive) {
                is $headers->{'connection'}, 'keep-alive', "$cnum conn keep";
            } else {
                ok !exists($headers->{'connection'}), 'conn close';
            }
        }
        is_deeply [split /\n/,$body], [
            "$cnum Hello streaming world! chunk one",
            "$cnum Hello streaming world! chunk 'two'",
            "$cnum Hello streaming world! chunk three",
        ], "$cnum got all three lines"
            or do {

t/05-streaming.t  view on Meta::CPAN

            };
        $cv->end;
        undef $h;
    };
}


client(1000+$_,1) for (1..CLIENTS_11);
client(2000+$_,0) for (1..CLIENTS_10); # HTTP/1.0 style

$evh->set_keepalive(1);
client(1000+$_,1,1) for (1..CLIENTS_11);

$cv->recv;
is $started, CLIENTS, 'handlers started';
is $finished, CLIENTS, 'handlers finished';

pass "all done";

t/07-graceful-shutdown.t  view on Meta::CPAN

    $cv->begin;
    my $w = $r->start_streaming("200 OK", ['Content-Type' => 'text/plain']);
    $started++;
    isa_ok($w, 'Feersum::Connection::Writer', "got a writer $cnum");
    isa_ok($w, 'Feersum::Connection::Handle', "... it's a handle $cnum");
    my $t; $t = AE::timer 1.5+rand(0.5), 0, sub {
        is exception {
            $w->write("So graceful!\n");
            $w->close();
        }, undef, "wrote after waiting a little $cnum";
        undef $t; # keep timer alive until it runs
        undef $w;
        $cv->end;
        $finished++;
    };
});

is exception {
    $evh->use_socket($socket);
}, undef, 'assigned socket';

t/51-psgi-streaming.t  view on Meta::CPAN

}

using_writer_and_1_0: {
    my $cv = AE::cv;
    $cv->begin;
    my $h2; $h2 = simple_client GET => '/', proto => '1.0', sub {
        my ($body, $headers) = @_;
        is $headers->{'Status'}, 200, "Response OK";
        is $headers->{'content-type'}, 'application/json', "... is JSON";
        ok !$headers->{'transfer-encoding'}, '... was not chunked';
        isnt $headers->{'connection'}, 'keep-alive', '... got close';
        is $body, q({"message":"O hai 3"}), "... correct body";
        $cv->end;
        undef $h2;
    };
    $cv->recv;
}

$evh->set_keepalive(1);

using_writer_and_1_1: {
    my $cv = AE::cv;
    $cv->begin;
    my $h2; $h2 = simple_client GET => '/', proto => '1.1', keepalive => 1, sub {
        my ($body, $headers) = @_;
        is $headers->{'Status'}, 200, "Response OK";
        is $headers->{'content-type'}, 'application/json', "... is JSON";
        ok $headers->{'transfer-encoding'}, '... not chunked';
        isnt $headers->{'connection'}, 'close', '... keep';
        is $body, q({"message":"O hai 4"}), "... correct de-chunked body";
        $cv->end;
        undef $h2;
    };
    $cv->recv;

t/63-plack-apps.t  view on Meta::CPAN

        my $req = HTTP::Request->new(GET =>
            "http://localhost/63-plack-apps.t");
        my $res = $cb->($req);
        my $s = "# IS THIS FILE"." STATICALLY SERVED?";
        is $res->code, 200;
        like $res->content, qr/^\Q$s\E$/m, "found static line (cascade)";
    }
);

via_redirect: test_psgi(
    # these two tests fail randomly on some platforms with keep_alive on.
    # from the pod...
    #   BUGS - Keep-alive is ignored completely.
    ua => Plack::LWPish->new( no_proxy => [qw/127.0.0.1/], keep_alive => 0 ),
    app => builder {
        mount '/static' => Plack::App::Cascade->new(apps => [
            Plack::App::File->new(root => 'notfound')->to_app,
            Plack::App::File->new(root => 't')->to_app,
        ]);
        mount '/' => sub {
            my $env = shift;
            my $req = Plack::Request->new($env);
            my $res = $req->new_response(200);
            if ($req->path eq '/') {

t/65-keepalive.t  view on Meta::CPAN


plan tests => 34;

pass 'using sock path '.$sock_path;

my $pid = fork();
if ($pid == 0) { # child
    eval {
        my $runner = Feersum::Runner->new(
            listen => [$sock_path],
            keepalive => 1,
            read_timeout => 1,
            max_connection_reqs => 4,
            app => sub {
                my $r = shift;
                pass 'got request http/1.'.($r->is_http11 ? 1 : 0);
                $r->send_response(200, [], []);
            }
        );
        ok $runner, "got a runner";
        $runner->run;

t/65-keepalive.t  view on Meta::CPAN

        unlike $_[1], qr(Connection), 'http/1.1 no connection header';
        $hdl->push_write("GET / HTTP/1.1\015\012Connection: close\015\012\015\012");
        $hdl->push_read(line => "\015\012\015\012" => sub {
            like $_[1], qr(Connection: close), 'http/1.1 connection close reply';
            $hdl->on_read(sub {});
        });
    });
    $cv->recv;
    undef $hdl;

    # keep alive timeout
    $socket = IO::Socket::UNIX->new(
        Peer => $sock_path,
        Type => SOCK_STREAM,
    ) or warn $!;
    ok $socket, 'client ok';
    ok $socket->blocking(0), 'unblock socket';

    $cv = AE::cv;
    $cv->begin;
    $hdl = AnyEvent::Handle->new(

t/65-keepalive.t  view on Meta::CPAN

            $hdl->destroy;
            $cv->send;
        },
        on_eof => sub {
            pass 'server closed connection';
            $hdl->destroy;
            $cv->send;
        },
        timeout => 2
    );
    $hdl->push_write("GET / HTTP/1.0\015\012Connection: keep-alive\015\012\015\012");
    $hdl->push_read(line => "\015\012\015\012" => sub {
        like $_[1], qr(Connection: keep-alive), 'http/1.0 connection keepalive reply';
        $hdl->push_write("GET / HTTP/1.0\015\012\015\012");
        $hdl->push_read(line => "\015\012\015\012" => sub {
            unlike $_[1], qr(Connection:), 'http/1.0 no connection header';
            $hdl->on_read(sub {});
        });
    });
    $cv->recv;
    undef $hdl;

    # max_connection_reqs

t/66-pipelining.t  view on Meta::CPAN

use lib 't'; use Utils;

BEGIN { use_ok('Feersum') };

my ($listen_socket, $port) = get_listen_socket();
ok $listen_socket, "made listen socket";
ok $listen_socket->fileno, "has a fileno";

my $evh = Feersum->new();

# Enable keep-alive which is needed for all tests
$evh->set_keepalive(1);
# Set a shorter read timeout to fail faster in case of problems
$evh->read_timeout(2.0);

my $request_count = 0;
$evh->request_handler(sub {
    my $r = shift;
    isa_ok $r, 'Feersum::Connection', 'got an object!';

    $request_count++;
    my $method = $r->method;

t/66-pipelining.t  view on Meta::CPAN

    }

    # Add small delays to verify ordering for specific requests
    my @res = (
        200,
        ['Content-Type' => 'text/plain'],
        ["Response $request_count: $method $path" . ($body ? " Body: $body" : "")]
    );

    # Add delay for certain paths to test response ordering
    if ($path =~ /\/delay|\/test2|\/keepalive2/) {
        my $w; $w = AE::timer 0.1, 0, sub {
            undef $w;
            $r->send_response(@res);
        };
    } else {
        $r->send_response(@res)
    }
});

is exception {

t/66-pipelining.t  view on Meta::CPAN

        "GET /test1 HTTP/1.1\r\nHost: localhost\r\n\r\n" .
        "GET /test2 HTTP/1.1\r\nHost: localhost\r\n\r\n" .
        "POST /post1 HTTP/1.1\r\nHost: localhost\r\nContent-Length: " . length($post_body1) . "\r\n\r\n" . $post_body1 .
        "POST /post2 HTTP/1.1\r\nHost: localhost\r\nContent-Length: " . length($post_body2) . "\r\n\r\n" . $post_body2 .
        "GET /test3 HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
    );

    $cv->recv;
};

# Part 2: Test keepalive requests (sequential requests on same connection)
subtest 'Keepalive Requests' => sub {
    plan tests => 14;
    my $cv = AE::cv;
    $cv->begin;

    my @responses;
    my $request_index = 0;
    my @requests = (
        "GET /keepalive1 HTTP/1.1\r\nHost: localhost\r\n\r\n",
        "GET /keepalive2 HTTP/1.1\r\nHost: localhost\r\n\r\n",
        "POST /keepalive-post HTTP/1.1\r\nHost: localhost\r\nContent-Length: 10\r\n\r\nKeepAlive!",
        "GET /keepalive-end HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
    );

    my $h; $h = AnyEvent::Handle->new(
        connect => ['localhost', $port],
        timeout => 5,
        on_error => sub {
            my ($h, $fatal, $msg) = @_;
            fail "client error in keepalive test: $msg";
            $cv->send;
        },
        on_eof => sub {
            # Done handling all responses
            is(scalar(@responses), 4, 'Got expected number of keepalive responses');

            my @parts = map { split /\r\n\r\n/ } @responses;

            # First keepalive response
            like($parts[0], qr/^HTTP\/1\.1 200 OK/, 'First keepalive response has correct status');
            like($parts[1], qr/Response \d+: GET \/keepalive1/, 'First keepalive response has correct body');

            # Second keepalive response (with delay)
            like($parts[2], qr/^HTTP\/1\.1 200 OK/, 'Second keepalive response has correct status');
            like($parts[3], qr/Response \d+: GET \/keepalive2/, 'Second keepalive response has correct body');

            # Third keepalive response (POST)
            like($parts[4], qr/^HTTP\/1\.1 200 OK/, 'Third keepalive response has correct status');
            like($parts[5], qr/Response \d+: POST \/keepalive-post Body: KeepAlive!/, 'Third keepalive response has correct POST body');

            # Fourth keepalive response (with close)
            like($parts[6], qr/^HTTP\/1\.1 200 OK/, 'Fourth keepalive response has correct status');
            like($parts[6], qr/Connection: close/, 'Fourth keepalive response has Connection: close');
            like($parts[7], qr/Response \d+: GET \/keepalive-end/, 'Fourth keepalive response has correct body');

            $cv->end;
            $h->destroy;
        },
        on_read => sub {
            my ($handle) = @_;
            last unless my $len = length(my $buf = $handle->rbuf);

            # Store response
            push @responses, $buf;

t/66-pipelining.t  view on Meta::CPAN

                $handle->push_write($requests[$request_index]);
            }
        }
    );

    # Send first request
    $h->push_write($requests[0]);
    $cv->recv;
};

# Part 3: Test mixed keepalive and pipelined requests with more POST requests
subtest 'Mixed Keepalive and Pipelined Requests' => sub {
    plan tests => 20;
    my $cv = AE::cv;
    $cv->begin;

    my @responses;
    my $mixed_phase = 0;
    my $h; $h = AnyEvent::Handle->new(
        connect => ['localhost', $port],
        timeout => 5,
        on_error => sub {

t/Utils.pm  view on Meta::CPAN

    my $proto = delete $opts{proto} || '1.1';
    my $body = delete $opts{body} || '';

    $headers->{'User-Agent'} ||= 'FeersumSimpleClient/1.0';
    $headers->{'Host'} ||= $host.':'.$port;
    if (length($body)) {
        $headers->{'Content-Length'} ||= length($body);
        $headers->{'Content-Type'} ||= 'text/plain';
    }

    # HTTP/1.1 default is 'keep-alive'
    $headers->{'Connection'} ||= 'close' if $proto eq '1.1' && !$opts{keepalive};

    my $head = join($CRLF, map {$_.': '.$headers->{$_}} sort keys %$headers);

    my $http_req = "$method $uri HTTP/$proto$CRLF";
    $strong_h->push_write($http_req);

    $strong_h->push_write($head.$CRLF.$CRLF.$body)
        unless $opts{skip_head};

#     $http_req =~ s/$CRLF/<CRLF>\n/sg;



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