Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Saving binary data from nsIXmlHttpRequest

33 views
Skip to first unread message

Michel Gutierrez

unread,
Dec 7, 2006, 8:11:36 AM12/7/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


hi,

in an extension implemented in javascript, i would like to use the
nsIXmlHttpRequest object to query data from an url. based on the
returned content-type, i would like to either parse the returned file
and do some stuff, or save the binary data into a file.

i cannot find a way to save the binary data. the
@mozilla.org/network/file-output-stream;1 component seems not to
implement writeFrom method and i'm running out of ideas.

does anybody have some trick for doing so ?

thanks,
/mig


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFeBMItMkIv0/ruZgRAtC7AKCijiXKg0w/AYYCHALbhw/gAJt3JACffFVB
bleMcTaaNGl6wW1mBn8LQmI=
=N9Wn
-----END PGP SIGNATURE-----

Michel Gutierrez

unread,
Dec 7, 2006, 9:23:35 AM12/7/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


I found the solution to my problem using binaryinputstream and
binaryoutputstream components.


var channel=request.channel;
var stream=channel.open();
var fos =
Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
var length=request.getResponseHeader("content-length");
fos.init(file,0x02 | 0x08 | 0x20, 0644, 0);
var bistream = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
bistream.setInputStream(stream);
var bostream = Components.classes["@mozilla.org/binaryoutputstream;1"].
createInstance(Components.interfaces.nsIBinaryOutputStream);
bostream.setOutputStream(fos);
var n=0;
length=parseInt(length);
while(n<length) {
var ba=bistream.readByteArray(bistream.available());
bostream.writeByteArray(ba,ba.length);
n+=ba.length;
}
fos.flush();
fos.close();
stream.close();


Michel Gutierrez wrote:
>
> hi,
>
> in an extension implemented in javascript, i would like to use the
> nsIXmlHttpRequest object to query data from an url. based on the
> returned content-type, i would like to either parse the returned file
> and do some stuff, or save the binary data into a file.
>
> i cannot find a way to save the binary data. the
> @mozilla.org/network/file-output-stream;1 component seems not to
> implement writeFrom method and i'm running out of ideas.
>
> does anybody have some trick for doing so ?
>
> thanks,
> /mig
>
>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFeCPmtMkIv0/ruZgRAoCDAKCNCbAA+RSUEIQ6OCGMMO3oGHdDpACeLFut
EZy/9QU/5OqvFNpmfKHS4lI=
=/aSh
-----END PGP SIGNATURE-----

Boris Zbarsky

unread,
Dec 7, 2006, 11:21:28 AM12/7/06
to
Michel Gutierrez wrote:
> var stream=channel.open();

For what it's worth, this does a synchronous load. This is not supported by all
channels, and badly supported by most. Of course you may not care if you make
the UI unusable while your data loads...

-Boris

Michel Gutierrez

unread,
Dec 7, 2006, 11:50:36 AM12/7/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


>> var stream=channel.open();

> For what it's worth, this does a synchronous load. This is not
> supported by all channels, and badly supported by most. Of course you
> may not care if you make the UI unusable while your data loads...

that's right.

i am in asynchronous mode for starting the download, up to getting the
response headers, then i block while downloading the response content. i
don't like this but it is acceptable in my situation as the maximum size
if 20-30KB.
i may try to use channel.asyncOpen with a stream listener later on.

concerning the synchronous load not being always supported, i am only
using http requests through XmlHttpRequest. Is there any chance that
fails in some situations ?

/mig
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFeEZbtMkIv0/ruZgRAu2vAKCaUp9YX2iDlI7nFzkpDacPX/9FCwCgimBd
mYZwTVwweXSo703zQCo31vI=
=eyKC
-----END PGP SIGNATURE-----

Boris Zbarsky

unread,
Dec 7, 2006, 12:24:47 PM12/7/06
to
Michel Gutierrez wrote:
> i am in asynchronous mode for starting the download, up to getting the
> response headers, then i block while downloading the response content.

Er.. you mean you call open() on an already-open channel? Or something different?

