From d89c1f4b0abc3eed9d03d400fd2e7f24c891de3c Mon Sep 17 00:00:00 2001 From: asergei Date: Sun, 23 Mar 2014 21:58:02 +0400 Subject: [PATCH 1/2] Make it possible to return PSGI-style streaming response --- lib/Dancer/Cookbook.pod | 27 +++++++++++++++++++++++++++ lib/Dancer/Handler.pm | 11 ++++++++++- lib/Dancer/Serializer.pm | 2 +- t/12_response/12_psgi_streaming.t | 26 ++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 t/12_response/12_psgi_streaming.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..e04bf22c4 --- /dev/null +++ b/t/12_response/12_psgi_streaming.t @@ -0,0 +1,26 @@ +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 { + return request->env->{'psgi.streaming'} ? 'YES' : 'NO'; + 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; + From b16d22e3c13fa02ae88a7dadc8e986667fcd9897 Mon Sep 17 00:00:00 2001 From: asergei Date: Sun, 23 Mar 2014 21:58:02 +0400 Subject: [PATCH 2/2] Make it possible to return PSGI-style streaming response --- lib/Dancer/Cookbook.pod | 27 +++++++++++++++++++++++++++ lib/Dancer/Handler.pm | 11 ++++++++++- lib/Dancer/Serializer.pm | 2 +- t/12_response/12_psgi_streaming.t | 25 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 t/12_response/12_psgi_streaming.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; +