[dancer-users] Streaming binary data without known size? Content-length problem.

Andy Walker walkeraj at gmail.com
Thu May 9 17:26:28 BST 2013


While $working on a RESTFul application, I came across a situation where I
needed to pack up a directory of files (already gzipped) and send them to
the user.  Rather than pulling everything into memory with Archive::Tar or
creating a temporary file with File::Temp that would meet an uncertain fate
after file_send depending on the deployment, I decided I would try and
forkexec using a pipe and just stream the tar to the user.

This was easy enough to accomplish with open and '-|' and then

exec ('/path/to/tar', '-c', '-f', '-' ...);

in the child.

After that, it was a matter of duplicating parts of send_file, and I think
this is where I got it wrong.  Testing with raw Telnet saw the binary data
come through, so I know that at least the piping worked, but  when I tested
the route as users would use it, the browser downloaded an empty file and
curl -v, mentioned something about "* Excess found in a non pipelined read:
excess = 7989".

I looked in the printed headers, and saw that it defined Content-Length: 0,
which I assume is part of the problem.  I tried NOT sending it using the
code below using:

$response->{headers}->remove_header('Content-Length');

but was unable to remove it, and I'm not sure that's what I want to do
anyway.  Hijacking the stream and just catting binary data doesn't seem to
be part of the spec, though I'd swear I've seen it done elswhere before,
but I thought I would pick the collective brains of the mailing list and
see.

I eventually ended up just using File::Temp::tmpnam and having tar output
to that file, which I then added to a var called 'cleanup' that an after
hook deletes.  Then I sent with file_send as normal.

So, I suppose my main question is: would this work if I were somehow able
to remove the Content-Length header?  I'm thinking not and, if not, should
I be using chunked transfer instead?  Does Dancer support this?

CODE:

sub send_data {
    Dancer::Continuation::Route::FileSent->new(
        return_value => _send_data(@_)
    )->throw
}

sub _send_data {
    my ($fh, %options) = @_;
    my $env = Dancer::SharedData->request->env;

    die "Must have streaming" if ! $env->{'psgi.streaming'};
    die "Must provide content_type\n" unless $options{content_type};
    die "Must provide filename\n" unless $options{filename};
    binmode $fh;

    my $response = Dancer::SharedData->response() ||
Dancer::Response->new();
    $response->header('Content-Type' => $options{content_type});
    $response->push_header('Content-Disposition' =>
        "attachment; filename=\"$options{filename}\""
    );

    $response->streamed( sub {
            my ( $status, $headers ) = @_;

            return sub {
                my $respond = shift;

                my $writer = $respond->( [
                    $status,
                    $headers,
                ] );

                my $content = $response->content;

                my $bytes = $options{'bytes'} || '43008';
                my $buf;

                while ( ( my $read = sysread $fh, $buf, $bytes ) != 0 ) {
                    $writer->write($buf);
                }
            };
        }
    );
    return $response if $response;
}
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.preshweb.co.uk/pipermail/dancer-users/attachments/20130509/b2e4251e/attachment.htm>


More information about the dancer-users mailing list