2

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();
Ben G
  • 26,091
  • 34
  • 103
  • 170
  • the return value in the `if(rule){` block isn't an EventEmitter – Chad Feb 09 '13 at 03:34
  • Yes, how could i return an EventEmitter here properly? – Ben G Feb 10 '13 at 21:41
  • Depends on what you want to have emitted. You are supposed to call `req.end()` and you will get a `data` event then an `end` event. That is how the library works. Since no call is ever made, you will never get an error. You really need to explain your end goal here. – Chad Feb 11 '13 at 01:32

1 Answers1

0

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);
    }
};
Chad
  • 19,219
  • 4
  • 50
  • 73
  • Hey man, once I make this change I get a new error: `TypeError: Object # has no method 'pipe'` caused by `var req = http.request(options, function (res) { res.pipe(output); });` – Ben G Feb 11 '13 at 20:39
  • that is because `req` is normally a writable stream, and not just an `EventEmitter`; like I said before I can't help you without knowing what your end-goal is here I'm just guessing. Tell me what you want and I can help you, but this sounds like an issue you should be posting on fakeweb if that is what you are using. – Chad Feb 12 '13 at 00:00
  • I'm trying to get Fakeweb to work Node, by creating a fake http.request that looks the same to Node. That's my only goal. I thought that maybe a few minor hacks like the one you've suggested already would get it "quacking" like the old http.request duck. – Ben G Feb 12 '13 at 00:09
  • It probably won't be a few minor hacks. Let me ask again more specifically, if you call `req.pipe()` on the fakeweb one, what do you expect to happen? There is no real request to pipe to so where does the data go? If you just return a writable stream with no target, will you really get the events you need? This isn't really something I can type out in a snippet, it is a big endeavor. Try using a better module like [nock](https://github.com/flatiron/nock) instead – Chad Feb 12 '13 at 00:28
  • Chad, first off thanks for staying on this. I realize I didn't provide enough info, so I pasted the code snippet where http.request is actually used to provide context and help with things. Let me know if that clarifies how I need to use http.request. Thanks. – Ben G Feb 12 '13 at 00:35
  • I think you best bet is not to "mock out" request, but to intercept the HTTP request themselves and control the return value. Look at [nock](https://github.com/flatiron/nock) I think it is exactly what you want. – Chad Feb 12 '13 at 00:39
  • Thanks, I marked your question as correct and switched over to nock. Still having some issues, maybe you can help with: http://stackoverflow.com/questions/14843019/node-http-request-does-nothing – Ben G Feb 12 '13 at 22:27
  • @babonk Sure, answered there as well with some working code.\ – Chad Feb 12 '13 at 22:43