<div dir="ltr">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.<div>
<br></div><div>This was easy enough to accomplish with open and &#39;-|&#39; and then</div><div><br></div><div style>exec (&#39;/path/to/tar&#39;, &#39;-c&#39;, &#39;-f&#39;, &#39;-&#39; ...);</div><div style><br></div><div style>
in the child.</div><div style><br></div><div style>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 &quot;* Excess found in a non pipelined read: excess = 7989&quot;.</div>
<div style><br></div><div style>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:</div><div style><br></div>
<div style>$response-&gt;{headers}-&gt;remove_header(&#39;Content-Length&#39;);</div><div style><br></div><div style>but was unable to remove it, and I&#39;m not sure that&#39;s what I want to do anyway.  Hijacking the stream and just catting binary data doesn&#39;t seem to be part of the spec, though I&#39;d swear I&#39;ve seen it done elswhere before, but I thought I would pick the collective brains of the mailing list and see.</div>
<div style><div><br></div><div style>I eventually ended up just using File::Temp::tmpnam and having tar output to that file, which I then added to a var called &#39;cleanup&#39; that an after hook deletes.  Then I sent with file_send as normal.</div>
<div style><br></div><div style>So, I suppose my main question is: would this work if I were somehow able to remove the Content-Length header?  I&#39;m thinking not and, if not, should I be using chunked transfer instead?  Does Dancer support this?</div>
<div style><br></div><div style>CODE:</div><div><br></div><div>sub send_data {</div><div>    Dancer::Continuation::Route::FileSent-&gt;new(</div><div>        return_value =&gt; _send_data(@_)</div><div>    )-&gt;throw</div>
<div>}</div><div><br></div></div><div style><div>sub _send_data {</div><div>    my ($fh, %options) = @_;</div><div>    my $env = Dancer::SharedData-&gt;request-&gt;env;</div><div><br></div><div>    die &quot;Must have streaming&quot; if ! $env-&gt;{&#39;psgi.streaming&#39;};</div>
<div>    die &quot;Must provide content_type\n&quot; unless $options{content_type};</div><div>    die &quot;Must provide filename\n&quot; unless $options{filename};</div><div>    binmode $fh;<br></div><div><br></div><div>
    my $response = Dancer::SharedData-&gt;response() || Dancer::Response-&gt;new();</div><div>    $response-&gt;header(&#39;Content-Type&#39; =&gt; $options{content_type});</div><div>    $response-&gt;push_header(&#39;Content-Disposition&#39; =&gt;</div>
<div>        &quot;attachment; filename=\&quot;$options{filename}\&quot;&quot;</div><div>    );</div><div><br></div><div>    $response-&gt;streamed( sub {</div><div>            my ( $status, $headers ) = @_;</div><div><br>
</div><div>            return sub {</div><div>                my $respond = shift;</div><div><br></div><div>                my $writer = $respond-&gt;( [</div><div>                    $status,</div><div>                    $headers,</div>
<div>                ] );</div><div><br></div><div>                my $content = $response-&gt;content;</div><div><br></div><div>                my $bytes = $options{&#39;bytes&#39;} || &#39;43008&#39;;</div><div>                my $buf;</div>
<div><br></div><div>                while ( ( my $read = sysread $fh, $buf, $bytes ) != 0 ) {</div><div>                    $writer-&gt;write($buf);</div><div>                }</div><div>            };</div><div>        }</div>
<div>    );</div><div>    return $response if $response;</div><div>}</div></div></div>