Comparing version 0.2.0 to 0.2.2
@@ -17,2 +17,3 @@ ////////////////////////////////////////// | ||
http.globalAgent.maxSockets = 128; | ||
exports.version = version; | ||
@@ -47,3 +48,2 @@ try { var unzip = require('zlib').unzip; } catch(e){ /* zlib not supported */ } | ||
// utility function for flattening params in multipart POST's | ||
function flatten(object, into, prefix){ | ||
@@ -57,3 +57,3 @@ | ||
if(prop && typeof prop === "object" && !(prop.file && prop.content_type)) | ||
if(prop && typeof prop === "object" && !((prop.buffer || prop.file) && prop.content_type)) | ||
flatten(prop, into, prefix_key) | ||
@@ -75,3 +75,3 @@ else | ||
// normalize arguments | ||
var self = this; | ||
var callback = (typeof options == 'function') ? options : callback; | ||
@@ -84,9 +84,11 @@ var options = options || {}; | ||
var port = remote.port || (is_https ? 443 : 80); | ||
var protocol = is_https ? https : http; | ||
var request_compressed = (options.compressed && typeof unzip != 'undefined') || false; | ||
var request_encoding = options.multipart ? 'binary' : 'utf8'; | ||
var post_data = null; | ||
var parse_response = options.parse === false ? false : true; | ||
var timeout = options.timeout || 10000; // 10 seconds timeout | ||
var timer = null; | ||
var request_opts = { | ||
encoding: options.multipart ? 'binary' : 'utf8', | ||
protocol: is_https ? https : http, | ||
parse_response: options.parse === false ? false : true, | ||
timeout: options.timeout || 10000 // 10 seconds timeout | ||
} | ||
@@ -102,15 +104,2 @@ var headers = { | ||
if(data) { | ||
if(options.multipart){ | ||
var boundary = options.boundary || this.default_boundary; | ||
var post_data = this.build_multipart_body(data, boundary); | ||
headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary; | ||
} else { | ||
var post_data = (typeof(data) === "string") ? data : stringify(data); | ||
headers['Content-Type'] = 'application/x-www-form-urlencoded' | ||
} | ||
headers['Content-Length'] = post_data.length; | ||
// if(process.env.DEBUG) console.log(post_data); | ||
} | ||
for(h in options.headers) | ||
@@ -124,36 +113,49 @@ headers[h] = options.headers[h]; | ||
var response_end = function(response, body){ | ||
var http_opts = { | ||
host: remote.hostname, | ||
port: port, | ||
path: options.proxy ? uri : remote.pathname + (remote.search || ''), | ||
method: method, | ||
headers: headers, | ||
agent: options.agent || http.globalAgent | ||
} | ||
if(process.env.DEBUG) console.log(response.headers); | ||
var content_type = response.headers['content-type'] && response.headers['content-type'].split(';')[0]; | ||
if(data) { | ||
if(options.multipart){ | ||
if(parse_response && parsers[content_type]) { | ||
parsers[content_type](body, function(result){ | ||
if(callback) callback(null, response, result); | ||
var boundary = options.boundary || this.default_boundary; | ||
return this.build_multipart_body(data, boundary, function(err, body){ | ||
if(err) throw(err); | ||
headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary; | ||
headers['Content-Length'] = body.length; | ||
self.send_request(request_opts, http_opts, body, callback); | ||
}); | ||
} else { | ||
if(callback) callback(null, response, body); | ||
post_data = (typeof(data) === "string") ? data : stringify(data); | ||
headers['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
headers['Content-Length'] = post_data.length; | ||
} | ||
}; | ||
var request_opts = { | ||
host: remote.hostname, | ||
port: port, | ||
path: options.proxy ? uri : remote.pathname + (remote.search || ""), | ||
method: method, | ||
headers: headers | ||
} | ||
if(process.env.DEBUG) console.log(request_opts); | ||
this.send_request(request_opts, http_opts, post_data, callback); | ||
var request = protocol.request(request_opts, function(response){ | ||
}, | ||
send_request: function(request_opts, http_opts, post_data, callback){ | ||
if(process.env.DEBUG) console.log(http_opts + "\n\n" + post_data); | ||
var self = this, timer, response_opts = {parse_response: request_opts.parse_response}; | ||
var request = request_opts.protocol.request(http_opts, function(response){ | ||
if(timer) clearTimeout(timer); | ||
var body = ''; | ||
var compressed = /gzip|deflate/.test(response.headers['content-encoding']); | ||
var response_encoding = compressed ? 'binary' : 'utf8'; | ||
response.setEncoding(response_encoding); | ||
response.setEncoding(compressed ? 'binary' : 'utf8'); | ||
if(timer) clearTimeout(timer); | ||
response.on('data', function(chunk){ | ||
@@ -166,6 +168,6 @@ body += chunk; | ||
unzip(new Buffer(body, 'binary'), function(err, buff){ | ||
response_end(response, buff.toString()) | ||
self.response_end(response_opts, response, buff.toString(), callback); | ||
}); | ||
else | ||
response_end(response, body); | ||
self.response_end(response_opts, response, body, callback); | ||
}); | ||
@@ -175,25 +177,39 @@ | ||
if(timeout) { | ||
if(request_opts.timeout) { | ||
timer = setTimeout(function() { | ||
request.abort(); | ||
}, timeout) | ||
}, request_opts.timeout) | ||
} | ||
request.on('error', function(err) { | ||
if(process.env.DEBUG) console.log('Error on request: ' + err); | ||
if(process.env.DEBUG) console.log('Error on request: ' + err.toString()); | ||
if(timer) clearTimeout(timer); | ||
if(callback) callback(err || true); | ||
if(callback) callback(err || new Error("Unkown error on request.")); | ||
}); | ||
if(post_data) request.write(post_data, request_encoding); | ||
if(post_data) request.write(post_data, request_opts.encoding); | ||
request.end(); | ||
return request; | ||
}, | ||
response_end: function(opts, response, body, callback){ | ||
if(process.env.DEBUG) console.log(response.headers); | ||
var content_type = response.headers['content-type'] && response.headers['content-type'].split(';')[0]; | ||
if(opts.parse_response && parsers[content_type]) { | ||
parsers[content_type](body, function(result){ | ||
if(callback) callback(null, response, result); | ||
}); | ||
} else { | ||
if(callback) callback(null, response, body); | ||
} | ||
}, | ||
build_multipart_body: function(data, boundary){ | ||
build_multipart_body: function(data, boundary, callback){ | ||
var body = ''; | ||
var object = flatten(data); | ||
var count = Object.keys(object).length; | ||
@@ -203,14 +219,17 @@ for(var key in object){ | ||
var value = object[key]; | ||
if(value !== null && typeof value != 'undefined'){ | ||
var part = value.file && value.content_type ? value : {value: value}; | ||
body += this.generate_part(key, part, boundary); | ||
} | ||
if(value === null || typeof value == 'undefined') return --count; | ||
var part = (value.buffer || value.file) && value.content_type ? value : {value: value}; | ||
this.generate_part(key, part, boundary, function(err, section){ | ||
if(err) return callback(err); | ||
body += section; | ||
--count || callback(null, body + '\r\n' + '--' + boundary + '--'); | ||
}); | ||
} | ||
return body + '\r\n' + '--' + boundary + '--'; | ||
}, | ||
generate_part: function(name, part, boundary){ | ||
generate_part: function(name, part, boundary, callback){ | ||
@@ -220,13 +239,27 @@ var return_part = '--' + boundary + "\r\n"; | ||
if(part.file && part.content_type){ | ||
var append = function(data, filename){ | ||
var filename = path.basename(part.file); | ||
var data = fs.readFileSync(part.file); | ||
if(data){ | ||
return_part += "; filename=\"" + encodeURIComponent(filename) + "\"\r\n"; | ||
return_part += "Content-Type: " + part.content_type + "\r\n\r\n"; | ||
return_part += (part.content_type.indexOf('text') == -1) | ||
? data.toString('binary') | ||
: data.toString('utf8'); | ||
} | ||
return_part += "; filename=\"" + filename + "\"\r\n"; | ||
return_part += "Content-Type: " + part.content_type + "\r\n\r\n"; | ||
return_part += (part.content_type.indexOf('text') == -1) | ||
? data.toString('binary') | ||
: data.toString('utf8'); | ||
callback(null, return_part + '\r\n'); | ||
}; | ||
if((part.file || part.buffer) && part.content_type){ | ||
var filename = part.filename || part.file ? path.basename(part.file) : name; | ||
if(part.buffer) return append(part.buffer, filename); | ||
fs.readFile(part.file, function(err, data){ | ||
if(err) return callback(err); | ||
append(data, filename); | ||
}); | ||
} else { | ||
@@ -236,7 +269,6 @@ | ||
return_part += part.value; | ||
append(); | ||
} | ||
return return_part + '\r\n'; | ||
} | ||
@@ -243,0 +275,0 @@ |
{ | ||
"name": "needle" | ||
, "version": "0.2.0" | ||
, "version": "0.2.2" | ||
, "description": "Tiny yet featureful HTTP client. With deflate & multipart POST support." | ||
@@ -5,0 +5,0 @@ , "keywords": ["http", "https", "client", "multipart", "deflate", "timeout", "basic-auth", "simple"] |
Needle | ||
====== | ||
HTTP client for NodeJS. Supports HTTPS, basic authentication, proxied requests, nested params, multipart | ||
Async HTTP client for NodeJS. Supports HTTPS, basic authentication, proxied requests, multipart | ||
form uploads and gzip/deflate compression. Really simple stuff, around ~250 lines of code. | ||
@@ -17,15 +17,16 @@ | ||
client.delete(url, [options], callback); | ||
callback receives three arguments: (error, response, body) | ||
``` | ||
Callback receives three arguments: (error, response, body) | ||
Options | ||
------ | ||
- `timeout`: Returns error if response takes more than X. Defaults to `10000` (10 secs). | ||
- `compressed`: Whether to ask for a deflated or gzipped response or not. Defaults to `false`. | ||
- `timeout`: Returns error if response takes more than X. Defaults to `10000` (10 secs). | ||
- `parse`: Whether to parse XML or JSON response bodies automagically. Defaults to `true`. | ||
- `multipart`: Enables multipart/form-data encoding. Defaults to `false`. | ||
- `username`: For HTTP basic auth. | ||
- `password`: For HTTP basic auth. Requires username to be passed, obviously. | ||
- `parse`: Whether to parse XML or JSON response bodies automagically. Defaults to `true`. | ||
- `agent`: Uses an http.Agent of your choice, instead of the global (default) one. | ||
- `proxy`: Sends request via HTTP proxy. Eg. `proxy: 'http://proxy.server.com:3128'` | ||
@@ -109,3 +110,3 @@ | ||
### Multipart POST | ||
### Multipart POST: passing file path | ||
@@ -131,10 +132,27 @@ ``` js | ||
### Multipart POST 2: passing data buffer | ||
``` js | ||
var buffer = fs.readFileSync('/path/to/package.zip'); | ||
var data = { | ||
zip_file: { buffer: buffer, filename: 'mypackage.zip', content_type: 'application/octet-stream' }, | ||
} | ||
client.post('http://somewhere.com/over/the/rainbow', data, {multipart: true}, function(err, resp, body){ | ||
// if you see, when using buffers we need to pass the filename for the multipart body. | ||
// you can also pass a filename when using the file path method, in case you want to override | ||
// the default filename. | ||
}); | ||
``` | ||
Credits | ||
------- | ||
Written by Tomás Pollak. | ||
Written by Tomás Pollak, with the help of contributors. | ||
Legal | ||
Copyright | ||
----- | ||
(c) Copyright 2011 Fork Ltd. Licensed under the MIT license. | ||
(c) 2012 Fork Ltd. Licensed under the MIT license. |
@@ -45,2 +45,4 @@ // TODO: write specs. :) | ||
var black_pixel = new Buffer("data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=".replace(/^data:image\/\w+;base64,/, ""), "base64"); | ||
var data = { | ||
@@ -54,3 +56,4 @@ foo: 'bar', | ||
} | ||
} | ||
}, | ||
pixel: { filename:'black_pixel.gif', buffer: black_pixel, content_type: 'image/gif' }, | ||
} | ||
@@ -84,4 +87,5 @@ | ||
multipart_post(); | ||
break; | ||
default: | ||
console.log("Usage: ./test.js [get|auth|proxy|multipart]") | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
156
3
15160
7
296