?
Solved

Byte serving from ASP

Posted on 2005-03-29
9
Medium Priority
?
1,795 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Question has a verified solution.

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

Have you ever needed to get an ASP script to wait for a while? I have, just to let something else happen. Or in my case, to allow other stuff to happen while I was murdering my MySQL database with an update. The Original Issue This was written…
I was asked about the differences between classic ASP and ASP.NET, so let me put them down here, for reference: Let's make the introductions... Classic ASP was launched by Microsoft in 1998 and dynamically generate web pages upon user interact…
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …
In this video, Percona Solutions Engineer Barrett Chambers discusses some of the basic syntax differences between MySQL and MongoDB. To learn more check out our webinar on MongoDB administration for MySQL DBA: https://www.percona.com/resources/we…
Suggested Courses

765 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