Sending large files to a client in Padarn

A customer recently contacted me with a Padarn Web Server problem.  They wanted users to be able to download data files from their device to a client browser, but they were running out of device memory when the file got large (38MB in their case, but the size would be completely hardware and environment dependent).

What you have to understand is that Padarn caches your Page’s response in memory until such time as you either tell it to Flush explicitly, or until the Page has full rendered (in Padarn’s view) at which point it will call Flush for you.

For a large file, it’s going to be bad to try to hold the entire thing in memory before pushing it across the wire, so the strategy would be to read the local file in packets, then send those packets across to the client using Response.BinaryWrite.

Now BinaryWrite simply puts the data into the memory cache, so you have to actually tell Padarn to send it across the wire by calling Flush.  The issue there, though, is that on the first Flush, Padarn has to assemble and send the payload header to the client, and part of that header is the total length of the content.  If you haven’t explicitly set the content length, Padarn has no idea how big it might be, so it defaults to the length what it has in the buffer, and sends the header.  The browser then decodes that header and expects a length of only that first packet.

The solution is to set the Response.ContentLength property before you send any data.  Padarn will use your override value when it sends it to the client, and all will be well.

Here’s a quick example of a page that I used to send a 1.1GB media file from a page request with no problem.  You might want to tune the packet size based on device memory and speed, but 64k worked pretty well for my test.

class LargeFileDownload : Page
{
    protected override void Render(HtmlTextWriter writer)
    {
        var largeFilePath = @"D:MediaMoviesshort_circuit.mp4";

        var packetSize = 0x10000; // 64k packets

        using(var stream = new FileStream(
            largeFilePath,
            FileMode.Open,
            FileAccess.Read))
        {
            var toSend = stream.Length;
            var packet = new byte[packetSize];

            // set the response content length
            Response.ContentLength = toSend;
            // set the content type as a binary stream
            Response.ContentType = "application/octet-stream";
            // set the filename so the browser doesn't try to render it
            Response.AppendHeader("Content-Disposition",
                string.Format("attachment; filename={0}",
                Path.GetFileName(largeFilePath)));

            // send the content in packets
            while (toSend > 0)
            {
                var actual = stream.Read(packet, 0, packetSize);

                if (!Response.IsClientConnected)
                {
                    Debug.WriteLine(
                        "Client disconnected.  Aborting file send.");
                    return;
                }
                else
                {
                    Debug.WriteLine(
                        string.Format("Sending {0} bytes. {1} to go.",
                        actual, toSend));
                }

                if (actual == packetSize)
                {
                    Response.BinaryWrite(packet);
                }
                else
                {
                    // partial packet, so crop
                    var last = new byte[actual];
                    Array.Copy(packet, last, actual);
                    Response.BinaryWrite(packet);
                }

                // send the packet acrtoss the wire
                Response.Flush();

                toSend -= actual;
            }
        }
    }
}

1 thought on “Sending large files to a client in Padarn”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s