Skip to content
Browse files

Merge https://github.com/disfated/restler into 2.x.x-disfated

Conflicts:
	package.json
  • Loading branch information...
2 parents 18fa9a9 + 00c4011 commit 2704ec09d6277870979b76a868309c8f3c6192c6 @ayoung ayoung committed
Showing with 164 additions and 74 deletions.
  1. +3 −3 README.md
  2. +67 −27 lib/restler.js
  3. +2 −2 package.json
  4. +1 −0 test/all.js
  5. +91 −42 test/restler.js
View
6 README.md
@@ -24,8 +24,8 @@ Features
* Transparently handle SSL (just specify https in the URL)
* Deals with basic auth for you, just provide username and password options
* Simple service wrapper that allows you to easily put together REST API libraries
-* Transparently handle content-encoded responses (gzip, deflate)
-* Transparently handle different content charsets via `iconv`
+* Transparently handle content-encoded responses (gzip, deflate) (requires node 0.6+)
+* Transparently handle different content charsets via [iconv](https://github.com/bnoordhuis/node-iconv) (if available)
API
@@ -47,7 +47,7 @@ Basic method to make a request of any type. The function returns a RestRequest o
#### members
-* `abort()` Cancels request. `abort` event is emitted. `aborted` property is set to `true`. only `complete` and `error` event should.
+* `abort([error])` Cancels request. `abort` event is emitted. `request.aborted` is set to `true`. If non-falsy `error` is passed, then `error` will be additionaly emitted (with `error` passed as a param and `error.type` is set to `"abort"`). Otherwise only `complete` event will raise.
* `aborted` Determines if request was aborted.
View
94 lib/restler.js
@@ -1,20 +1,29 @@
-var sys = require('util'),
- http = require('http'),
- https = require('https'),
- url = require('url'),
- qs = require('querystring'),
- multipart = require('./multipartform'),
- zlib = require('zlib'),
- Iconv = require('iconv').Iconv;
-
+var sys = require('util');
+var http = require('http');
+var https = require('https');
+var url = require('url');
+var qs = require('querystring');
+var multipart = require('./multipartform');
+var zlib = null;
+var Iconv = null;
+
+try {
+ zlib = require('zlib');
+} catch (err) {}
+
+try {
+ Iconv = require('iconv').Iconv;
+} catch (err) {}
+
function mixin(target, source) {
+ source = source || {};
Object.keys(source).forEach(function(key) {
target[key] = source[key];
});
return target;
}
-
+
function Request(uri, options) {
this.url = url.parse(uri);
this.options = options;
@@ -23,6 +32,10 @@ function Request(uri, options) {
'User-Agent': 'Restler for node.js',
'Host': this.url.host
};
+
+ if (zlib) {
+ this.headers['Accept-Encoding'] = 'gzip, deflate';
+ }
mixin(this.headers, options.headers || {});
@@ -97,7 +110,7 @@ mixin(Request.prototype, {
_responseHandler: function(response) {
var self = this;
- if (this._isRedirect(response) && this.options.followRedirects == true) {
+ if (this._isRedirect(response) && this.options.followRedirects) {
try {
var location = url.resolve(this.url, response.headers['location']);
Request.call(this, location, this.options); // reusing request object to handle recursive redirects
@@ -145,18 +158,22 @@ mixin(Request.prototype, {
}
},
_iconv: function(body, response) {
- var charset = response.headers['content-type'];
- if (!charset) return body;
- charset = charset.match(/\bcharset=(.+)(?:;|$)/i);
- if (!charset) return body;
- charset = charset[1].trim().toUpperCase();
- if (charset == 'UTF-8') return body;
- try {
- var iconv = new Iconv(charset, 'UTF-8//TRANSLIT//IGNORE');
- return iconv.convert(body);
- } catch (err) {
- return body;
+ if (Iconv) {
+ var charset = response.headers['content-type'];
+ if (charset) {
+ charset = /\bcharset=(.+)(?:;|$)/i.exec(charset);
+ if (charset) {
+ charset = charset[1].trim().toUpperCase();
+ if (charset != 'UTF-8') {
+ try {
+ var iconv = new Iconv(charset, 'UTF-8//TRANSLIT//IGNORE');
+ return iconv.convert(body);
+ } catch (err) {}
+ }
+ }
+ }
}
+ return body;
},
_encode: function(body, response, callback) {
var self = this;
@@ -190,7 +207,9 @@ mixin(Request.prototype, {
this.request.on('response', function(response) {
self._responseHandler(response);
}).on('error', function(err) {
- self._fireError(err, null);
+ if (!self.aborted) {
+ self._fireError(err, null);
+ }
});
},
run: function() {
@@ -209,10 +228,31 @@ mixin(Request.prototype, {
return this;
},
- abort: function() {
- this.aborted = true;
- this.request.abort();
- this.emit('abort');
+ abort: function(err) {
+ var self = this;
+
+ if (err) {
+ if (typeof err == 'string') {
+ err = new Error(err);
+ } else if (!(err instanceof Error)) {
+ err = new Error('AbortError');
+ }
+ err.type = 'abort';
+ } else {
+ err = null;
+ }
+
+ self.request.on('close', function() {
+ if (err) {
+ self._fireError(err, null);
+ } else {
+ self.emit('complete', null, null);
+ }
+ });
+
+ self.aborted = true;
+ self.request.abort();
+ self.emit('abort', err);
return this;
}
});
View
4 package.json
@@ -8,11 +8,11 @@
"main" : "./lib/restler",
"engines": { "node": ">= 0.6.x" },
"dependencies": {
- "iconv": ">=1.0.0"
},
"devDependencies": {
"nodeunit": ">=0.5.0",
"xml2js" : ">=0.1.0",
- "yaml" : ">=0.2.0"
+ "yaml" : ">=0.2.0",
+ "iconv": ">=1.0.0"
}
}
View
1 test/all.js
@@ -1,4 +1,5 @@
+require('./restler'); // debug
var nodeunit = require('nodeunit');
var reporter = nodeunit.reporters['default'];
process.chdir(__dirname);
View
133 test/restler.js
@@ -2,10 +2,19 @@
var rest = require('../lib/restler');
var http = require('http');
var sys = require('util');
-var zlib = require('zlib');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
+var zlib = null;
+var Iconv = null;
+
+try {
+ zlib = require('zlib');
+} catch (err) {}
+
+try {
+ Iconv = require('iconv').Iconv;
+} catch (err) {}
var p = sys.inspect;
@@ -289,6 +298,12 @@ function dataResponse(request, response) {
response.writeHead(200, { 'content-type': 'application/yaml' });
response.end('{Чебурашка');
break;
+ case '/abort':
+ setTimeout(function() {
+ response.writeHead(200);
+ response.end('not aborted');
+ }, 100);
+ break;
default:
response.writeHead(404);
response.end();
@@ -322,31 +337,43 @@ module.exports['Deserialization'] = {
},
'Should gunzip': function(test) {
- rest.get(host + '/gzip').on('complete', function(data) {
- test.re(data, /^(compressed data){10}$/, 'returned: ' + p(data));
+ if (zlib) {
+ rest.get(host + '/gzip').on('complete', function(data) {
+ test.re(data, /^(compressed data){10}$/, 'returned: ' + p(data));
+ test.done();
+ });
+ } else {
test.done();
- })
+ }
},
'Should inflate': function(test) {
- rest.get(host + '/deflate').on('complete', function(data) {
- test.re(data, /^(compressed data){10}$/, 'returned: ' + p(data));
+ if (zlib) {
+ rest.get(host + '/deflate').on('complete', function(data) {
+ test.re(data, /^(compressed data){10}$/, 'returned: ' + p(data));
+ test.done();
+ })
+ } else {
test.done();
- })
+ }
},
'Should decode and parse': function(test) {
- rest.get(host + '/truth').on('complete', function(data) {
- try {
- with (data) {
- var result = what + (is + the + answer + to + life + the + universe + and + everything).length;
+ if (zlib) {
+ rest.get(host + '/truth').on('complete', function(data) {
+ try {
+ with (data) {
+ var result = what + (is + the + answer + to + life + the + universe + and + everything).length;
+ }
+ test.equal(result, 42, 'returned: ' + p(data));
+ } catch (err) {
+ test.ok(false, 'returned: ' + p(data));
}
- test.equal(result, 42, 'returned: ' + p(data));
- } catch (err) {
- test.ok(false, 'returned: ' + p(data));
- }
+ test.done();
+ })
+ } else {
test.done();
- })
+ }
},
'Should decode as buffer': function(test) {
@@ -410,22 +437,39 @@ module.exports['Deserialization'] = {
});
},
- 'Should correctly abort request': function(test) {
- test.expect(5);
- rest.get(host + '/json').on('complete', function(data) {
- test.ok(data instanceof Error, 'should be error, got: ' + p(data));
- test.equal(this.aborted, true, 'should aborted, got: ' + p(this.aborted));
+ 'Should correctly soft-abort request': function(test) {
+ test.expect(4);
+ rest.get(host + '/abort').on('complete', function(data) {
+ test.equal(data, null, 'data should be null');
+ test.equal(this.aborted, true, 'should be aborted');
test.done();
- }).on('error', function(data) {
+ }).on('error', function(err) {
+ test.ok(false, 'should not emit error event');
+ }).on('abort', function(err) {
+ test.equal(err, null, 'err should be null');
+ test.equal(this.aborted, true, 'should be aborted');
+ }).on('success', function() {
+ test.ok(false, 'should not emit success event');
+ }).on('fail', function() {
+ test.ok(false, 'should not emit fail event');
+ }).abort();
+ },
+
+ 'Should correctly hard-abort request': function(test) {
+ test.expect(4);
+ rest.get(host + '/abort').on('complete', function(data) {
test.ok(data instanceof Error, 'should be error, got: ' + p(data));
- test.equal(this.aborted, true, 'should aborted, got: ' + p(this.aborted));
- }).on('abort', function() {
- test.equal(this.aborted, true, 'should aborted, got: ' + p(this.aborted));
- }).on('success', function() {
- test.ok(false, 'should not have got here');
- }).on('fail', function() {
- test.ok(false, 'should not have got here');
- }).abort();
+ test.equal(this.aborted, true, 'should be aborted');
+ test.done();
+ }).on('error', function(err) {
+ test.ok(err instanceof Error, 'should be error, got: ' + p(err));
+ }).on('abort', function(err) {
+ test.equal(this.aborted, true, 'should be aborted');
+ }).on('success', function() {
+ test.ok(false, 'should not emit success event');
+ }).on('fail', function() {
+ test.ok(false, 'should not emit fail event');
+ }).abort(true);
},
'Should correctly handle malformed JSON': function(test) {
@@ -487,9 +531,13 @@ function charsetsResponse(request, response) {
var charset = request.url.substr(1);
response.writeHead(200, {
'content-type': 'text/plain; charset=' + charset,
- 'content-encoding': 'gzip'
+ 'content-encoding': zlib ? 'gzip' : ''
});
- fs.createReadStream(path.join(__dirname, charsetsDir, charset)).pipe(zlib.createGzip()).pipe(response);
+ var stream = fs.createReadStream(path.join(__dirname, charsetsDir, charset));
+ if (zlib) {
+ stream = stream.pipe(zlib.createGzip());
+ }
+ stream.pipe(response);
}
module.exports['Charsets'] = {
@@ -511,15 +559,17 @@ var charsetCases = {
'gbk' : '01329db97a6a202ecffaf95d4f77a18d'
};
-for (var charset in charsetCases) {
- (function(charset, hash) {
- module.exports['Charsets']['Should correctly convert charset ' + charset] = function(test) {
- rest.get(host + '/' + charset).on('complete', function(data) {
- test.equal(md5(Buffer(data, 'utf8')), hash, 'hashes should match');
- test.done();
- });
- };
- })(charset, charsetCases[charset]);
+if (Iconv) {
+ for (var charset in charsetCases) {
+ (function(charset, hash) {
+ module.exports['Charsets']['Should correctly convert charset ' + charset] = function(test) {
+ rest.get(host + '/' + charset).on('complete', function(data) {
+ test.equal(md5(Buffer(data, 'utf8')), hash, 'hashes should match');
+ test.done();
+ });
+ };
+ })(charset, charsetCases[charset]);
+ }
}
@@ -600,4 +650,3 @@ module.exports['Content-Length'] = {
}
};
-

0 comments on commit 2704ec0

Please sign in to comment.
Something went wrong with that request. Please try again.