Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
?
Solved

Byte serving from ASP

Posted on 2005-03-29
9
Medium Priority
?
1,835 Views
Last Modified: 2011-09-20
I'm trying to implement byte serving from ASP. Byte serving is that technique that allows an http client to receive file chunks instead than a whole, such as acrobat reader when it display the first pages way before the entire file has been downloaded.

This is what I have implemented, using ASP and Jscript (which can be easily converted to vbscript anyway):

var adTypeBinary = 1

function set_range(range, filesize){
  /*
  Sets the first and last bytes of a range, given a range expressed as a string
  and the size of the file.

  If the end of the range is not specified, or the end of the range is greater
  than the length of the file, $last is set as the end of the file.

  If the begining of the range is not specified, the meaning of the value after
  the dash is "get the last n bytes of the file".

  If $first is greater than $last, the range is not satisfiable, and we should
  return a response with a status of 416 (Requested range not satisfiable).

  Examples:
  $range='0-499', $filesize=1000 => $first=0, $last=499 .
  $range='500-', $filesize=1000 => $first=500, $last=999 .
  $range='500-1200', $filesize=1000 => $first=500, $last=999 .
  $range='-200', $filesize=1000 => $first=800, $last=999 .

  */
  var dash  = range.indexOf('-');
  var first = Trim(range.substr(0, dash))
  var last  = Trim(range.substr(dash+1));
  if (first=='') {
    //suffix byte range: gets last n bytes
    var suffix = last;
    last       = filesize-1;
    first      = filesize-parseInt(suffix);
    if(first<0) first = 0;
  }
  else
  {
    if (last=='' || parseInt(last) > filesize-1)
          last = filesize-1;
  }
 
  first = parseInt(first);
  last  = parseInt(last);
 
  if (first > last){
    //unsatisfiable range
    Response.Status("416 Requested range not satisfiable");
    Response.addHeader("Content-Range", "*/" + filesize);
    Response.end
  }
 
  var returnobj = new Object();
  returnobj.first = first;
  returnobj.last  = last;       
 
  return returnobj;
}

function buffered_read(ostream, bytes, buffer_size)
{
      if (!buffer_size)
            buffer_size = 1024;
            
  /*
  Outputs up to $bytes from the file $file to standard output, $buffer_size bytes at a time.
  */
  var bytes_left = bytes;
  while(bytes_left > 0 && !ostream.EOS)
  {
    if(bytes_left > buffer_size)
      bytes_to_read = buffer_size;
    else
      bytes_to_read = bytes_left;
   
    bytes_left -= bytes_to_read;
   
    Response.BinaryWrite(ostream.Read(bytes_to_read));
    Response.Flush();
  }
}

function byteserve(filepath, filesize){
  /*
  Byteserves the file $filename.  

  When there is a request for a single range, the content is transmitted
  with a Content-Range header, and a Content-Length header showing the number
  of bytes actually transferred.

  When there is a request for multiple ranges, these are transmitted as a
  multipart message. The multipart media type used for this purpose is
  "multipart/byteranges".
  */
 
      Response.Clear
      var ostream = Server.CreateObject("ADODB.Stream")
      ostream.Open       
      ostream.Type = adTypeBinary
      ostream.LoadFromFile(filepath)       
      
      var boundary = "";
      var etag = '"f094b12d4e31c51:992"';
      var ipos = (""+Request.ServerVariables('HTTP_RANGE')).indexOf('bytes=')            
      
  ranges = new Array();
  if (Request.ServerVariables('REQUEST_METHOD')=='GET' && !isEmpty(Request.ServerVariables('HTTP_RANGE')) && (ipos >= 0))
  {
        range    = (""+Request.ServerVariables('HTTP_RANGE')).substr(ipos+6);
    boundary = '[lka9uw3et5vxybtp87ghq23dpu7djv84nhls9p]';//set a random boundary
    ranges   = range.split(",")
  }
 
  if (ranges.length > 0)
  {
             Response.status = "206 Partial content";
             Response.CacheControl = "No-store"
         
    Response.addHeader("Accept-Ranges", "bytes");
    if (ranges.length > 1)
    {
      /*
      More than one range is requested.
      */

      //compute content length
      content_length = 0;
      for (var i=0; i < ranges.length; i++)
      {
        rangeobj = set_range(ranges[i], filesize);
       
        content_length += ("\r\n--" + boundary + "\r\n").length;
        content_length += ("Content-type: application/pdf\r\n").length;
        content_length += ("Content-range: bytes " + rangeobj.first + "-" + rangeobj.last + "/" + filesize + "\r\n\r\n").length;
        content_length += rangeobj.last - rangeobj.first + 1;          
      }
                  content_length += ("\r\n--" + boundary + "--\r\n").length;
                  
      //output headers
      Response.addHeader("Content-Length", ""+content_length);
      Response.addHeader("ETag", etag);
      //see http://httpd.apache.org/docs/misc/known_client_problems.html for an discussion of x-byteranges vs. byteranges
      Response.ContentType = "multipart/byteranges; boundary=" + boundary
     
      //output the content
      for (var i=0; i < ranges.length; i++)
      {
        rangeobj = set_range(ranges[i], filesize);        
        Response.BinaryWrite("\r\n--" + boundary + "\r\n");
        Response.BinaryWrite("Content-type: application/pdf\r\n");
        Response.BinaryWrite("Content-range: bytes " + rangeobj.first + "-" + rangeobj.last + "/" + filesize + "\r\n\r\n");
        Response.Flush();
        ostream.position = rangeobj.first;
        buffered_read(ostream, rangeobj.last - rangeobj.first + 1);
      }
      Response.BinaryWrite("\r\n--" + boundary + "--\r\n");
      Response.Flush();
    } else {
      /*
      A single range is requested.
      */
      range = ranges[0];
      rangeobj = set_range(range, filesize);  
      Response.addHeader("Content-Length", (rangeobj.last - rangeobj.first));
      Response.addHeader("Content-Range", "bytes " + rangeobj.first + "-" + rangeobj.last + "/" + filesize);
      Response.addHeader("Content-Type", "application/pdf");
      ostream.position(rangeobj.first);
      buffered_read(ostream, rangeobj.last - rangeobj.first + 1);
    }
  }
  else
  {
    //no byteserving
    Response.addHeader("Accept-Ranges", "bytes");
    Response.addHeader("Content-Length", filesize);
    Response.addHeader("Content-Type", "application/pdf");
            buffered_read(ostream, filesize);
  }
  ostream.Close();
  ostream = null;
}

