view release on metacpan or search on metacpan
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
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.
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)
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);
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);
}
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);
}
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;
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
{
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;
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;
}
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);
}
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:
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;
}
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
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
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";
});
});