views:

57

answers:

2

So I asked a question a while back about securing downloads using C# (http://stackoverflow.com/questions/2178984/securing-large-downloads-using-c-and-iis-7) and I got some great advice on how to do it (involving reading the file into memory and then writing it to the user). The only problem is, now that I'm trying to implement some basic logging, I'm hitting a brick wall. Here's the code to stream a file:

public void StreamFile(string file_path)
{
    DateTime start;
    TimeSpan ts;
    FileStream fstream;
    string filename = Path.GetFileName(file_path);
    byte[] buffer = new byte[STREAM_BUFFER_SIZE];
    int count = 1, total = 0, seconds;

    // Open the file to read
    fstream = new FileStream("D:\\" + file_path, FileMode.Open, FileAccess.Read);

    // Set up the response headers
    Response.AddHeader("Content-Length", fstream.Length.ToString());
    Response.AddHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
    Response.ContentType = "application/octet-stream";

    // If the user agent is Internet Explorer, we add one last header
    if (Request.UserAgent.Contains("MSIE"))
        Response.AddHeader("Content-Transfer-Encoding", "binary");

    // Start counting the time
    start = DateTime.Now;

    // Now, until the client disconnects, we stream the file
    while (Response.IsClientConnected)
    {
        // Read the file into the buffer
        count = fstream.Read(buffer, 0, buffer.Length);

        // If the buffer is empty, break out of the loop. We're done!
        if (count == 0)
            break;

        // Write to the output stream and push it to the user
        Response.OutputStream.Write(buffer, 0, count);
        Response.Flush();

        // Increment the total as well, this way we can know how much we've streamed
        total += count;
    }
    // The transfer is done! Close the connection.
    Response.Close();

    // Count the number of seconds
    ts = DateTime.Now - start;
    seconds = ts.Seconds + (60 * ts.Minutes) + (60 * 60 * ts.Hours); // Also, is there a better way to do this? This is laaaaaame!

    // Finally, log the transfer
    Logging.AddLog(Request["user"], file_path, total, count == 0, seconds);
}

Ok, so the problem is, the log entries that this is creating say that the file completed in the number of seconds it took to read the file into memory, not for the user to download it. I assumend that Response.ClientConnected would take care of that, but apparently not. So the time to download the file as reported by the logs is 0-1 seconds, and even when I stop a download part way through, the logs say it provided the whole thing.

Anyone done anything like this before, or any ideas how I can get the REAL numbers behind the transfer? Having this information is a huge priority, unfortunately, or I'd just shrug it off and remove those two values from the logging altogether.

+1  A: 

I'm sorry, but it's probably not possible to do what you want to do. After you call Write() (on the NetworkStream), the data is immediately given to Windows' Sockets, which in turn writes the data to the buffer of the network card. So, .Net doesn't do any buffering and Write returns immediately. Because a NetworkStream is not buffered by .Net, calling Flush() has no effect. Therefore, from .Net it is not possible to wait until the data has left the network card's buffer.

Even if you, somehow, managed to wait exactly until the data has left the NIC's buffer, there is no guarantee that the client ever received the data.

Virtlink
Hmm, this will be my accepted unless someone else can come up with a way to do this. Heck, maybe I'll just have to resort to parsing IIS logs to get what I need.
Sheep Slapper
A: 

The solution is to have your client application send a "Received" confirmation packet back once it has the whole file. Once you receive that, you know they actually received the whole thing, and can stop the timer.

MikeP
There is no 'client application' other than a standard web browser, otherwise the logging would happen on the clients side, where the information would be more accurate.
Sheep Slapper