You can test the code using these simple lines:

filepath = Server.MapPath([pdf file to download]);
      
fs = Server.CreateObject("Scripting.fileSystemObject");
f = fs.getFile(filepath);
byteserve(filepath, f.size)

I have translated this from the equivalent php code found here:

http://www.coneural.org/florian/papers/04_byteserving.php

The ASP routne doesn't work. The headers seems ok, but after that the web page freezes and stops loading.

Anybody willing to help?
0
Comment
Question by:cip
  • 5
  • 4
9 Comments
 
LVL 7

Expert Comment

by:wesbird
ID: 13653031
Could it be that response.binarywrite is sending out unicode - I think I've been here before, take a look at http://www.alleged.org.uk/pdc/2003/asp.html 
0
 
LVL 1

Author Comment

by:cip
ID: 13654160
Thanks wesbird.

Do you think there is any way to handle this without going to a component? I have tried to read all the Response.Binary stuff into variants from a text file using ado.stream binary mode, but that didn't work too.

0
 
LVL 7

Expert Comment

by:wesbird
ID: 13654307
yes - I just need to find it again....
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 7

Expert Comment

by:wesbird
ID: 13654373
Yes, I think half the problem is that this part (and similar with quoted text):

        Response.BinaryWrite("\r\n--" + boundary + "\r\n");
        Response.BinaryWrite("Content-type: application/pdf\r\n");
        Response.BinaryWrite("Content-range: bytes " + rangeobj.first + "-" + rangeobj.last + "/" + filesize + "\r\n\r\n");

will be pumped out as unicode unless you explicitly convert the strings to bytes using widechartomultibyte or take a look at this: http://support.microsoft.com/default.aspx?scid=KB;en-us;q232580

for further ideas...

will post again when I have more.

Wes
0
 
LVL 7

Expert Comment

by:wesbird
ID: 13654485
http://www.experts-exchange.com/Web/Web_Languages/ASP/Q_21099501.html contains a similar problem in reverse.

The solution I think is probably to convert the text strings using something like combination of chr(AscW(mid(...))) for rebuilding each character in the string for a quick inelegant solution just to make it work.  Take a look at my old thread in the link above for ideas.
0
 
LVL 1

Author Comment

by:cip
ID: 13660178
Hi wesbird,

I still cannot have it working. I am trying to keep it in javascript since I have to mix this code with lots of includes already developed.

I have tried to implement a binary streamed string function such as:

function stream_string(ss)
{
  for (i=0; i<ss.length; i++)
  {
     var b = (ss.charCodeAt(i) & 255)
     Response.BinaryWrite(b)
  }
}

But I get:

ASP 0106~Type Mismatch~An unhandled data type was encountered.

I have even tried:

function stream_string(ss)
{
  var os = Server.CreateObject(ADODB.Stream);
  os.Open;
  os.Type = 1;
  for (i=0; i<ss.length; i++)
  {
     var b = (ss.charCodeAt(i) & 255)
     os.write(b);
  }

  os.position = 0;
  Response.BinaryWrite(os.Read)
}

But now I get:

Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
(at the "os.write(b)" line)

Is there any possible way to get a byte array so that it can be passed to Response.BinaryWrite() or ADODB.Stream.Write() using Javascript?
0
 
LVL 7

Accepted Solution

by:
wesbird earned 2000 total points
ID: 13660375
As far as I am aware, MS JScript only works in Unicode (everything is built on the BSTR underlying data type) - so you'll have to do this VBScript if you want it to work, unless perhaps?... Just out of curiosity, what happens if you Response.Write the "text" parts as you would normally instead of Response.BinaryWrite - will they mix on the page?
0
 
LVL 1

Author Comment

by:cip
ID: 13662026
Using Response.write instead of Reposen.BinaryWrite works perfectly fine! I can't believe I didn't make this test myself...

Lemme do another another couple of checks then I'll award the points. Thanks!
0
 
LVL 1

Author Comment

by:cip
ID: 13662789
Thanks wesbird, you've been extremely useful. Actually, I don't really understand WHY it is working, but it does. Shouldn't Response.write() send unicode characters? Maybe the acrobat plugin is able to parse them.
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

I have helped a lot of people on EE with their coding sources and have enjoyed near about every minute of it. Sometimes it can get a little tedious but it is always a challenge and the one thing that I always say is:   The Exchange of informatio…
I would like to start this tip/trick by saying Thank You, to all who said that this could not be done, as it forced me to make sure that it could be accomplished. :) To start, I want to make sure everyone understands the importance of utilizing p…
Integration Management Part 2
The Relationships Diagram is a good way to get an overall view of what a database is keeping track of. It is also where relationships are defined. A relationship specifies how two tables connect to each other. As you build tables in Microsoft Ac…
Suggested Courses

571 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question