Permalink
Browse files

most of the main work done, TODO tests + enhancements

  • Loading branch information...
1 parent d009ae2 commit f8b9f362c5d6a9766b8bd6d0d46d40084314ae6d @danwrong committed Feb 2, 2011
Showing with 132 additions and 122 deletions.
  1. +1 −0 index.js
  2. +98 −75 lib/restler.js
  3. +4 −18 package.json
  4. +4 −4 test/multipartform.js
  5. +24 −24 test/restler.js
  6. +1 −1 test/test_helper.js
View
@@ -0,0 +1 @@
+module.exports = require('./lib/restler');
View
@@ -1,26 +1,24 @@
var sys = require('sys'),
http = require('http'),
+ https = require('https'),
url = require('url'),
qs = require('querystring'),
multipart = require('./multipartform');
-function Request(url, options) {
- this.url = uri.parse(url);
+function Request(uri, options) {
+ this.url = url.parse(uri);
this.options = options;
this.headers = {
'Accept': '*/*',
- 'Host': this.url.domain,
'User-Agent': 'Restler for node.js'
}
- var _this = this
- Object.keys(options.headers || {}).forEach(function(headerKey) {
- _this.headers[headerKey] = options.headers[headerKey]
- })
- if (!this.url.path) this.url.path = '/'
+ Object.keys(options.headers || {}).forEach(function(headerKey) {
+ this.headers[headerKey] = options.headers[headerKey]
+ }, this);
// set port and method defaults
- if (!this.url.port) this.url.port = (this.url.protocol == 'https') ? '443' : '80';
+ if (!this.url.port) this.url.port = (this.url.protocol == 'https:') ? '443' : '80';
if (!this.options.method) this.options.method = (this.options.data) ? 'POST' : 'GET';
if (typeof this.options.followRedirects == 'undefined') this.options.followRedirects = true;
@@ -33,10 +31,27 @@ function Request(url, options) {
this._applyBasicAuth();
- this.client = this._getClient(this.url.port, this.url.domain, (this.url.protocol == 'https'));
+ // TODO support chunked requests as well
+ if (typeof this.options.data == 'object') {
+ this.options.data = qs.stringify(this.options.data);
+ this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ this.headers['Content-Length'] = this.options.data.length;
+ } else if (this.options.multipart) {
+ this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
+ this.headers['Transfer-Encoding'] = 'chunked';
+ }
- if (this.options.multipart) this._createMultipartRequest();
- else this._createRequest();
+ var proto = (this.url.protocol == 'https:') ? https : http;
+
+ this.request = proto.request({
+ host: this.url.host,
+ port: this.url.port,
+ path: this._fullPath(),
+ method: this.options.method,
+ headers: this.headers
+ });
+
+ this._makeRequest();
}
Request.prototype = new process.EventEmitter();
@@ -51,25 +66,27 @@ var requestMethods = {
return path;
},
_applyBasicAuth: function() {
- // use URL credentials over options
- if (this.url.user) this.options.username = this.url.user;
- if (this.url.password) this.options.password = this.url.password;
+ var authParts;
+
+ if (this.url.auth) {
+ authParts = this.url.auth.split(':');
+ this.options.username = authParts[0];
+ this.options.password = authParts[1];
+ }
if (this.options.username && this.options.password) {
- var auth = base64.encode(this.options.username + ':' + this.options.password);
- this.headers['Authorization'] = "Basic " + auth;
+ var b = new Buffer([this.options.username, this.options.password].join(':'));
+ this.headers['Authorization'] = "Basic " + b.toString('base64');
}
},
- _getClient: function(port, host, https) {
- return this.options.client || http.createClient(port, host, https);
- },
_responseHandler: function(response) {
var self = this;
if (this._isRedirect(response) && this.options.followRedirects == true) {
try {
- var location = uri.resolve(this.url, response.headers['location']);
- this.options.originalRequest = this;
+ var location = url.resolve(this.url, response.headers['location']);
+ this.options.originalRequest = this;
+
request(location, this.options);
} catch(e) {
self._respond('error', '', 'Failed to follow redirect');
@@ -78,7 +95,8 @@ var requestMethods = {
var body = '';
response.addListener('data', function(chunk) {
- body += chunk;
+ // TODO Handle different encodings
+ body += chunk.toString();
});
response.addListener('end', function() {
@@ -94,53 +112,38 @@ var requestMethods = {
}
},
_respond: function(type, data, response) {
- if (this.options.originalRequest) this.options.originalRequest.emit(type, data, response);
- else this.emit(type, data, response);
- },
- _makeRequest: function(method) {
- var self = this;
- // Support new and old interface for making requests for now
- var request;
- if (typeof this.client.request == 'function') {
- request = this.client.request(method, this._fullPath(), this.headers);
+ if (this.options.originalRequest) {
+ this.options.originalRequest.emit(type, data, response);
} else {
- method = method.toLowerCase();
- if (method == 'delete') method = 'del';
- request = this.client[method](this._fullPath(), this.headers);
- }
- request.addListener("response", function(response) {
- self._responseHandler(response);
- });
- return request;
- },
- _createRequest: function() {
- if (typeof this.options.data == 'object') {
- this.options.data = qs.stringify(this.options.data);
- this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
- this.headers['Content-Length'] = this.options.data.length;
+ this.emit(type, data, response);
}
-
- this.request = this._makeRequest(this.options.method);
- if (this.options.data) this.request.write(this.options.data.toString(), this.options.encoding || 'utf8');
},
- _createMultipartRequest: function() {
- this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
- this.headers['Transfer-Encoding'] = 'chunked';
+ _makeRequest: function() {
+ var self = this;
+
+ this.request.addListener("response", function(response) {
+ self._responseHandler(response);
+ });
- this.request = this._makeRequest(this.options.method);
+ if (this.options.data) {
+ this.request.write(this.options.data.toString(), this.options.encoding || 'utf8');
+ }
},
run: function() {
var self = this;
+
if (this.options.multipart) {
multipart.write(this.request, this.options.data, function() {
self.request.end();
});
} else {
- this.request.end();
+ this.request.end();
}
+
return this;
}
}
+
Object.keys(requestMethods).forEach(function(requestMethod) {
Request.prototype[requestMethod] = requestMethods[requestMethod]
})
@@ -157,7 +160,7 @@ function request(url, options) {
}
function get(url, options) {
- return request(url, shortcutOptions(options, 'GET'));;
+ return request(url, shortcutOptions(options, 'GET'));
}
function post(url, options) {
@@ -172,33 +175,50 @@ function del(url, options) {
return request(url, shortcutOptions(options, 'DELETE'));
}
+var ResponseParser = function(contentTypeMatcher, parser) {
+ this.contentTypeMatcher = contentTypeMatcher;
+ this.parser = parser;
+}
+
+ResponseParser.prototype.match = function(contentType) {
+ // TODO enhance for fuzzier matching if required
+ return contentType == this.contentTypeMatcher;
+};
+
+ResponseParser.prototype.parse = function(data) {
+ return this.parser(data);
+};
+
var parsers = {
auto: function(data) {
var contentType = this.headers['content-type'];
+
if (contentType) {
- if (contentType.indexOf('application/') == 0) {
- if (contentType.indexOf('json', 12) == 12)
- return parsers.json(data);
- if (contentType.indexOf('xml', 12) == 12)
- return parsers.xml(data);
- if (contentType.indexOf('yaml', 12) == 12)
- return parsers.yaml(data);
+ for (var matcher in parsers.auto.parsers) {
+ if (contentType == matcher) {
+ return parsers.auto.parsers[matcher].call(this, data);
+ }
}
}
-
- // Data doesn't match any known application/* content types.
+
return data;
- },
- xml: function(data) {
- return data && x2j.parse(data);
- },
- json: function(data) {
+ }
+}
+
+// TODO Make these async so it'll work with async parsers (xml2js)
+parsers.auto.parsers = {
+ 'application/json': function(data) {
return data && JSON.parse(data);
- },
- yaml: function(data) {
+ }
+};
+
+try {
+ var yaml = require('yaml');
+ parsers.auto.parsers['application/yaml'] = function(data) {
return data && yaml.eval(data);
}
-}
+} catch(e) {}
+
function Service(defaults) {
if (defaults.baseURL) {
@@ -231,17 +251,20 @@ var serviceMethods = {
},
_withDefaults: function(options) {
var o = {};
- var defaults = this.defaults
+ var defaults = this.defaults;
+
Object.keys(defaults).forEach(function(_default) {
o[_default] = defaults[_default]
- })
+ });
+
Object.keys(options).forEach(function(option) {
o[option] = options[option]
- })
+ });
return o;
}
}
+
Object.keys(serviceMethods).forEach(function(serviceMethod) {
Service.prototype[serviceMethod] = serviceMethods[serviceMethod]
})
View
@@ -1,21 +1,7 @@
{
"name": "restler",
- "version": "1.0.0",
- "directories": {
- "lib": "./lib"
- },
- "main": "./lib/restler",
- "engines": {
- "node" : ">=0.1.93"
- },
- "dependencies": {
- },
- "description": "An HTTP client library for node.js. Hides most of the complexity of creating and using http.Client.",
- "author": "Dan Webb <dan@danwebb.net>",
- "homepage": "https://github.com/danwrong/restler",
- "repository" :
- {
- "type" : "git",
- "url" : "git://github.com/danwrong/restler.git"
- }
+ "version": "2.0.0",
+ "description": "An HTTP client library for node.js",
+ "contributors": [{ name: "Dan Webb", email: {dan@danwebb.net" }],
+ "homepage": "https://github.com/danwrong/restler"
}
View
@@ -1,8 +1,8 @@
- multipartform = require('../lib/multipartform'),
+var multipartform = require('../lib/multipartform'),
multipart = require('multipart'),
- sys = require('sys');
- fs = require('fs')
-http = require("http");
+ sys = require('sys'),
+ fs = require('fs'),
+ http = require("http");
http.createServer(function (req, res) {
Oops, something went wrong.

0 comments on commit f8b9f36

Please sign in to comment.