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

Changes  view on Meta::CPAN

0.982 Tue Oct 12 10:55:00 2010 -0700
        Initial pre-forking support via Feersum::Runner & Plack::Runner
        Fix: resource leak induced by header-read errors
        Fix: compilation on BSD & Solaris re: SOL_TCP

0.981 Wed Oct  9 04:30:00 2010 -0700
        Support Web::Hippie (and psgix.io)
        Add missing JSON::XS test-dep.
        Fix: write() prototype was incorrect.
        Fix: writer not flushing on DESTROY.
        Fix: IO::Handle in PSGI streaming response.
        Fix: `use overload` PSGI callbacks in perl 5.8.x

0.971 Wed Oct  6 16:21:00 2010 -0700
        Fix the feersum script.

0.97  Wed Oct  6 15:00:00 2010 -0700
        Full Plack::Test::Suite compliance!
        Handle 304 responses properly.
        Don't manually steal TEMP vars.

Changes  view on Meta::CPAN

        Fix examples, Add a chat app example.

0.94  Thu Sep 30 22:45:00 2010 -0700
        Major documentation rewrite.
        Deprecated the "delayed response" part of the Feersum API in favor of the "streamed response" API.
        Allow for tied variables in the response.
        Move the IO::Handle response reading code into XS.

0.93  Tue Sep 29 01:30:00 2010 -0700
        First CPAN release.
        Redo PSGI streaming responses (mostly for code clarity).
        Fixes for Perl 5.8.x

0.92  Tue Sep 28 22:18:01 2010 -0700
        Support "Connection:close" style streaming for 1.0 clients.
        Support IO::Handle-like responses for PSGI handlers.
        Add REMOTE_ADDR and REMOTE_PORT to env.

0.91  Sun Sep 19 15:33:39 2010 -0700
        Support running Feersum under plackup.
        Remove AnyEvent::HTTP dependency.
        Make $r->env() calls faster.

0.90  Mon Sep  6 16:35:00 2010
        Initial PSGI 1.03 support (except for IO::Handle-like responses)

Feersum.xs  view on Meta::CPAN

static HV* feersum_env(pTHX_ struct feer_conn *c);
static SV* feersum_env_path(pTHX_ struct feer_req *r);
static SV* feersum_env_query(pTHX_ struct feer_req *r);
static HV* feersum_env_headers(pTHX_ struct feer_req *r, int norm);
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);

Feersum.xs  view on Meta::CPAN

    HV *e;
    e = newHV();

    // constants
    hv_stores(e, "psgi.version", newRV((SV*)psgi_ver));
    hv_stores(e, "psgi.url_scheme", newSVpvs("http"));
    hv_stores(e, "psgi.run_once", &PL_sv_no);
    hv_stores(e, "psgi.nonblocking", &PL_sv_yes);
    hv_stores(e, "psgi.multithread", &PL_sv_no);
    hv_stores(e, "psgi.multiprocess", &PL_sv_no);
    hv_stores(e, "psgi.streaming", &PL_sv_yes);
    hv_stores(e, "psgi.errors", newRV((SV*)PL_stderrgv));
    hv_stores(e, "psgix.input.buffered", &PL_sv_yes);
    hv_stores(e, "psgix.output.buffered", &PL_sv_yes);
    hv_stores(e, "psgix.body.scalar_refs", &PL_sv_yes);
    hv_stores(e, "psgix.output.guard", &PL_sv_yes);
    hv_stores(e, "SCRIPT_NAME", newSVpvs(""));

    // placeholders that get defined for every request
    hv_stores(e, "SERVER_PROTOCOL", &PL_sv_undef);
    hv_stores(e, "SERVER_NAME", &PL_sv_undef);

Feersum.xs  view on Meta::CPAN

}

INLINE_UNLESS_DEBUG static ssize_t
feersum_env_content_length(pTHX_ struct feer_conn *c)
{
    return c->expected_cl;
}