> concerning the synchronous load not being always supported, i am only
> using http requests through XmlHttpRequest. Is there any chance that
> fails in some situations ?

That probably ought to be OK in new enough Gecko.

-Boris

Michel Gutierrez

unread,
Dec 7, 2006, 12:46:35 PM12/7/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

>> i am in asynchronous mode for starting the download, up to getting the


>> response headers, then i block while downloading the response content.
>
> Er.. you mean you call open() on an already-open channel? Or something
> different?

yes i was calling open from the onload callback of xmlhttprequest.
i throught xmlhttprequest.channel was there for getting the content only
and was not already opened. i get the http headers i need from
xmlhttprequest.getResponseHeader() prior to (re)opening the channel.

i am now trying to get the content in an asynchronous way. this means i
must attach a nsIStreamListener to the channel. you can pass the
listener when you call openAsync, but if the channel is already opened
and openAsync should not be called again, i don't see anything in the
interface to attach the listener afterwards.


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFeFN7tMkIv0/ruZgRAnQeAJ9cK4HvSKfyiU/Xl1XVhLKzGb5QuACgw9Hs
ffWScnJBVw12DmOo1+iLIuY=
=+gbq
-----END PGP SIGNATURE-----

Boris Zbarsky

unread,
Dec 7, 2006, 1:32:11 PM12/7/06
to
Michel Gutierrez wrote:
> yes i was calling open from the onload callback of xmlhttprequest.
> i throught xmlhttprequest.channel was there for getting the content only
> and was not already opened.

It gets opened when you call send(). If you're in onload, that means that it's
already finished loading and gotten closed. That's the only reason the open()
call is not throwing; I'm not sure whether it should in fact throw (ccing
.network on that).

What the channel _is_ there for is getting the response headers and configuring
the channel _before_ you call send().

So you're basically making two requests to the server, in case you care.

> i am now trying to get the content in an asynchronous way.

The point of XMLHttpRequest is to return the data as 1) a string and 2) a DOM.
Since you want neither one, why are you bothering with XMLHttpRequest? Just
create a channel and use it directly... In your OnStartRequest you can either
set up a pipe, stick the data into it, and then in OnStopRequest parse with a
DOMParser, or set up a stream to write to a file and write.

-Boris

-Boris

Michel Gutierrez

unread,
Dec 7, 2006, 2:34:55 PM12/7/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

> It gets opened when you call send(). If you're in onload, that means
> that it's already finished loading and gotten closed. That's the only
> reason the open() call is not throwing; I'm not sure whether it should
> in fact throw (ccing .network on that).
>
> What the channel _is_ there for is getting the response headers and
> configuring the channel _before_ you call send().
>
> So you're basically making two requests to the server, in case you care.

ok, i catch that. however, given a channel, as i need a nsIInputStream
to access the data, the only way i see to get this stream is through the
open function. i don't see a getInputStream-like method in nsIChannel.
how am i supposed to get this input stream ?

> The point of XMLHttpRequest is to return the data as 1) a string and 2)
> a DOM. Since you want neither one, why are you bothering with
> XMLHttpRequest?

just because i've always done that way and never realize creating the
channel by hand was possible. thanks to you, now i know :)


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFeGzftMkIv0/ruZgRAnzJAKCFvPc5PEEgfmhN3oDIVl4OBFQ+JgCgjP93
BM/asQdKRm4obIibGH0DuWo=
=PvgG
-----END PGP SIGNATURE-----

Boris Zbarsky

unread,
Dec 7, 2006, 10:27:47 PM12/7/06
to
Michel Gutierrez wrote:
> ok, i catch that. however, given a channel, as i need a nsIInputStream
> to access the data, the only way i see to get this stream is through the
> open function. i don't see a getInputStream-like method in nsIChannel.
> how am i supposed to get this input stream ?

asyncOpen, and then the stream is passed to your onDataAvailable whenever data
comes in is what I'd go with.

-Boris

Michel Gutierrez

unread,
Dec 10, 2006, 12:56:57 PM12/10/06
to
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


