Skip to content

Commit

Permalink
last updates
Browse files Browse the repository at this point in the history
  • Loading branch information
tunnckoCore committed Apr 27, 2015
1 parent 51594b5 commit 65a8203
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 24 deletions.
58 changes: 35 additions & 23 deletions index.js
Expand Up @@ -17,12 +17,12 @@ var kindOf = require('kind-of');
var statuses = require('statuses');
var prependHttp = require('prepend-http');
var readAllStream = require('read-all-stream');
var objectAssign = require('object-assign');
var lowercase = require('lowercase-keys');
var redirects = 0;

module.exports = tosck;

var redirects = 0;

function tosck(address, opts, callback) {
if (kindOf(address) !== 'string') {
throw new TypeError('[tosck] expect `address` be string');
Expand All @@ -39,6 +39,11 @@ function tosck(address, opts, callback) {
var followRedirects = kindOf(opts.followRedirects) === 'boolean';

opts = kindOf(opts) === 'object' ? opts : {};

if (opts.body && !(kindOf(opts.body) === 'string' || kindOf(opts.body) === 'buffer')) {
throw new TypeError('[tosck] opts.body can be only Buffer or String');
}

opts.maxRedirects = maxRedirects ? opts.maxRedirects : 10;
opts.followRedirects = followRedirects ? opts.followRedirects : false;

Expand All @@ -50,44 +55,48 @@ function tosck(address, opts, callback) {
if (opts.body) {
opts.method = kindOf(opts.method) === 'string' ? opts.method : 'post';
}
opts.method = opts.method ? opts.method.toUpperCase() : undefined;

request(address, opts, callback)
};

function request(address, opts, callback) {
var parsedUrl = url.parse(prependHttp(address));
var fn = parsedUrl.protocol === 'https:' ? https : http;

opts = extend(parsedUrl, opts);
var options = extend({}, parsedUrl, opts);

if (opts.query) {
var query = opts.query;
opts.path = (opts.path ? opts.path.split('?')[0] : '') + '?';
if (options.query) {
var query = options.query;
options.path = (options.path ? options.path.split('?')[0] : '') + '?';
query = typeof query === 'string' ? query : qs.stringify(query);
opts.path = opts.path + query;
options.path = options.path + query;
}


var req = fn.request(opts, function(response) {
var req = fn.request(options, function(response) {
var res = response;
var code = response.statusCode;
var contentEncoding = response.headers['content-encoding'];


// decompress
if (['gzip', 'deflate'].indexOf(contentEncoding) !== -1) {
res = res.pipe(zlib.createUnzip());
}

// redirects
var isRedirect = statuses.redirect[code];
if (opts.followRedirects && isRedirect && response.headers.location) {
var location = response.headers.location;
if (isRedirect && opts.followRedirects && location) {
response.resume(); // Discard response

if (redirects++ > opts.maxRedirects) {
var msg = 'Redirected ' + redirects;
msg = msg + ' times, ' + opts.maxRedirects;
msg = msg + ' allowed. Aborting.';
if (++redirects > opts.maxRedirects) {
var msg = 'Redirected ' + opts.maxRedirects + ' times. Aborting.';
callback(new Error(msg), undefined, response);
return;
}
tosck(url.resolve(address, response.headers.location), opts, callback);

request(url.resolve(address, location), opts, callback);
return;
}

Expand All @@ -99,24 +108,27 @@ function tosck(address, opts, callback) {
err.code = code;
}
if (opts.json === true) {
tryJson(data, callback, response);
tryJson(err, data, response, callback);
return;
}
callback(err, data, response);
});
});
req.once('error', function (err) {
req.once('error', function(err) {
callback(err);
});
if (opts.body) {
req.end(opts.body, opts.encoding);
return;
}
req.end();
};
}

function tryJson(data, callback, response) {
var isError = null;
function tryJson(err, data, res, cb) {
try {
data = JSON.parse(data);
} catch(err) {
isError = err;
} catch(e) {
err = e;
}
callback(isError, data, response);
cb(err, data, res);
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -52,7 +52,8 @@
},
"devDependencies": {
"assertit": "^0.1.0",
"from2-array": "0.0.3",
"istanbul-harmony": "~0.3.1",
"pem": "^1.7.2"
}
}
}
161 changes: 161 additions & 0 deletions test/index.js
Expand Up @@ -12,6 +12,7 @@ var zlib = require('zlib');
var test = require('assertit');
var tosck = require('../index');
var server = require('./server.js');
var from2Array = require('from2-array');
var httpServer = server.createHttpServer();

// assertit like mocha
Expand All @@ -37,6 +38,28 @@ httpServer.on('/', function(req, res) {
httpServer.on('/test', function(req, res) {
res.end(req.url);
});
httpServer.on('/json/ok', function(req, res) {
res.end('{"cat":"meow"}');
});
httpServer.on('/json/500', function(req, res) {
res.statusCode = 500;
res.end('{"json":"cat"}');
});
httpServer.on('/json/invalid', function(req, res) {
res.end('/1"2"3/');
});
httpServer.on('/json/invalid/500', function(req, res) {
res.statusCode = 500;
res.end('invalid non 200');
});
httpServer.on('/method', function (req, res) {
res.setHeader('method', req.method);
res.end();
});
httpServer.on('/post/body', function (req, res) {
res.setHeader('method', req.method);
req.pipe(res);
});
httpServer.on('/empty', function(req, res) {
res.end();
});
Expand All @@ -63,6 +86,31 @@ httpServer.on('/corrupted', function(req, res) {
res.setHeader('Content-Encoding', 'gzip');
res.end('Not gzipped content');
});
httpServer.on('/finite', function(req, res) {
res.statusCode = 302;
res.setHeader('Location', httpServer.url + '/reached');
res.end();
});
httpServer.on('/endless', function(req, res) {
res.statusCode = 301;
res.setHeader('Location', httpServer.url + '/endless');
res.end();
});
httpServer.on('/relative', function(req, res) {
res.statusCode = 301;
res.setHeader('Location', '/reached');
res.end();
});
httpServer.on('/relativeQuery?bang', function(req, res) {
res.statusCode = 301;
res.setHeader('Location', '/reached');
res.end();
});
httpServer.on('/reached', function(req, res) {
res.statusCode = 200;
res.setHeader('X-Charlike', 'tunnckoCore');
res.end('reached');
});

// tests
describe('tosck', function() {
Expand Down Expand Up @@ -255,6 +303,119 @@ describe('tosck', function() {
done();
});
});
it('should parse json response when json option is true', function(done) {
tosck(httpServer.url + '/json/ok', {json: true}, function(err, json, res) {
assert.strictEqual(err, null);
assert.strictEqual(res.statusCode, 200);
assert.deepEqual(json, {cat: 'meow'});
done();
});
});
it('should parse non-200 json responses and pass error', function(done) {
tosck(httpServer.url + '/json/500', {json: true}, function(err, json, res) {
assert.ok(err !== null);
assert.strictEqual(err.code, 500);
assert.strictEqual(res.statusCode, 500);
assert.deepEqual(json, {json: 'cat'});
done();
});
});
it('should catch parsing json errors on 200 responses', function(done) {
tosck(httpServer.url + '/json/invalid', {json: true}, function(err, data, res) {
assert.ok(err !== null);
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(/Unexpected token \//.test(err.message), true);
assert.strictEqual(data, '/1"2"3/');
done();
});
});
it('should catch parsing json errors on non-200 responses', function(done) {
tosck(httpServer.url + '/json/invalid/500', {json: true}, function(err, data, res) {
assert.ok(err !== null);
assert.strictEqual(res.statusCode, 500);
assert.strictEqual(/Unexpected token i/.test(err.message), true);
assert.strictEqual(data, 'invalid non 200');
done();
});
});
it('GET requests can have body', function(done) {
tosck(httpServer.url + '/method', {
method: 'get',
body: 'dancing parrot'
}, function(err, data, res) {
assert.strictEqual(err, null);
assert.strictEqual(data, '');
assert.strictEqual(res.headers.method, 'GET');
done();
});
});
it('POST request when options.body is string automagically', function(done) {
tosck(httpServer.url + '/post/body', {body: 'wow'}, function(err, data, res) {
assert.strictEqual(err, null);
assert.strictEqual(data, 'wow');
assert.strictEqual(res.headers.method, 'POST');
done();
});
});
it('POST request when options.body is Buffer', function(done) {
tosck(httpServer.url + '/post/body', {body: new Buffer('wow')}, function(err, data) {
assert.strictEqual(err, null);
assert.strictEqual(data, 'wow');
done();
});
});
it('should throw TypeError when no String or Buffer POST opts.body given', function(done) {
function fixture() {
var opts = {body: from2Array(['cat', 'meow'])};
var noop = function _noop() {};
tosck(httpServer.url + '/post/body', opts, noop);
}
assert.throws(fixture, TypeError);
assert.throws(fixture, /opts\.body can be only Buffer or String/);
done();
});
it('works with non empty body sent and empty post response', function(done) {
tosck(httpServer.url + '/empty', {body: 'wow'}, function(err, data) {
assert.strictEqual(err, null);
assert.strictEqual(data, '');
done();
});
});
it('follows redirect', function(done) {
tosck(httpServer.url + '/finite', {followRedirects: true}, function(err, data, res) {
assert.strictEqual(err, null);
assert.strictEqual(data, 'reached');
assert.strictEqual(res.headers['x-charlike'], 'tunnckoCore');
assert.strictEqual(res.statusCode, 200);
done();
});
});
it('follows relative redirect', function(done) {
tosck(httpServer.url + '/relative', {followRedirects: true}, function(err, data, res) {
assert.strictEqual(err, null);
assert.strictEqual(data, 'reached');
assert.strictEqual(res.headers['x-charlike'], 'tunnckoCore');
assert.strictEqual(res.statusCode, 200);
done();
});
});
it('errors on endless redirect default max redirects 10', function(done) {
tosck(httpServer.url + '/endless', {followRedirects: true}, function(err) {
assert.ok(err !== null, 'should get error');
assert.strictEqual(/Redirected 10 times/.test(err.message), true);
done();
});
});
it('errors on endless redirect when maxRedirects: 5', function(done) {
tosck(httpServer.url + '/endless', {
followRedirects: true,
maxRedirects: 5
}, function(err) {
assert.ok(err !== null, 'should get error');
assert.strictEqual(/Redirected 5 times/.test(err.message), true);
done();
});
});
it('cleanup HTTP server', function(done) {
httpServer.close();
done();
Expand Down

0 comments on commit 65a8203

Please sign in to comment.