Padarn: Dynamic Authentication

Our Padarn Web Server largely uses a subset of the IIS object model, so for many features and capabilities how you do things in IIS is paralleled in Padarn. Authentication follows that rule, but with a couple exceptions.

For good old-fashioned, built-into-the-browser Basic or Digest authentication it follows the same steps. Set the Authentication in your app.config file and the user will get a browser-provided authentication popup.

But what if we want to do authentication ourselves against our own user store? In this case Padarn has a few “custom” extensions to make things easier.

The first mechanism is to add users to the underlying user cache when you start up the Padarn Server. Something along these lines:

var server = new WebServer();
server.Configuration.Authentication.Users.Add(
    new OpenNETCF.Web.Configuration.User { Name = "username", Password = "password" }
);

You could also hard-code the user identities right into the app.config file.

This isn’t all that friendly, though – at least in my thinking. Another, more robust, way is to use Padarn’s AuthenticationCallback. This allows you to send a delegate into Padarn and have it call your customer authentication algorithm every time it needs to do authentication. It’s slightly more complex, but really it’s still not too terrible, and it supports Basic or Digest authentication.

// somewhere in startup
var server = new WebServer();
server.Configuration.Authentication.AuthenticationCallback = VerifyUsername;

...
// then somewhere else - in this case in the same class
bool VerifyUsername(IAuthenticationCallbackInfo info)
{
    // no username is invalid
    if (string.IsNullOrEmpty(info.UserName)) return false;

    // first do a lookup of the password - this might come from a database, file, etc
    string password = GetPasswordForUser(info.UserName);
    if (password == null) return false;

    // determine the authentication type
    var basic = info as BasicAuthInfo;
    if (basic != null)
    {
        // we're using basic auth
        return (basic.Password == password);
    }

    // it wasn't basic, so it must be digest (the only two supported options)
    var digest = info as DigestAuthInfo;
    // let the DigestAuthInfo check the password for us (it's hashed)
    return digest.MatchCredentials(password);
}

Uploading Large Files to a Padarn Server

About a year ago I posted some code that showed how to use our Padarn Web Server to serve up large files from a device to a connected client browser. Late last week I got a request for the corollary operation – how do I send large file from a client browser to my Padarn server?  It’s pretty straightforward. First, you need to render a simple page that allows the user to select a file.  For simplicity I’ll show an HTML version, though you could just as easily use jQuery or whatever you like.

protected override void Render(HtmlTextWriter writer)
{
    writer
        .Html()
            .Body()
                .Form(t => t
                    [HtmlTextWriterAttribute.Id, "Form"]
                    // submit back to ourselves
                    ["action", string.Empty]
                    // use a POST method on submit
                    ["method", "post"]
                    // set the POST data mime encoding for a file
                    ["enctype", "multipart/form-data"]
                    )

                    .P()
                        .Text("File:")

                        // render a file input button
                        // this allows the user to browse for a file
                        .Tag(HtmlTextWriterTag.Input, t => t
                            [HtmlTextWriterAttribute.Type, "file"]
                            [HtmlTextWriterAttribute.Size, "80"]
                            [HtmlTextWriterAttribute.Id, "filename"]
                            [HtmlTextWriterAttribute.Name, "filename"])
                        .EndTag() // input

                    .EndTag() // p

                    // render a submit button
                    // when clicked it will send the file
                    .Tag(HtmlTextWriterTag.Input, t => t
                        [HtmlTextWriterAttribute.Type, "submit"]
                        [HtmlTextWriterAttribute.Id, "upload"]
                        [HtmlTextWriterAttribute.Name, "upload"]
                        [HtmlTextWriterAttribute.Value, "Upload File"])
                    .EndTag()

                .EndTag() // form
            .EndTag() // body
        .EndTag(); // html

}

This will get a page that looks like this (yes, it’s a bit ugly without CSS): Now we simply need to handle incoming file data. For simplicity I have the Form above just posting back to itself, so in the same page, in Page_Load, I handle incoming data:

Side Note: WordPress isn’t properly showing my Media Uploads, so if it’s  broken image below, I know about it and am working to resolve it

padarn_upload

protected void Page_Load(object sender, EventArgs e)
{
    if (Request.Files.Count > 0)
    {
        // NOTE: You may have to change the config file to allow large 
        // files. The maxRequestLength is in KB, so make sure it is in 
        // line with expectations.
        // For example, this allows up to 300MB files to be uploaded
        //    <httpRuntime
        //      maxRequestLength="300000"
        //      requestLengthDiskThreshold="256"
        //    />

        // this might take a while, set the session.timeout to 120 minutes
        var origTimeout = Session.Timeout;
        Session.Timeout = 120; // 120 minutes;

        string error = "";
        // file upload
        try
        {
            // make sure the target local folder exists
            var path = Path.GetFullPath(".\uploads");
            if(!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            // now save off the files
            for(int i = 0 ; i < Request.Files.Count ; i++)
            {
                path = Path.Combine(path, Request.Files[i].FileName);

                Request.Files[i].SaveAs(path);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            error = ex.Message;
        }

        // put the timeout back to what it was
        Session.Timeout = origTimeout;
    }
}

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;
            }
        }
    }
}