Sometimes you need HTTP requests tracer in your application. There are many pieces of code scattered over the internet, but how to join all of them together?
Idea of this method is stream listener substitution during http-examine-response event. When all content read we just send our own event http-on-examine-response-content. To utilize this method your object should implement nsIObserver interface. Also you should subscribe to http-on-examine-response-content event using @mozilla.org/observer-service;1 service. And of course, you might be interested in http-examine-response and http-examine-request events (just subscribe to them via observer service).
Usage example
let MyCoolObject = {
downloadedSize: 0,
observe: function (aChannel, aTopic, aData) {
switch (aTopic) {
case "http-on-examine-response-content":
// counting all responses size, for example
this.downloadedSize += aData.length;
break;
}
},
};
let ObserverService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
ObserverService.addObserver(MyCoolObject, "http-on-examine-response-content", false);
Component source code
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const CC = Components.classes,
CI = Components.interfaces,
ObserverService = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
function HttpRequestObserver () {
}
HttpRequestObserver.prototype = {
classID: Components.ID('{51b8262e-e556-4b41-b530-9145c61a38fa}'),
observe: function (subject, topic, data) {
switch (topic) {
case "profile-after-change":
this._register();
break;
case "http-on-examine-response":
let interceptor = new StreamInterceptor(subject);
interceptor.originalListener = subject.QueryInterface(CI.nsITraceableChannel).setNewListener(interceptor);
break;
}
},
QueryInterface: XPCOMUtils.generateQI([CI.nsIObserver, CI.nsISupports]),
_register: function () {
ObserverService.addObserver(this, "http-on-examine-response", false);
},
};
function StreamInterceptor (aSubject) {
this.subject = aSubject;
this.interceptedData = [];
}
StreamInterceptor.prototype = {
originalListener: null,
subject: null,
onDataAvailable: function (request, context, inputStream, offset, count) {
let binaryInputStream = CC["@mozilla.org/binaryinputstream;1"].createInstance(CI.nsIBinaryInputStream),
storageStream = CC["@mozilla.org/storagestream;1"].createInstance(CI.nsIStorageStream),
binaryOutputStream = CC["@mozilla.org/binaryoutputstream;1"].createInstance(CI.nsIBinaryOutputStream),
data;
binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
data = binaryInputStream.readBytes(count);
this.interceptedData.push(data);
binaryOutputStream.writeBytes(data, count);
try { // XXX see moz-bug 492534
this.originalListener.onDataAvailable(request, context, storageStream.newInputStream(0), offset, count);
} catch (e) {
request.cancel(e.result);
}
},
onStartRequest: function (request, context) {
try { // XXX see moz-bug 492534
this.originalListener.onStartRequest(request, context);
} catch (e) {
request.cancel(e.result);
}
},
onStopRequest: function (request, context, statusCode) {
ObserverService.notifyObservers(this.subject, "http-on-examine-response-content", this.interceptedData.join());
this.originalListener.onStopRequest(request, context, statusCode);
},
QueryInterface: XPCOMUtils.generateQI([CI.nsIStreamListener, CI.nsISupports]),
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([HttpRequestObserver]);
Don’t forget to register your component in chrome.manifest (check XPCOM changes in Gecko 2.0