Where magic lives

Monday, October 23, 2006

File Upload Status Bar for PHP

When you are using PHP and Apache and you upload a file to a PHP script using a multipart/form-data POST your PHP script typically does not start executing until all of the POST data has reached the server. This makes it impossible to provide server-side feedback to your clients about the status of their uploads.

One way around this problem is to have another process running on the server capable of reading the clients headers, passes all data received onto the actual web server whilst keeping count of how much data it has passed on and telling clients the current status of a given request. This however sounds easier than it actually it is when it comes around to implementation. Your server program has to understand quite a bit about HTTP (not a simple protocol in its full detail) and there are logging and security implications (suddenly your webserver thinks a lot of its requests are coming from 127.0.0.1 when they are not).

Anyway, I have created a "first draft" of such a program. It cheats a bit by forcing the Connection header of every HTTP request and response to Close so that it only has to deal with one request per connection (this should be fine if you only send file uploads through the proxy program). I attempt to handle the logging program by writing to standard output my own log in the same format as my Apache server does so that this output can be piped into the Apache log file (requests will then appear duplicated but at least you have a record of the actual end users who have used the server).

The solution is by no means complete, it works, and it is secure in the sense that it wont give a remote user any more that it "says on the tin" but I have not tested it under load and it is probably quite susceptible to denial of service attacks in its current state. I am releasing the Java source code hoping that people might make improvements and give feedback, use it at your own risk!

Usage instructions from the source code:

 /*
  * Command Line Usage:
  *  java UploadServer 81 that-server >> 
  *    /location/of/your/apache/access_log
  *  Where 81 is a spare TCP port to listen on, and 
  *  that-server is a server listening for web 
  *  connections on port 80.
  *
  * Remote usage:
  *  http://this-server:81/foo
  *  Will return http://that-server:80/foo (works with GET,
  *  POST, etc.)
  *  http://this-server:81/foo___STATS___ will return a string
  *  describing how many bytes have been transmitted in the 
  *  current users (based on IP address and Cookie contents) 
  *  request to http://this-server:81/foo and (if available) 
  *  how many bytes the server is expecting.
  */

Some JavaScript to display a status bar might work something like this (using the forms onsubmit attribute to call statusBar() and <div id="status"><h2>Upload status</h2><div id="statusinner">Starting upload...</div></div> somewhere in your HTML):

 function requestObject()
 {
  return ((navigator.appName == "Microsoft Internet Explorer") ? 
   new ActiveXObject("Microsoft.XMLHTTP") :  new XMLHttpRequest());
 }
 var statusconn = requestObject();
 var statusBarUri = '';
 function statusBar()
 {
  document.getElementById('status').style.visibility = "visible";
  startStatusBar();
 }
 function startStatusBar()
 {
  statusconn.open('get', 'http://this-server:81/foo___STATS___',
   true);
  statusconn.onreadystatechange = updateStatusBar;
  statusconn.send('');
 }
 function updateStatusBar()
 {
  if(statusconn.readyState == 4)
  {
   if(parseInt(statusconn.responseText)==-1)
   {
    document.getElementById('statusinner').innerHTML = 
     "(Status bar loading...)";
   }
   else
   {
    if(statusconn.responseText.indexOf('/')==-1)
    {
     document.getElementById('statusinner').innerHTML = "(" + 
      (Math.round(parseInt(statusconn.responseText)/1024)) + 
      "KB uploaded so far.)";
    }
    else
    {
     var bits = statusconn.responseText.split("/");
     document.getElementById('statusinner').innerHTML = "(" + 
      (Math.round(parseInt(bits[0])/1024)) + 
      "KB/" + (Math.round(parseInt(bits[1])/1024)) + 
      "KB uploaded so far.)";     
    }
   }
   setTimeout("startStatusBar()", 100);
  }
 }

Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]



<$I18N$LinksToThisPost>:

Create a Link

<< Home