diff --git a/MANIFEST b/MANIFEST index 2825f3806..0a55a12c6 100644 --- a/MANIFEST +++ b/MANIFEST @@ -268,6 +268,7 @@ t/12_response/08_drop_content.t t/12_response/09_headers_to_array.t t/12_response/10_error_dumper.t t/12_response/11_CVE-2012-5572.t +t/12_response/12_psgi_streaming.t t/13_engines/00_load.t t/13_engines/02_template_init.t t/14_serializer/01_helpers.t diff --git a/lib/Dancer/Cookbook.pod b/lib/Dancer/Cookbook.pod index ac8328018..58d617750 100644 --- a/lib/Dancer/Cookbook.pod +++ b/lib/Dancer/Cookbook.pod @@ -302,6 +302,33 @@ The following routes will be generated for us: +=head2 Low-level access to PSGI interface + +You may return a PSGI-formatted response from your Dancer route if needed. +This ability is useful if you are building a streaming app. Note that you will +need a PSGI-compliant server with streaming support. + + get '/streaming' => sub { + # use my app infrastructure to make database lookups, session checks etc. + # then return a callback-style response + return sub { + my $respond = shift; + my $writer = $respond->( + [ 200, [ 'Content-Type', 'text/event-stream' ] ] + ); + subscribe_user($writer); # set up events in your app + $writer->write( + HTTP::ServerEvent->as_string( + data => 'connected' + ) + ); + } + }; + +See L for more details. + + + =head1 MUSCLE MEMORY: STORING DATA =head2 Handling sessions diff --git a/lib/Dancer/Handler.pm b/lib/Dancer/Handler.pm index 0f5e6afe3..b61b3c532 100644 --- a/lib/Dancer/Handler.pm +++ b/lib/Dancer/Handler.pm @@ -145,7 +145,16 @@ sub render_response { my $content = $response->content; - unless ( ref($content) eq 'GLOB' ) { + if (ref($content) eq 'CODE') { + unless ( + Dancer::SharedData->request + && Dancer::SharedData->request->env->{'psgi.streaming'} + ) { + raise core => 'Sorry, streaming is not supported on this server.'; + } + return $content; + } + elsif ( ref($content) ne 'GLOB' ) { my $charset = setting('charset'); my $ctype = $response->header('Content-Type'); diff --git a/lib/Dancer/Serializer.pm b/lib/Dancer/Serializer.pm index 772e52623..4e739ce96 100644 --- a/lib/Dancer/Serializer.pm +++ b/lib/Dancer/Serializer.pm @@ -40,7 +40,7 @@ sub process_response { my $content = $response->{content}; - if (ref($content) && (ref($content) ne 'GLOB')) { + if (ref($content) && ref($content) ne 'GLOB' && ref($content) ne 'CODE') { local $@; eval { $content = engine->serialize($content) }; diff --git a/t/12_response/12_psgi_streaming.t b/t/12_response/12_psgi_streaming.t new file mode 100644 index 000000000..a3d02ade7 --- /dev/null +++ b/t/12_response/12_psgi_streaming.t @@ -0,0 +1,25 @@ +package main; +use strict; +use warnings; +use Test::More tests => 2, import => ['!pass']; +use Dancer::Test; + +{ + use Dancer; + set environment => 'psgi.streaming'; # it is required in a real-world app + # but here it makes no difference + get '/psgi' => sub { + sub { + # implementation is irrelevant + } + }; +} + +note "Testing PSGI-streaming response"; +my $req = [ GET => '/psgi' ]; +route_exists $req; +my $res = dancer_response @$req; +is(ref($res->content), 'CODE', 'reponse contains coderef'); + +1; +