cip
asked on
Byte serving from ASP
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("Conten t-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(ostre am.Read(by tes_to_rea d));
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(filep ath)
var boundary = "";
var etag = '"f094b12d4e31c51:992"';
var ipos = (""+Request.ServerVariable s('HTTP_RA NGE')).ind exOf('byte s=')
ranges = new Array();
if (Request.ServerVariables(' REQUEST_ME THOD')=='G ET' && !isEmpty(Request.ServerVar iables('HT TP_RANGE') ) && (ipos >= 0))
{
range = (""+Request.ServerVariable s('HTTP_RA NGE')).sub str(ipos+6 );
boundary = '[lka9uw3et5vxybtp87ghq23d pu7djv84nh ls9p]';//s et 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").leng th;
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("Conten t-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("Cont ent-type: application/pdf\r\n");
Response.BinaryWrite("Cont ent-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("Conten t-Length", (rangeobj.last - rangeobj.first));
Response.addHeader("Conten t-Range", "bytes " + rangeobj.first + "-" + rangeobj.last + "/" + filesize);
Response.addHeader("Conten t-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("Conten t-Length", filesize);
Response.addHeader("Conten t-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("Scrip ting.fileS ystemObjec t");
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?
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("Conten
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(ostre
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
ostream.Open
ostream.Type = adTypeBinary
ostream.LoadFromFile(filep
var boundary = "";
var etag = '"f094b12d4e31c51:992"';
var ipos = (""+Request.ServerVariable
ranges = new Array();
if (Request.ServerVariables('
{
range = (""+Request.ServerVariable
boundary = '[lka9uw3et5vxybtp87ghq23d
ranges = range.split(",")
}
if (ranges.length > 0)
{
Response.status = "206 Partial content";
Response.CacheControl = "No-store"
Response.addHeader("Accept
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").leng
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("Conten
Response.addHeader("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
Response.BinaryWrite("Cont
Response.BinaryWrite("Cont
Response.Flush();
ostream.position = rangeobj.first;
buffered_read(ostream, rangeobj.last - rangeobj.first + 1);
}
Response.BinaryWrite("\r\n
Response.Flush();
} else {
/*
A single range is requested.
*/
range = ranges[0];
rangeobj = set_range(range, filesize);
Response.addHeader("Conten
Response.addHeader("Conten
Response.addHeader("Conten
ostream.position(rangeobj.
buffered_read(ostream, rangeobj.last - rangeobj.first + 1);
}
}
else
{
//no byteserving
Response.addHeader("Accept
Response.addHeader("Conten
Response.addHeader("Conten
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("Scrip
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?
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
ASKER
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.
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.
yes - I just need to find it again....
Yes, I think half the problem is that this part (and similar with quoted text):
Response.BinaryWrite("\r\n --" + boundary + "\r\n");
Response.BinaryWrite("Cont ent-type: application/pdf\r\n");
Response.BinaryWrite("Cont ent-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
Response.BinaryWrite("\r\n
Response.BinaryWrite("Cont
Response.BinaryWrite("Cont
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
https://www.experts-exchange.com/questions/21099501/Simple-Wscript-Shell-question.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.
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.
ASKER
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.Re ad)
}
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?
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.
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.Re
}
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?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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!
Lemme do another another couple of checks then I'll award the points. Thanks!
ASKER
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.