Boris, i followed your advice and implemented the solution using directly a channel in asynchronous mode.

For the records, here is my code in case that can help someone.

Many thanks for your help.


function StreamListener(fileContentType,filePath,callback,callbackArgs) {
this.fileContentType=fileContentType;
this.filePath=filePath;
this.callback=callback;
this.callbackArgs=callbackArgs;
}
StreamListener.prototype={
onStartRequest: function(request,context) {
this.httpChannel=request.QueryInterface(Components.interfaces.nsIHttpChannel);
this.responseStatus=this.httpChannel.responseStatus;
this.responseStatusText=this.httpChannel.responseStatusText;
this.contentType=this.httpChannel.getResponseHeader("content-type");
if(this.contentType==this.fileContentType) {
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(this.filePath);


var fos = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);

fos.init(file,0x02 | 0x08 | 0x20, 0644, 0);

this.outputStream = Components.classes["@mozilla.org/binaryoutputstream;1"].
createInstance(Components.interfaces.nsIBinaryOutputStream);
this.outputStream.setOutputStream(fos);

this.type="file";
} else if(this.contentType=="text/xml") {
this.pipe=Components.classes["@mozilla.org/pipe;1"].
createInstance(Components.interfaces.nsIPipe);
this.pipe.init(true,false,1024,10,null);
this.outputStream = Components.classes["@mozilla.org/binaryoutputstream;1"].
createInstance(Components.interfaces.nsIBinaryOutputStream);
this.outputStream.setOutputStream(this.pipe.outputStream);
this.type="xml";
}
},
onDataAvailable: function(request,context,inputStream,offset,count) {
if(this.outputStream!=null) {


var bistream = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);

bistream.setInputStream(inputStream);
var n=0;
while(n<count) {
var ba=bistream.readByteArray(bistream.available());
this.outputStream.writeByteArray(ba,ba.length);
n+=ba.length;
}
}
},
onStopRequest: function(request,context,nsresult) {
var status=false;
if(this.responseStatus==200) {
if(this.type=="file") {
status=true;
} else if(this.type=="xml") {
var parser=Components.classes["@mozilla.org/xmlextras/domparser;1"].
createInstance(Components.interfaces.nsIDOMParser);
this.xmlDoc=parser.parseFromStream(this.pipe.inputStream,null,this.pipe.inputStream.available(),this.contentType);
}
}
if(this.callback) {
try {
this.callback(status,this.callbackArgs,this);
} catch(e) { dump("Callback exception: "+e+"\n"); }
}
this.outputStream.close();
}
}

var cb=function(status,args,listener) {
if(status) {
// do stuff on success
} else {
// do stuff on failure
}
}

/* url is the address where to get the file or xml document */
/* file is the nsILocalFile where to store the downloaded data */

var ioService = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var channel=ioService.newChannel(url,'',null);
channel.asyncOpen(new StreamListener("application/x-pokereval-refset",file.path,cb,{}),null);


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFfEpptMkIv0/ruZgRAsQBAJ4gIbhPkwdLEqTDSXlaned9OhUX/QCfbNkj
bOFRR7WwDQzMFXhq3MCjHf8=
=XZRy
-----END PGP SIGNATURE-----

Doug Turner

unread,
Dec 12, 2006, 3:47:29 PM12/12/06
to Michel Gutierrez
fwiw, if you wanted to do this without access to xpconnect (like in web
content), you could do something like this:

xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", gImageSource, true);
xmlhttp.overrideMimeType('text/plain; charset=x-user-defined');

Results may vary. This was coded up at by Marcus Granado 2006
[http://mgran.blogspot.com]

Then in your onreadystatechange do something like:

var data = xmlhttp.responseText;

var bytes = [];
for (var i=0; i<data.length; i++) {
var c = data.charCodeAt(i);
bytes.push((c > 255) ? c - 63232 : c);
}

At this point, your data will be in the bytes var.

This isn't the right newsgroup, but we should have something like
xmlhttp.rawData or something which contains a byte array of the actual
returned content. I am sure that has been debated in other places.

Doug

0 new messages