This article needs a technical review. How you can help.
nsIHttpChannel
.
nsISupports
Last changed in Gecko 1.9.0.4 The typical way to use this interface is as follows:
- Register for the "http-on-examine-response" notification to track all HTTP responses;
- Skip redirects (
responseStatus
= 3xx on nsIHttpChannel), since otherwise you may end up with two listeners registered for a channel; - QI the channel passed as the "
subject
" to your observer tonsITraceableChannel
, and replace the defaultnsIStreamListener
(that passes the data to the original requester - e.g. to XMLHttpRequest or to the browser tab that made the request) with your own implementation (see "Implementing nsIStreamListener" below).
After that your nsIStreamListener
implementation will get the response data and will be able to pass the data on to the original nsIStreamListener (possibly modifying it).
See nsITraceableChannel, Intercept HTTP Traffic for a more detailed description with code samples.
See Modify URL before loading page in firefox for an overview of how to modify a request before it is made.
Method overview
nsIStreamListener setNewListener(in nsIStreamListener aListener); |
Methods
setNewListener()
Replaces the channel's current listener with a new one, returning the listener previously assigned to the channel.
nsIStreamListener setNewListener( in nsIStreamListener aListener );
Parameters
aListener
- An
nsIStreamListener
to be notified of events on the HTTP channel.
Return value
The previous listener for the channel. Each listener call through to the previous listener for every call, in order to establish a call chain to allow all interested parties a chance to act on each event.
Implementing nsIStreamListener
The nsIStreamListener
passed to setNewListener()
should implement the following methods, which are called to notify it of events that occur on the HTTP stream:
onStartRequest
: An HTTP request is beginning.onDataAvailable
: Data is arriving on the HTTP channel.onStopRequest
: The HTTP request is ending.
onStartRequest
.Channels may restrict when you may replace the listener. In particular, listeners typically should not be replaced after onStartRequest
has been called.
Example
This example can be copied and pasted into Scratchpad and run with "Environment" menu item set to "Browser". This uses Promise syntax that is available in Firefox 30 and onwards.
/////// START - DO NOT EDIT var {classes: Cc, interfaces: Ci, results: Cr, Constructor: CC, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); var BinaryInputStream = CC('@mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream'); var BinaryOutputStream = CC('@mozilla.org/binaryoutputstream;1', 'nsIBinaryOutputStream', 'setOutputStream'); var StorageStream = CC('@mozilla.org/storagestream;1', 'nsIStorageStream', 'init'); function TracingListener() { this.receivedChunks = []; // array for incoming data. holds chunks as they come, onStopRequest we join these junks to get the full source this.responseBody; // we'll set this to the this.responseStatusCode; this.deferredDone = { promise: null, resolve: null, reject: null }; this.deferredDone.promise = new Promise(function(resolve, reject) { this.resolve = resolve; this.reject = reject; }.bind(this.deferredDone)); Object.freeze(this.deferredDone); this.promiseDone = this.deferredDone.promise; } TracingListener.prototype = { onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { var iStream = new BinaryInputStream(aInputStream) // binaryaInputStream var sStream = new StorageStream(8192, aCount, null); // storageStream // not sure why its 8192 but thats how eveyrone is doing it, we should ask why var oStream = new BinaryOutputStream(sStream.getOutputStream(0)); // binaryOutputStream // Copy received data as they come. var data = iStream.readBytes(aCount); this.receivedChunks.push(data); oStream.writeBytes(data, aCount); this.originalListener.onDataAvailable(aRequest, aContext, sStream.newInputStream(0), aOffset, aCount); }, onStartRequest: function(aRequest, aContext) { this.originalListener.onStartRequest(aRequest, aContext); }, onStopRequest: function(aRequest, aContext, aStatusCode) { this.responseBody = this.receivedChunks.join(""); delete this.receivedChunks; this.responseStatus = aStatusCode; this.originalListener.onStopRequest(aRequest, aContext, aStatusCode); this.deferredDone.resolve(); }, QueryInterface: function(aIID) { if (aIID.equals(Ci.nsIStreamListener) || aIID.equals(Ci.nsISupports)) { return this; } throw Cr.NS_NOINTERFACE; } }; var httpResponseObserver = { observe: function(aSubject, aTopic, aData) { var newListener = new TracingListener(); aSubject.QueryInterface(Ci.nsITraceableChannel); newListener.originalListener = aSubject.setNewListener(newListener); /////// END - DO NOT EDIT newListener.promiseDone.then( function() { // no error happened console.log('yay response done:', newListener.responseBody); }, function(aReason) { // promise was rejected, right now i didnt set up rejection, but i should listen to on abort or bade status code then reject maybe } ).catch( function(aCatch) { console.error('something went wrong, a typo by dev probably:', aCatch); } ); } }; Services.obs.addObserver(httpResponseObserver, 'http-on-examine-response', false); // Services.obs.removeObserver(httpResponseObserver, 'http-on-examine-response'); // call this when you dont want to listen anymore