Search code examples
javascriptnode.jsfakeweb

Overwriting http.request with EventEmitter


I'm using the fakeweb module which overwrites Node's http.request with the following function:

var old_request = http.request;

http.request = function(options, callback){
    var rule = match_rule(options);
    if(rule){
        var res = new events.EventEmitter();
        res.headers = rule.headers || {'Content-Type': 'text/html'};
                return {end: function(){ 
            callback(res);
            res.emit('data', rule.body || '');
            res.emit('end');
            } 
        };
    } else {
        return old_request.call(http, options, callback);
    }
};

My issue is that I get the bug: TypeError: Object #<Object> has no method 'on' from the following code in another file:

var req = proto.request(options, function (res) { ... });
req.on('error', function (err) {
        err.success = false;
        settings.complete(err);
    });

I think my issue occurs because it's no longer an EventEmitter, though I may be wrong. How can I successfully overwrite http.request without getting this issue?

Background: I'm using Node version 0.8.2. The request NPM is version 2.12.0

Update (Feb 11th, 2013): I want to provide some more info on where the http.request is being called so I can be more specific about what's needed and what causes bugs. Here is where it's called:

var proto = (options.protocol === 'https:') ? https : http;              
var req = proto.request(options, function (res) {
    var output = new StringStream();                
    switch (res.headers['content-encoding']) {
        case 'gzip':
            res.pipe(zlib.createGunzip()).pipe(output);
            break;
        case 'deflate':
            res.pipe(zlib.createInflate()).pipe(output);
            break;
        default:
            // Treat as uncompressed string
            res.pipe(output);
            break;
    }

    output.on('end', function() {
        if (settings.cookieJar && res.headers['set-cookie']) {
            var cookies = res.headers['set-cookie'];
            for (var i = 0; i < cookies.length; i++) {
                settings.cookieJar.set(cookies[i]);
            }
        }

        if (res.statusCode >= 300 && res.statusCode < 400) {
            if (settings.maxRedirects > settings.nRedirects++) {
                // Follow redirect                                            
                var baseUrl = options.protocol + '//' + ((proxy) ? options.headers['host'] : options.host),
                    location = url.resolve(baseUrl, res.headers['location']);                    
                request(location, settings);                                   
            } else {
                var err = new Error('Max redirects reached.');
                err.success = false;
                settings.complete(err); 
            }
        } else if (res.statusCode >= 400) {
            var err = new Error(output.caught);
            err.success = false;
            err.code = res.statusCode;
            settings.complete(err);            
        } else {                                
            settings.complete(null, output.caught, res);
        }
    });
});
req.on('error', function (err) {
    err.success = false;
    settings.complete(err);
});

if (data) { req.write(data); }

req.end();

Solution

  • EDIT

    If you want request to act the exact same, just without the app actually hitting the server of the request then try using nock which will intercept requests you define and return data that you define.



    Original Answer:

    I think what you are looking for is something more like this:

    var old_request = http.request;
    
    http.request = function(options, callback){
        var rule = match_rule(options);
        if(rule){
            var res = new events.EventEmitter(),
                req = new events.EventEmitter();
            res.headers = rule.headers || {'Content-Type': 'text/html'};
            req.end = function(){ 
                if(callback) callback(res);
                res.emit('data', rule.body || '');
                res.emit('end'); 
            };
            return req;
        } else {
            return old_request.call(http, options, callback);
        }
    };