static void
feersum_start_response (pTHX_ struct feer_conn *c, SV *message, AV *headers,
                        int streaming)
{
    const char *ptr;
    I32 i;

    trace("start_response fd=%d streaming=%d\n", c->fd, streaming);

    if (unlikely(c->responding != RESPOND_NOT_STARTED))
        croak("already responding?!");
    change_responding_state(c, streaming ? RESPOND_STREAMING : RESPOND_NORMAL);

    if (unlikely(!SvOK(message) || !(SvIOK(message) || SvPOK(message)))) {
        croak("Must define an HTTP status code or message");
    }

    I32 avl = av_len(headers);
    if (unlikely(avl+1 % 2 == 1)) {
        croak("expected even-length array, got %d", avl+1);
    }

Feersum.xs  view on Meta::CPAN

        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;
    int i;
    bool body_is_string = 0;
    STRLEN cur;

    if (c->responding != RESPOND_NORMAL)
        croak("can't use write_whole_body when in streaming mode");

    if (!SvOK(body)) {
        body = sv_2mortal(newSVpvs(""));
        body_is_string = 1;
    }
    else if (SvROK(body)) {
        SV *refd = SvRV(body);
        if (SvOK(refd) && !SvROK(refd)) {
            body = refd;
            body_is_string = 1;

Feersum.xs  view on Meta::CPAN

        sv_setpvf(cl_sv, "Content-Length: %"Sz_uf"" CRLFx2, (Sz)RETVAL);
        update_wbuf_placeholder(c, cl_sv, cl_iov);
    }

    change_responding_state(c, RESPOND_SHUTDOWN);
    conn_write_ready(c);
    return RETVAL;
}

static void
feersum_start_psgi_streaming(pTHX_ struct feer_conn *c, SV *streamer)
{
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    mXPUSHs(feer_conn_2sv(c));
    XPUSHs(streamer);
    PUTBACK;
    call_method("_initiate_streaming_psgi", G_DISCARD|G_EVAL|G_VOID);
    SPAGAIN;
    if (unlikely(SvTRUE(ERRSV))) {
        call_died(aTHX_ c, "PSGI stream initiator");
    }
    PUTBACK;
    FREETMPS;
    LEAVE;
}

static void

Feersum.xs  view on Meta::CPAN

{
    if (unlikely(!SvOK(ret) || !SvROK(ret))) {
        sv_setpvs(ERRSV, "Invalid PSGI response (expected reference)");
        call_died(aTHX_ c, "PSGI request");
        return;
    }

    if (SvOK(ret) && unlikely(!IsArrayRef(ret))) {
        if (likely(can_recurse)) {
            trace("PSGI response non-array, c=%p ret=%p\n", c, ret);
            feersum_start_psgi_streaming(aTHX_ c, ret);
        }
        else {
            sv_setpvs(ERRSV, "PSGI attempt to recurse in a streaming callback");
            call_died(aTHX_ c, "PSGI request");
        }
        return;
    }

    AV *psgi_triplet = (AV*)SvRV(ret);
    if (unlikely(av_len(psgi_triplet)+1 != 3)) {
        sv_setpvs(ERRSV, "Invalid PSGI array response (expected triplet)");
        call_died(aTHX_ c, "PSGI request");
        return;

Feersum.xs  view on Meta::CPAN


    XSRETURN_IV(len);
}

STRLEN
write (feer_conn_handle *hdl, ...)
    PROTOTYPE: $;$
    CODE:
{
    if (unlikely(c->responding != RESPOND_STREAMING))
        croak("can only call write in streaming mode");

    SV *body = (items == 2) ? ST(1) : &PL_sv_undef;
    if (unlikely(!body || !SvOK(body)))
        XSRETURN_IV(0);

    trace("write fd=%d c=%p, body=%p\n", c->fd, c, body);
    if (SvROK(body)) {
        SV *refd = SvRV(body);
        if (SvOK(refd) && SvPOK(refd)) {
            body = refd;

Feersum.xs  view on Meta::CPAN

}
    OUTPUT:
        RETVAL

void
write_array (feer_conn_handle *hdl, AV *abody)
    PROTOTYPE: $$
    PPCODE:
{
    if (unlikely(c->responding != RESPOND_STREAMING))
        croak("can only call write in streaming mode");

    trace("write_array fd=%d c=%p, abody=%p\n", c->fd, c, abody);

    I32 amax = av_len(abody);
    int i;
    if (c->is_http11) {
        for (i=0; i<=amax; i++) {
            SV *sv = fetch_av_normal(aTHX_ abody, i);
            if (likely(sv)) add_chunk_sv_to_wbuf(c, sv);
        }

Feersum.xs  view on Meta::CPAN

    CODE:
        RETVAL = feersum_conn_guard(aTHX_ c, (items==2) ? ST(1) : NULL);
    OUTPUT:
        RETVAL

MODULE = Feersum	PACKAGE = Feersum::Connection

PROTOTYPES: ENABLE

SV *
start_streaming (struct feer_conn *c, SV *message, AV *headers)
    PROTOTYPE: $$\@
    CODE:
        feersum_start_response(aTHX_ c, message, headers, 1);
        RETVAL = new_feer_conn_handle(aTHX_ c, 1); // RETVAL gets mortalized
    OUTPUT:
        RETVAL

int
is_http11 (struct feer_conn *c)
    CODE:

Feersum.xs  view on Meta::CPAN

    PROTOTYPE: $$\@$
    CODE:
        feersum_start_response(aTHX_ c, message, headers, 0);
        if (unlikely(!SvOK(body)))
            croak("can't send_response with an undef body");
        RETVAL = feersum_write_whole_body(aTHX_ c, body);
    OUTPUT:
        RETVAL

SV*
_continue_streaming_psgi (struct feer_conn *c, SV *psgi_response)
    PROTOTYPE: $\@
    CODE:
{
    AV *av;
    int len = 0;

    if (IsArrayRef(psgi_response)) {
        av = (AV*)SvRV(psgi_response);
        len = av_len(av) + 1;
    }

MANIFEST  view on Meta::CPAN

picohttpparser-git/picohttpparser.c
picohttpparser-git/picohttpparser.h
picohttpparser-git/test.c
picohttpparser-git/test_response.c
ppport.h
rinq.c
t/01-simple.t
t/02-array-body.t
t/03-env-hash.t
t/04-died.t
t/05-streaming.t
t/06-input.t
t/07-graceful-shutdown.t
t/08-read-timeout.t
t/09-magic.t
t/10-respond-304.t
t/11-runner.t
t/12-close-on-drop.t
t/13-pre-fork.t
t/14-guard.t
t/15-write_array.t
t/50-psgi-simple.t
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

TODO  view on Meta::CPAN

Timeouts
    * slow responses? (feasable? just let TCP do it?)

psgi.input streaming

    * add a "poll_cb()" method to the psgi.input handle as an extension?  EV
    gets to schedule the watcher in that case rather than bleeding the fd to
    the handler.
    * related: Connection: close bodies
    * related: Transfer-Encoding: chunked bodies

IO::Handle-like responses

    * check if it's got a real file descriptor? optimize (libeio or similar

TODO  view on Meta::CPAN

read right away?

multiple Feersum threads, one Perl thread?

WebSocket support (v1.1)

    * http://www.whatwg.org/specs/web-socket-protocol/
    * Support psgix.io and Web::Hippie already (0.981), but would be good to
    accelerate it.
    * Do the handshake in C/XS, call request_handler once request is complete.
    * I/O is done using the streaming interface (buffered)
    * requires random numbers (drand48?) and an MD5 implementation (link
    openssl? use the guts of Digest::MD5 somehow?)
    * make this a separate module since if it brings in an openssl deps.
    * will this work for PSGI? magic psgix.web_socket or something?

Release t/Utils.pm's "simple_client" as "anyevent::anotherhttp" or something?

accept4
    * available on newer linuxes, saves calls to fcntl for setting NONBLOCK
    and CLOEXEC

eg/chat.feersum  view on Meta::CPAN

    Say: <input type="text" name="say" /><br />
    <input type="submit">
</form>
</body>
</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>

eg/oneshot.pl  view on Meta::CPAN

    Listen => 1024,
    Blocking => 0,
    ReuseAddr => 1,
);

my $evh = Feersum->new();
$evh->use_socket($socket);
$evh->request_handler(sub {
    my $r = shift;
    my $n = "only";
    my $w = $r->start_streaming("200 OK", [
        'Content-Type' => 'text/plain',
        'Connection' => 'close',
    ]);
    $w->write("Hello customer number ");
    $w->write(\$n);
    $w->write("\n");
    $w->close();
    $evh->graceful_shutdown(sub { EV::break });
});

lib/Feersum.pm  view on Meta::CPAN

                ['Content-Type' => 'text/plain'],
                \"You win one cryptosphere!\n"
            );
            undef $t;
        };
    });

=head1 DESCRIPTION

Feersum is an HTTP server built on L<EV>.  It fully supports the PSGI 1.03
spec including the C<psgi.streaming> interface and is compatible with Plack.
PSGI 1.1, which has yet to be published formally, is also supported.  Feersum
also has its own "native" interface which is similar in a lot of ways to PSGI,
but is B<not compatible> with PSGI or PSGI middleware.

Feersum uses a single-threaded, event-based programming architecture to scale
and can handle many concurrent connections efficiently in both CPU and RAM.
It skips doing a lot of sanity checking with the assumption that a "front-end"
HTTP/HTTPS server is placed between it and the Internet.

=head2 How It Works

lib/Feersum.pm  view on Meta::CPAN

loopback interface, OS Ubuntu 6.06LTS, Perl 5.8.7.  Your mileage will likely
vary.

For even faster results, Feersum can support very simple pre-forking (See
L<feersum>, L<Feersum::Runner> or L<Plack::Handler::Feersum> for details).

=head1 INTERFACE

There are two handler interfaces for Feersum: The PSGI handler interface and
the "Feersum-native" handler interface.  The PSGI handler interface is fully
PSGI 1.03 compatible and supports C<psgi.streaming>. The
C<psgix.input.buffered> and C<psgix.io> features of PSGI 1.1 are also
supported.  The Feersum-native handler interface is "inspired by" PSGI, but
does some things differently for speed.

Feersum will use "Transfer-Encoding: chunked" for HTTP/1.1 clients and
"Connection: close" streaming as a fallback.  Technically "Connection: close"
streaming isn't part of the HTTP/1.0 or 1.1 spec, but many browsers and agents
support it anyway.

Currently POST/PUT does not stream input, but read() can be called on
C<psgi.input> to get the body (which has been buffered up before the request
callback is called and therefore will never block).  Likely C<read()> will
change to raise EAGAIN responses and allow for a callback to be registered on
the arrival of more data. (The C<psgix.input.buffered> env var is set to
reflect this).

=head2 PSGI interface

Feersum fully supports the PSGI 1.03 spec including C<psgi.streaming>.

See also L<Plack::Handler::Feersum>, which provides a way to use Feersum with
L<plackup> and L<Plack::Runner>.

Call C<< psgi_request_handler($app) >> to register C<$app> as a PSGI handler.

    my $app = do $filename;
    Feersum->endjinn->psgi_request_handler($app);

The env hash passed in will always have the following keys in addition to
dynamic ones:

    psgi.version      => [1,0],
    psgi.nonblocking  => 1,
    psgi.multithread  => '', # i.e. false
    psgi.multiprocess => '',
    psgi.streaming    => 1,
    psgi.errors       => \*STDERR,
    SCRIPT_NAME       => "",

Feersum adds these extensions (see below for info)

    psgix.input.buffered   => 1,
    psgix.output.buffered  => 1,
    psgix.body.scalar_refs => 1,
    psgix.output.guard     => 1,
    psgix.io               => \$magical_io_socket,

lib/Feersum.pm  view on Meta::CPAN


For requests with a body (e.g. POST) C<psgi.input> will contain a valid
file-handle.  Feersum currently passes C<undef> for psgi.input when there is
no body to avoid unnecessary work.

    my $r = delete $env->{'psgi.input'};
    $r->read($body, $env->{CONTENT_LENGTH});
    # optional: choose to stop receiving further input, discard buffers:
    $r->close();

The C<psgi.streaming> interface is fully supported, including the
writer-object C<poll_cb> callback feature defined in PSGI 1.03.  B<Note that
poll_cb is removed from the preliminary PSGI 1.1 spec>.  Feersum calls the
poll_cb callback after all data has been flushed out and the socket is
write-ready.  The data is buffered until the callback returns at which point
it will be immediately flushed to the socket.

    my $app = sub {
        my $env = shift;
        return sub {
            my $respond = shift;

lib/Feersum.pm  view on Meta::CPAN

buffered in some way.

Feersum currently buffers the entire input in memory calling the callback.

B<Feersum's input behaviour MAY eventually change to not be
psgix.input.buffered!>  Likely, a C<poll_cb()> method similar to how the
writer handle works could be registered to have input "pushed" to the app.

=item psgix.output.guard

The streaming responder has a C<response_guard()> method that can be used to
attach a guard to the request.  When the request completes (all data has been
written to the socket and the socket has been closed) the guard will trigger.
This is an alternate means to doing a "write completion" callback via
C<poll_cb()> that should be more efficient.  An analogy is the "on_drain"
handler in L<AnyEvent::Handle>.

A "guard" in this context is some object that will do something interesting in
its DESTROY/DEMOLISH method. For example, L<Guard>.

=item psgix.io

The raw socket extension B<psgix.io> is provided in order to support
L<Web::Hippie> and websockets.  C<psgix.io> is defined as part of PSGI 1.1.
To obtain the L<IO::Socket> corresponding to this connection, read this
environment variable.

The underlying file descriptor will have C<O_NONBLOCK>, C<TCP_NODELAY>,
C<SO_OOBINLINE> enabled and C<SO_LINGER> disabled.

PSGI apps B<MUST> use a C<psgi.streaming> response so that Feersum doesn't try
to flush and close the connection.  Additionally, the "respond" parameter to
the streaming callback B<MUST NOT> be called for the same reason.

    my $env = shift;
    return sub {
        my $fh = $env->{'psgix.io'};
        syswrite $fh,
    };

=back

=head2 The Feersum-native interface

lib/Feersum.pm  view on Meta::CPAN

        my $r = delete $env->{'psgi.input'};
        $r->read($body, $env->{CONTENT_LENGTH});
        # optional:
        $r->close();
    }

Starting a response in stream mode enables the C<write()> method (which really
acts more like a buffered 'print').  Calls to C<write()> will never block.

    my $req = shift;
    my $w = $req->start_streaming(200, \@headers);
    $w->write(\"this is a reference to some shared chunk\n");
    $w->write("regular scalars are OK too\n");
    $w->close(); # close off the stream

The writer object supports C<poll_cb> as also specified in PSGI 1.03.  Feersum
will call the callback only when all data has been flushed out at the socket
level.  Use C<close()> or unset the handler (C<< $w->poll_cb(undef) >>) to
stop the callback from getting called.

    my $req = shift;
    my $w = $req->start_streaming(
        "200 OK", ['Content-Type' => 'application/json']);
    my $n = 0;
    $w->poll_cb(sub {
        # $_[0] is a copy of $w so a closure doesn't need to be made
        $_[0]->write(get_next_chunk());
        $_[0]->close if ($n++ >= 100);
    });

Note that C<< $w->close() >> will be called when the last reference to the
writer is dropped.

lib/Feersum.pm  view on Meta::CPAN


Worth noting is that a similar zero-copy effect can be achieved by using the
C<psgix.body.scalar_refs> feature.

=back

=head1 BUGS

Please report bugs using http://github.com/stash/Feersum/issues/

Currently there's no way to limit the request entity length of a B<streaming>
POST/PUT/etc.  This could lead to a DoS attack on a Feersum server.  Suggested
remedy is to only run Feersum behind some other web server and to use that to
limit the entity size.

Although not explicitly a bug, the following may cause undesirable behavior.
Feersum will have set SIGPIPE to be ignored by the time your handler gets
called.  If your handler needs to detect SIGPIPE, be sure to do a
C<local $SIG{PIPE} = ...> (L<perlipc>) to make it active just during the
necessary scope.

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

sub new {
    croak "Cannot instantiate Feersum::Connection directly";
}

sub read_handle {
    croak "read_handle is deprecated; use psgi.input instead";
}

sub write_handle {
    croak "write_handle is deprecated; ".
        "use return value from start_streaming instead";
}

sub start_response {
    croak "start_response is deprecated; ".
        "use start_streaming() or start_whole_response() instead";
}

sub initiate_streaming {
    croak "initiate_streaming is deprecated; ".
        "use start_streaming() and its return value instead";
}

sub _initiate_streaming_psgi {
    my ($self, $streamer) = @_;
    return $streamer->(sub { $self->_continue_streaming_psgi(@_) });
}

my $_pkg = "Feersum::";
sub _raw { ## no critic (RequireArgUnpacking)
    # don't shift; want to modify $_[0] directly.
    my $fileno = $_[1];
    my $name = "RAW$fileno";
    # Hack to make gensyms via new_from_fd() show up in the Feersum package.
    # This may or may not save memory (HEKs?) over true gensyms.
    no warnings 'redefine';

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

}
1;
__END__

=head1 NAME

Feersum::Connection - HTTP connection encapsulation

=head1 SYNOPSIS

For a streaming response:

    Feersum->endjinn->request_handler(sub {
        my $req = shift; # this is a Feersum::Connection object
        my $env = $req->env();
        my $w = $req->start_streaming(200, ['Content-Type' => 'text/plain']);
        # then immediately or after some time:
        $w->write("Ergrates ");
        $w->write(\"FTW.");
        $w->close();
    });

For a response with a Content-Length header:

    Feersum->endjinn->request_handler(sub {
        my $req = shift; # this is a Feersum::Connection object

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

=over 4

=item C<< my $env = $req->env() >>

Obtain an environment hash.  This hash contains the same entries as for a PSGI
handler environment hash.  See L<Feersum> for details on the contents.

This is a method instead of a parameter so that future versions of Feersum can
request a slice of the hash for speed.

=item C<< my $w = $req->start_streaming($code, \@headers) >>

A full HTTP header section is sent with "Transfer-Encoding: chunked" (or
"Connection: close" for HTTP/1.0 clients).  

Returns a C<Feersum::Connection::Writer> handle which should be used to
complete the response.  See L<Feersum::Connection::Handle> for methods.

=item C<< $req->send_response($code, \@headers, $body) >>

=item C<< $req->send_response($code, \@headers, \@body) >>

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

=item C<< $req->force_http10 >>

=item C<< $req->force_http11 >>

Force the response to use HTTP/1.0 or HTTP/1.1, respectively.

Normally, if the request was made with 1.1 then Feersum uses HTTP/1.1 for the
response, otherwise HTTP/1.0 is used (this includes requests made with the
HTTP "0.9" non-declaration).

For streaming under HTTP/1.1 C<Transfer-Encoding: chunked> is used, otherwise
a C<Connection: close> stream-style is used (with the usual non-guarantees
about delivery).  You may know about certain user-agents that
support/don't-support T-E:chunked, so this is how you can override that.

Supposedly clients and a lot of proxies support the C<Connection: close>
stream-style, see support in Varnish at
http://www.varnish-cache.org/trac/ticket/400

=item C<< $req->fileno >>

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

No-op. Feersum will create these objects internally.

=item C<< $req->read_handle >>

use psgi.input instead

=item C<< $req->write_handle >>

=item C<< $req->start_response(...) >>

use start_streaming() or start_whole_response() instead

=item C<< $req->initiate_streaming(...) >>

use start_streaming() and its return value instead

=back

=end comment

=head1 AUTHOR

Jeremy Stashewsky, C<< stash@cpan.org >>

=head1 COPYRIGHT AND LICENSE

lib/Feersum/Connection/Handle.pm  view on Meta::CPAN


=item C<< $r->poll_cb(sub { .... }) >>

B<NOT YET SUPPORTED>.  PSGI only defined poll_cb for the Writer object.

=back

=head2 Writer methods.

The writer is obtained under PSGI by sending a code/headers pair to the
"starter" callback.  Under Feersum, calls to C<< $req->start_streaming >>
return one.

=over 4

=item C<< $w->write("scalar") >>

Send the scalar as a "T-E: chunked" chunk.

The calls to C<< $w->write() >> will never block and data is buffered until
transmitted.  This behaviour is indicated by C<psgix.output.buffered> in the

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


my $cv = AE::cv;
my $started = 0;
my $finished = 0;
$evh->request_handler(sub {
    my $r = shift;
    isa_ok $r, 'Feersum::Connection', 'got an object!';
    my $env = $r->env();
    ok $env && ref($env) eq 'HASH';

    ok $env->{'psgi.streaming'}, 'got psgi.streaming';
    my $cnum = $env->{HTTP_X_CLIENT};
    ok $cnum, "got client number";

    ok !$r->can('write'), "write method removed from connection object";

    $cv->begin;
    my $w = $r->start_streaming("200 OK", ['Content-Type' => 'text/plain', 'X-Client' => $cnum, 'X-Fileno' => $r->fileno ]);
    $started++;
    isa_ok($w, 'Feersum::Connection::Writer', "got a writer $cnum");
    isa_ok($w, 'Feersum::Connection::Handle', "... it's a handle $cnum");
    my $n = 0;
    my $wrote_third = 0;
    my $t; $t = AE::timer rand()/5,rand()/5, sub {
        $n++;
        eval {
            if ($n == 1) {
                ok blessed($w), "still blessed? $cnum";
                # cover PADTMP case
                $w->write("$cnum Hello streaming world! chunk ".
                          ($n==1?"one":"WTF")."\n");
                pass "wrote chunk $n $cnum";
            }
            elsif ($n == 2) {
                ok blessed($w), "still blessed? $cnum";
                # cover PADMY case
                my $d = "$cnum Hello streaming world! chunk ".
                        ($n==1?"WTF":"'two'")."\n";
                $w->write($d);
                pass "wrote chunk $n $cnum";
            }
            elsif ($n == 3) {
                ok blessed($w), "still blessed? $cnum";
                my $buf = "$cnum Hello streaming world! chunk three\n";
                $w->poll_cb(sub {
                    my $w2 = shift;
                    isa_ok($w2, 'Feersum::Connection::Writer',
                        "got another writer $cnum");
                    $w2->write($buf);
                    $w2->poll_cb(undef); # unset
                    $wrote_third = 1;
                });
            }
            elsif ($wrote_third) {

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

        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 {
                warn "descriptor ".$headers->{'x-fileno'}." failed!";
                exit 2;
            };
        $cv->end;
        undef $h;
    };
}

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


my $cv = AE::cv;
my $started = 0;
my $finished = 0;
$evh->request_handler(sub {
    my $r = shift;
    isa_ok $r, 'Feersum::Connection', 'got an object!';
    my $env = $r->env();
    ok $env && ref($env) eq 'HASH';

    ok $env->{'psgi.streaming'}, 'got psgi.streaming';
    my $cnum = $env->{HTTP_X_CLIENT};
    ok $cnum, "got client number";

    $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;

t/12-close-on-drop.t  view on Meta::CPAN

    no warnings 'redefine';
    *Feersum::DIED = sub {
        my $err = shift;
        fail "Died during request handler: $err";
    };
}

$evh->request_handler(sub {
    my $r = shift;
    ok $r, 'got request';
    my $w = $r->start_streaming(200, []);
    $w->write("hello ");
    $w->write("world!\n");
    is exception {
        undef $w;
    }, undef, 'no death on undef';
});

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

t/14-guard.t  view on Meta::CPAN


$cv->recv;
is $guard_fired, 1, "guard fired only once";
pass 'done simple guard';

$endjinn->request_handler(sub {
    my $r = shift;
    my $env = $r->env;
    ok $env->{'psgix.output.guard'}, 'env says the writer has this guard';
    scope_for_writer: {
        my $w = $r->start_streaming(200,[]);
        $w->response_guard(guard {
            $guard_fired++;
            fail "guard called (should get cancelled)";
        });
        $w->response_guard->cancel;
        is $guard_fired, 0, "guard didn't fire yet (cancelled)";
        $w->response_guard(guard {
            $guard_fired++;
            pass "stream writer guard called";
        });

t/15-write_array.t  view on Meta::CPAN

my $finished = 0;
$endjinn->request_handler(sub {
    my $r = shift;
    isa_ok $r, 'Feersum::Connection', 'got an object!';
    my $env = $r->env();
    ok $env && ref($env) eq 'HASH';

    my $cnum = $env->{'HTTP_X_CLIENT'};

    $cv->begin;
    my $w = $r->start_streaming("200 OK", ['Content-Type' => 'text/plain', 'X-Client' => $cnum, 'X-Fileno' => $r->fileno ]);
    $started++;
    isa_ok($w, 'Feersum::Connection::Writer', "got a writer $cnum");
    isa_ok($w, 'Feersum::Connection::Handle', "... it's a handle $cnum");
    my @first = (
        "$cnum Hello streaming world! chunk one\n",
        \"$cnum Hello streaming world! chunk two\n",
        undef,
        "$cnum Hello streaming world! chunk three\n",
        \"$cnum Hello streaming world! chunk four\n",
    );
    $w->write_array(\@first);
    $w->close;
    $cv->end;
    pass "$cnum handler completed";
});


sub client {
    my $cnum = sprintf("%04d",shift);

t/15-write_array.t  view on Meta::CPAN

            "Accept" => "*/*",
            'X-Client' => $cnum,
        },
    sub {
        my ($body, $headers) = @_;
        is $headers->{Status}, 200, "$cnum got 200"
            or diag $headers->{Reason};
        is $headers->{HTTPVersion}, '1.1', "$cnum version";
        is $headers->{'transfer-encoding'}, "chunked", "$cnum got chunked!";
        is_deeply [split /\n/,$body], [
            "$cnum Hello streaming world! chunk one",
            "$cnum Hello streaming world! chunk two",
            "$cnum Hello streaming world! chunk three",
            "$cnum Hello streaming world! chunk four",
        ], "$cnum got all four lines"
            or do {
                warn "descriptor ".$headers->{'x-fileno'}." failed!";
                exit 2;
            };
        $cv->end;
        undef $h;
    };
}

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

        $cb->(Message->new());
        undef $t; # cancel circular-ref
    };
    return;
}

# from the PSGI::FAQ
my $APP = <<'EOAPP';
    my $app = sub {
        my $env = shift;
        unless ($env->{'psgi.streaming'}) {
            die "This application needs psgi.streaming support";
        }
        Test::More::pass "called app";
        return sub {
            Test::More::pass "called streamer";
            my $respond = shift;
            wait_for_new_message(sub {
                my $message = shift;
                my $body = [ $message->to_json ];
                Test::More::pass "sending response";
                undef $env;

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

        undef $h;
    };

    $cv->recv;
    pass "all done app 1";
}

my $APP2 = <<'EOAPP';
    my $app2 = sub {
        my $env = shift;
        unless ($env->{'psgi.streaming'}) {
            die "This application needs psgi.streaming support";
        }
        Test::More::pass "called app2";
        return sub {
            Test::More::pass "called streamer2";
            my $respond = shift;
            wait_for_new_message(sub {
                my $message = shift;
                Test::More::pass "sending response2";
                my $w = $respond->([200, ['Content-Type', 'application/json']]);
                Test::More::pass "started response2";

t/53-psgi-overloaded.t  view on Meta::CPAN

        my $class = shift;
        my $the_code = shift;
        my $self = bless { the_code => $the_code }, $class;
        return $self;
    }
}

my $APP = <<'EOAPP';
    my $app = Ovrldr->new(sub {
        my $env = shift;
        unless ($env->{'psgi.streaming'}) {
            die "This application needs psgi.streaming support";
        }
        Test::More::pass "called app";
        return Ovrldr->new(sub {
            Test::More::pass "called streamer";
            my $respond = shift;
            my $msg = q({"message":"O hai 1"});
            $respond->([200, ['Content-Type', 'application/json'], [$msg]]);
            Test::More::pass "sent response";
        });
    });



( run in 0.384 second using v1.01-cache-2.11-cpan-4d50c553e7e )