Skip to content

Commit f8b9f36

Browse files
committed
most of the main work done, TODO tests + enhancements
1 parent d009ae2 commit f8b9f36

File tree

6 files changed

+132
-122
lines changed

6 files changed

+132
-122
lines changed

Diff for: index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./lib/restler');

Diff for: lib/restler.js

+98-75
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
var sys = require('sys'),
22
http = require('http'),
3+
https = require('https'),
34
url = require('url'),
45
qs = require('querystring'),
56
multipart = require('./multipartform');
67

7-
function Request(url, options) {
8-
this.url = uri.parse(url);
8+
function Request(uri, options) {
9+
this.url = url.parse(uri);
910
this.options = options;
1011
this.headers = {
1112
'Accept': '*/*',
12-
'Host': this.url.domain,
1313
'User-Agent': 'Restler for node.js'
1414
}
15-
var _this = this
16-
Object.keys(options.headers || {}).forEach(function(headerKey) {
17-
_this.headers[headerKey] = options.headers[headerKey]
18-
})
1915

20-
if (!this.url.path) this.url.path = '/'
16+
Object.keys(options.headers || {}).forEach(function(headerKey) {
17+
this.headers[headerKey] = options.headers[headerKey]
18+
}, this);
2119

2220
// set port and method defaults
23-
if (!this.url.port) this.url.port = (this.url.protocol == 'https') ? '443' : '80';
21+
if (!this.url.port) this.url.port = (this.url.protocol == 'https:') ? '443' : '80';
2422
if (!this.options.method) this.options.method = (this.options.data) ? 'POST' : 'GET';
2523
if (typeof this.options.followRedirects == 'undefined') this.options.followRedirects = true;
2624

@@ -33,10 +31,27 @@ function Request(url, options) {
3331

3432
this._applyBasicAuth();
3533

36-
this.client = this._getClient(this.url.port, this.url.domain, (this.url.protocol == 'https'));
34+
// TODO support chunked requests as well
35+
if (typeof this.options.data == 'object') {
36+
this.options.data = qs.stringify(this.options.data);
37+
this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
38+
this.headers['Content-Length'] = this.options.data.length;
39+
} else if (this.options.multipart) {
40+
this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
41+
this.headers['Transfer-Encoding'] = 'chunked';
42+
}
3743

38-
if (this.options.multipart) this._createMultipartRequest();
39-
else this._createRequest();
44+
var proto = (this.url.protocol == 'https:') ? https : http;
45+
46+
this.request = proto.request({
47+
host: this.url.host,
48+
port: this.url.port,
49+
path: this._fullPath(),
50+
method: this.options.method,
51+
headers: this.headers
52+
});
53+
54+
this._makeRequest();
4055
}
4156

4257
Request.prototype = new process.EventEmitter();
@@ -51,25 +66,27 @@ var requestMethods = {
5166
return path;
5267
},
5368
_applyBasicAuth: function() {
54-
// use URL credentials over options
55-
if (this.url.user) this.options.username = this.url.user;
56-
if (this.url.password) this.options.password = this.url.password;
69+
var authParts;
70+
71+
if (this.url.auth) {
72+
authParts = this.url.auth.split(':');
73+
this.options.username = authParts[0];
74+
this.options.password = authParts[1];
75+
}
5776

5877
if (this.options.username && this.options.password) {
59-
var auth = base64.encode(this.options.username + ':' + this.options.password);
60-
this.headers['Authorization'] = "Basic " + auth;
78+
var b = new Buffer([this.options.username, this.options.password].join(':'));
79+
this.headers['Authorization'] = "Basic " + b.toString('base64');
6180
}
6281
},
63-
_getClient: function(port, host, https) {
64-
return this.options.client || http.createClient(port, host, https);
65-
},
6682
_responseHandler: function(response) {
6783
var self = this;
6884

6985
if (this._isRedirect(response) && this.options.followRedirects == true) {
7086
try {
71-
var location = uri.resolve(this.url, response.headers['location']);
72-
this.options.originalRequest = this;
87+
var location = url.resolve(this.url, response.headers['location']);
88+
this.options.originalRequest = this;
89+
7390
request(location, this.options);
7491
} catch(e) {
7592
self._respond('error', '', 'Failed to follow redirect');
@@ -78,7 +95,8 @@ var requestMethods = {
7895
var body = '';
7996

8097
response.addListener('data', function(chunk) {
81-
body += chunk;
98+
// TODO Handle different encodings
99+
body += chunk.toString();
82100
});
83101

84102
response.addListener('end', function() {
@@ -94,53 +112,38 @@ var requestMethods = {
94112
}
95113
},
96114
_respond: function(type, data, response) {
97-
if (this.options.originalRequest) this.options.originalRequest.emit(type, data, response);
98-
else this.emit(type, data, response);
99-
},
100-
_makeRequest: function(method) {
101-
var self = this;
102-
// Support new and old interface for making requests for now
103-
var request;
104-
if (typeof this.client.request == 'function') {
105-
request = this.client.request(method, this._fullPath(), this.headers);
115+
if (this.options.originalRequest) {
116+
this.options.originalRequest.emit(type, data, response);
106117
} else {
107-
method = method.toLowerCase();
108-
if (method == 'delete') method = 'del';
109-
request = this.client[method](this._fullPath(), this.headers);
110-
}
111-
request.addListener("response", function(response) {
112-
self._responseHandler(response);
113-
});
114-
return request;
115-
},
116-
_createRequest: function() {
117-
if (typeof this.options.data == 'object') {
118-
this.options.data = qs.stringify(this.options.data);
119-
this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
120-
this.headers['Content-Length'] = this.options.data.length;
118+
this.emit(type, data, response);
121119
}
122-
123-
this.request = this._makeRequest(this.options.method);
124-
if (this.options.data) this.request.write(this.options.data.toString(), this.options.encoding || 'utf8');
125120
},
126-
_createMultipartRequest: function() {
127-
this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
128-
this.headers['Transfer-Encoding'] = 'chunked';
121+
_makeRequest: function() {
122+
var self = this;
123+
124+
this.request.addListener("response", function(response) {
125+
self._responseHandler(response);
126+
});
129127

130-
this.request = this._makeRequest(this.options.method);
128+
if (this.options.data) {
129+
this.request.write(this.options.data.toString(), this.options.encoding || 'utf8');
130+
}
131131
},
132132
run: function() {
133133
var self = this;
134+
134135
if (this.options.multipart) {
135136
multipart.write(this.request, this.options.data, function() {
136137
self.request.end();
137138
});
138139
} else {
139-
this.request.end();
140+
this.request.end();
140141
}
142+
141143
return this;
142144
}
143145
}
146+
144147
Object.keys(requestMethods).forEach(function(requestMethod) {
145148
Request.prototype[requestMethod] = requestMethods[requestMethod]
146149
})
@@ -157,7 +160,7 @@ function request(url, options) {
157160
}
158161

159162
function get(url, options) {
160-
return request(url, shortcutOptions(options, 'GET'));;
163+
return request(url, shortcutOptions(options, 'GET'));
161164
}
162165

163166
function post(url, options) {
@@ -172,33 +175,50 @@ function del(url, options) {
172175
return request(url, shortcutOptions(options, 'DELETE'));
173176
}
174177

178+
var ResponseParser = function(contentTypeMatcher, parser) {
179+
this.contentTypeMatcher = contentTypeMatcher;
180+
this.parser = parser;
181+
}
182+
183+
ResponseParser.prototype.match = function(contentType) {
184+
// TODO enhance for fuzzier matching if required
185+
return contentType == this.contentTypeMatcher;
186+
};
187+
188+
ResponseParser.prototype.parse = function(data) {
189+
return this.parser(data);
190+
};
191+
175192
var parsers = {
176193
auto: function(data) {
177194
var contentType = this.headers['content-type'];
195+
178196
if (contentType) {
179-
if (contentType.indexOf('application/') == 0) {
180-
if (contentType.indexOf('json', 12) == 12)
181-
return parsers.json(data);
182-
if (contentType.indexOf('xml', 12) == 12)
183-
return parsers.xml(data);
184-
if (contentType.indexOf('yaml', 12) == 12)
185-
return parsers.yaml(data);
197+
for (var matcher in parsers.auto.parsers) {
198+
if (contentType == matcher) {
199+
return parsers.auto.parsers[matcher].call(this, data);
200+
}
186201
}
187202
}
188-
189-
// Data doesn't match any known application/* content types.
203+
190204
return data;
191-
},
192-
xml: function(data) {
193-
return data && x2j.parse(data);
194-
},
195-
json: function(data) {
205+
}
206+
}
207+
208+
// TODO Make these async so it'll work with async parsers (xml2js)
209+
parsers.auto.parsers = {
210+
'application/json': function(data) {
196211
return data && JSON.parse(data);
197-
},
198-
yaml: function(data) {
212+
}
213+
};
214+
215+
try {
216+
var yaml = require('yaml');
217+
parsers.auto.parsers['application/yaml'] = function(data) {
199218
return data && yaml.eval(data);
200219
}
201-
}
220+
} catch(e) {}
221+
202222

203223
function Service(defaults) {
204224
if (defaults.baseURL) {
@@ -231,17 +251,20 @@ var serviceMethods = {
231251
},
232252
_withDefaults: function(options) {
233253
var o = {};
234-
var defaults = this.defaults
254+
var defaults = this.defaults;
255+
235256
Object.keys(defaults).forEach(function(_default) {
236257
o[_default] = defaults[_default]
237-
})
258+
});
259+
238260
Object.keys(options).forEach(function(option) {
239261
o[option] = options[option]
240-
})
262+
});
241263

242264
return o;
243265
}
244266
}
267+
245268
Object.keys(serviceMethods).forEach(function(serviceMethod) {
246269
Service.prototype[serviceMethod] = serviceMethods[serviceMethod]
247270
})

Diff for: package.json

+4-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
{
22
"name": "restler",
3-
"version": "1.0.0",
4-
"directories": {
5-
"lib": "./lib"
6-
},
7-
"main": "./lib/restler",
8-
"engines": {
9-
"node" : ">=0.1.93"
10-
},
11-
"dependencies": {
12-
},
13-
"description": "An HTTP client library for node.js. Hides most of the complexity of creating and using http.Client.",
14-
"author": "Dan Webb <[email protected]>",
15-
"homepage": "https://github.com/danwrong/restler",
16-
"repository" :
17-
{
18-
"type" : "git",
19-
"url" : "git://github.com/danwrong/restler.git"
20-
}
3+
"version": "2.0.0",
4+
"description": "An HTTP client library for node.js",
5+
"contributors": [{ name: "Dan Webb", email: {[email protected]" }],
6+
"homepage": "https://github.com/danwrong/restler"
217
}

Diff for: test/multipartform.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
multipartform = require('../lib/multipartform'),
1+
var multipartform = require('../lib/multipartform'),
22
multipart = require('multipart'),
3-
sys = require('sys');
4-
fs = require('fs')
5-
http = require("http");
3+
sys = require('sys'),
4+
fs = require('fs'),
5+
http = require("http");
66

77

88
http.createServer(function (req, res) {

0 commit comments

Comments
 (0)