Trace HTTP response’s content in your XUL app

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

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>