superagent
Advanced tools
Comparing version 0.3.0 to 0.4.0
0.4.0 / 2012-03-04 | ||
================== | ||
* Added `.head()` method [browser]. Closes #78 | ||
* Added `make test-cov` support | ||
* Added multipart request support. Closes #11 | ||
* Added all methods that node supports. Closes #71 | ||
* Added "response" event providing a Response object. Closes #28 | ||
* Added `.query(obj)`. Closes #59 | ||
* Added `res.type` (browser). Closes #54 | ||
* Changed: default `res.body` and `res.files` to {} | ||
* Fixed: port existing query-string fix (browser). Closes #57 | ||
0.3.0 / 2012-01-24 | ||
@@ -3,0 +16,0 @@ ================== |
module.exports = require('./lib/node'); | ||
module.exports = process.env.SUPERAGENT_COV | ||
? require('./lib-cov/node') | ||
: require('./lib/node'); |
@@ -13,8 +13,13 @@ | ||
var Stream = require('stream').Stream | ||
, formidable = require('formidable') | ||
, Response = require('./response') | ||
, parse = require('url').parse | ||
, format = require('url').format | ||
, methods = require('./methods') | ||
, utils = require('./utils') | ||
, Part = require('./part') | ||
, mime = require('mime') | ||
, https = require('https') | ||
, http = require('http') | ||
, fs = require('fs') | ||
, qs = require('qs'); | ||
@@ -32,3 +37,3 @@ | ||
exports.version = '0.3.0'; | ||
exports.version = '0.4.0'; | ||
@@ -45,5 +50,19 @@ /** | ||
var noop = function(){}; | ||
function noop(){}; | ||
/** | ||
* Expose `Response`. | ||
*/ | ||
exports.Response = Response; | ||
/** | ||
* Define "form" mime type. | ||
*/ | ||
mime.define({ | ||
'application/x-www-form-urlencoded': ['form'] | ||
}); | ||
/** | ||
* Protocol map. | ||
@@ -70,15 +89,2 @@ */ | ||
/** | ||
* Default MIME type map. | ||
* | ||
* superagent.types.xml = 'application/xml'; | ||
* | ||
*/ | ||
exports.types = { | ||
html: 'text/html' | ||
, json: 'application/json' | ||
, form: 'application/x-www-form-urlencoded' | ||
}; | ||
/** | ||
* Default serialization map. | ||
@@ -100,3 +106,3 @@ * | ||
* | ||
* superagent.parse['application/xml'] = function(req, fn){ | ||
* superagent.parse['application/xml'] = function(res, fn){ | ||
* fn(null, result); | ||
@@ -107,130 +113,5 @@ * }; | ||
exports.parse = { | ||
'application/x-www-form-urlencoded': function(req, fn){ | ||
var buf = ''; | ||
req.setEncoding('ascii'); | ||
req.on('data', function(chunk){ buf += chunk; }); | ||
req.on('end', function(){ | ||
try { | ||
fn(null, qs.parse(buf)); | ||
} catch (err) { | ||
fn(err); | ||
} | ||
}); | ||
}, | ||
exports.parse = require('./parsers'); | ||
'application/json': function(req, fn){ | ||
var buf = ''; | ||
req.setEncoding('utf8'); | ||
req.on('data', function(chunk){ buf += chunk; }); | ||
req.on('end', function(){ | ||
try { | ||
fn(null, JSON.parse(buf)); | ||
} catch (err) { | ||
fn(err); | ||
} | ||
}); | ||
} | ||
}; | ||
/** | ||
* Initialize a new `Response` with the given `xhr`. | ||
* | ||
* - set flags (.ok, .error, etc) | ||
* - parse header | ||
* | ||
* @param {ClientRequest} req | ||
* @param {IncomingMessage} res | ||
* @param {Object} options | ||
* @api private | ||
*/ | ||
function Response(req, res, options) { | ||
options = options || {}; | ||
this.req = req; | ||
this.res = res; | ||
this.text = res.text; | ||
this.body = res.body; | ||
this.header = this.headers = res.headers; | ||
this.setStatusProperties(res.statusCode); | ||
this.setHeaderProperties(this.header); | ||
} | ||
/** | ||
* Set header related properties: | ||
* | ||
* - `.contentType` the content type without params | ||
* | ||
* A response of "Content-Type: text/plain; charset=utf-8" | ||
* will provide you with a `.contentType` of "text/plain". | ||
* | ||
* @param {Object} header | ||
* @api private | ||
*/ | ||
Response.prototype.setHeaderProperties = function(header){ | ||
// TODO: moar! | ||
// TODO: make this a util | ||
// content-type | ||
var ct = this.header['content-type'] || ''; | ||
this.type = utils.type(ct); | ||
// params | ||
var params = utils.params(ct); | ||
for (var key in params) this[key] = params[key]; | ||
}; | ||
/** | ||
* Set flags such as `.ok` based on `status`. | ||
* | ||
* For example a 2xx response will give you a `.ok` of __true__ | ||
* whereas 5xx will be __false__ and `.error` will be __true__. The | ||
* `.clientError` and `.serverError` are also available to be more | ||
* specific, and `.statusType` is the class of error ranging from 1..5 | ||
* sometimes useful for mapping respond colors etc. | ||
* | ||
* "sugar" properties are also defined for common cases. Currently providing: | ||
* | ||
* - .noContent | ||
* - .badRequest | ||
* - .unauthorized | ||
* - .notAcceptable | ||
* - .notFound | ||
* | ||
* @param {Number} status | ||
* @api private | ||
*/ | ||
Response.prototype.setStatusProperties = function(status){ | ||
var type = status / 100 | 0; | ||
// status / class | ||
this.status = this.statusCode = status; | ||
this.statusType = type; | ||
// basics | ||
this.info = 1 == type; | ||
this.ok = 2 == type; | ||
this.redirect = 3 == type; | ||
this.clientError = 4 == type; | ||
this.serverError = 5 == type; | ||
this.error = 4 == type || 5 == type; | ||
// sugar | ||
this.accepted = 202 == status; | ||
this.noContent = 204 == status; | ||
this.badRequest = 400 == status; | ||
this.unauthorized = 401 == status; | ||
this.notAcceptable = 406 == status; | ||
this.notFound = 404 == status; | ||
}; | ||
/** | ||
* Expose `Response`. | ||
*/ | ||
exports.Response = Response; | ||
/** | ||
* Initialize a new `Request` with the given `method` and `url`. | ||
@@ -253,4 +134,5 @@ * | ||
this._buffer = true; | ||
this.on('end', function(){ | ||
self.callback(new Response(self.req, self.res)); | ||
this.attachments = []; | ||
this.on('response', function(res){ | ||
self.callback(res); | ||
}); | ||
@@ -266,2 +148,21 @@ } | ||
/** | ||
* Queue the given `file` as an attachment | ||
* with optional `filename`. | ||
* | ||
* @param {String} file | ||
* @param {String} filename | ||
* @return {Request} for chaining | ||
* @api public | ||
*/ | ||
Request.prototype.attach = function(file, filename){ | ||
this.attachments.push({ | ||
name: file | ||
, part: new Part(this) | ||
, filename: filename | ||
}); | ||
return this; | ||
}; | ||
/** | ||
* Set the max redirects to `n`. | ||
@@ -334,8 +235,6 @@ * | ||
/** | ||
* Set Content-Type to `type`, mapping values from `exports.types`. | ||
* Set _Content-Type_ response header passed through `mime.lookup()`. | ||
* | ||
* Examples: | ||
* | ||
* superagent.types.xml = 'application/xml'; | ||
* | ||
* request.post('/') | ||
@@ -347,6 +246,11 @@ * .type('xml') | ||
* request.post('/') | ||
* .type('application/xml') | ||
* .send(xmlstring) | ||
* .type('json') | ||
* .send(jsonstring) | ||
* .end(callback); | ||
* | ||
* request.post('/') | ||
* .type('application/json') | ||
* .send(jsonstring) | ||
* .end(callback); | ||
* | ||
* @param {String} type | ||
@@ -358,3 +262,21 @@ * @return {Request} for chaining | ||
Request.prototype.type = function(type){ | ||
this.set('Content-Type', exports.types[type] || type); | ||
return this.set('Content-Type', ~type.indexOf('/') | ||
? type | ||
: mime.lookup(type)); | ||
}; | ||
/** | ||
* Add `obj` to the query-string, later formatted | ||
* in `.end()`. | ||
* | ||
* @param {Object} obj | ||
* @return {Request} for chaining | ||
* @api public | ||
*/ | ||
Request.prototype.query = function(obj){ | ||
this._query = this._query || {}; | ||
for (var key in obj) { | ||
this._query[key] = obj[key]; | ||
} | ||
return this; | ||
@@ -398,2 +320,3 @@ }; | ||
Request.prototype.send = function(data){ | ||
if ('GET' == this.method) return this.query(data); | ||
var obj = isObject(data); | ||
@@ -410,5 +333,8 @@ | ||
if ('GET' == this.method) return this; | ||
if (!obj) return this; | ||
// pre-defined type | ||
if (this.request().getHeader('Content-Type')) return this; | ||
// default to json | ||
this.type('json'); | ||
@@ -442,3 +368,3 @@ return this; | ||
this.preventBuffer(); | ||
return this.end().on('response', function(res){ | ||
return this.end().req.on('response', function(res){ | ||
res.pipe(stream, options); | ||
@@ -492,2 +418,18 @@ }); | ||
/** | ||
* Write the field `name` and `val`. | ||
* | ||
* @param {String} name | ||
* @param {String} val | ||
* @return {Request} for chaining | ||
* @api public | ||
*/ | ||
Request.prototype.field = function(name, val){ | ||
this.part() | ||
.name(name) | ||
.write(val); | ||
return this; | ||
}; | ||
/** | ||
* Return an http[s] request. | ||
@@ -503,3 +445,4 @@ * | ||
, options = this.options || {} | ||
, data = this._data || null | ||
, data = this._data | ||
, query = this._query | ||
, url = this.url; | ||
@@ -517,8 +460,12 @@ | ||
if (null == url.query) url.query = ''; | ||
// querystring | ||
if ('GET' == this.method && null != data) { | ||
options.path += '?' + qs.stringify(data); | ||
data = null; | ||
if (query) { | ||
query = qs.stringify(query); | ||
url.query += (url.query.length ? '&' : '') + query; | ||
} | ||
if (url.query.length) options.path += '?' + url.query; | ||
// initiate request | ||
@@ -554,5 +501,6 @@ var mod = exports.protocols[url.protocol]; | ||
var self = this | ||
, data = this._data || null | ||
, data = this._data | ||
, req = this.request() | ||
, buffer = this._buffer; | ||
, buffer = this._buffer | ||
, method = this.method; | ||
@@ -563,23 +511,23 @@ // store callback | ||
// body | ||
if ('GET' == this.method || 'HEAD' == this.method) { | ||
data = null; | ||
} else if (!req._headerSent) { | ||
// serialize stuff | ||
var serialize = exports.serialize[req.getHeader('Content-Type')]; | ||
if (serialize) data = serialize(data); | ||
switch (method) { | ||
case 'GET': | ||
case 'HEAD': | ||
break; | ||
default: | ||
if (req._headerSent) break; | ||
// serialize stuff | ||
var serialize = exports.serialize[req.getHeader('Content-Type')]; | ||
if (serialize) data = serialize(data); | ||
// content-length | ||
if (null != data && !req.getHeader('Content-Length')) { | ||
this.set('Content-Length', data.length); | ||
} | ||
// content-length | ||
if (data && !req.getHeader('Content-Length')) { | ||
this.set('Content-Length', data.length); | ||
} | ||
} | ||
// multi-part boundary | ||
if (this._boundary) { | ||
req.write('\r\n--' + this._boundary + '--'); | ||
} | ||
// response | ||
req.on('response', function(res){ | ||
var max = self._maxRedirects; | ||
var max = self._maxRedirects | ||
, type = res.headers['content-type'] || '' | ||
, multipart = ~type.indexOf('multipart'); | ||
@@ -591,5 +539,2 @@ // redirect | ||
// response event | ||
self.emit('response', res); | ||
// zlib support | ||
@@ -600,2 +545,22 @@ if (/^(deflate|gzip)$/.test(res.headers['content-encoding'])) { | ||
// don't buffer multipart | ||
if (multipart) buffer = false; | ||
// TODO: make all parsers take callbacks | ||
if (multipart) { | ||
var form = new formidable.IncomingForm; | ||
form.parse(res, function(err, fields, files){ | ||
if (err) throw err; | ||
// TODO: handle error | ||
// TODO: emit formidable events, parse json etc | ||
var response = new Response(req, res); | ||
response.body = fields; | ||
response.files = files; | ||
self.emit('end'); | ||
self.callback(response); | ||
}); | ||
return; | ||
} | ||
// buffered response | ||
@@ -620,5 +585,14 @@ // TODO: optional | ||
self.res = res; | ||
res.on('end', function(){ self.emit('end'); }); | ||
res.on('end', function(){ | ||
// TODO: unless buffering emit earlier to stream | ||
self.emit('response', new Response(self.req, self.res)); | ||
self.emit('end'); | ||
}); | ||
}); | ||
if (this.attachments.length) return this.writeAttachments(); | ||
// multi-part boundary | ||
if (this._boundary) this.writeFinalBoundary(); | ||
req.end(data); | ||
@@ -629,2 +603,52 @@ return this; | ||
/** | ||
* Write the final boundary. | ||
* | ||
* @api private | ||
*/ | ||
Request.prototype.writeFinalBoundary = function(){ | ||
this.request().write('\r\n--' + this._boundary + '--'); | ||
}; | ||
/** | ||
* Write the attachments in sequence. | ||
* | ||
* @api private | ||
*/ | ||
Request.prototype.writeAttachments = function(){ | ||
var files = this.attachments | ||
, req = this.request() | ||
, self = this; | ||
function next() { | ||
var file = files.shift(); | ||
if (!file) { | ||
self.writeFinalBoundary(); | ||
return req.end(); | ||
} | ||
// custom filename | ||
if (file.filename) { | ||
file.part.type(file.name); | ||
file.part.set('Content-Disposition', 'attachment; filename="' + file.filename + '"'); | ||
} else { | ||
file.part.filename(file.name); | ||
} | ||
var stream = fs.createReadStream(file.name); | ||
// TODO: pipe | ||
// TODO: handle errors | ||
stream.on('data', function(data){ | ||
file.part.write(data); | ||
}).on('error', function(err){ | ||
self.emit('error', err); | ||
}).on('end', next); | ||
} | ||
next(); | ||
}; | ||
/** | ||
* Expose `Request`. | ||
@@ -664,64 +688,15 @@ */ | ||
/** | ||
* GET `url` with optional callback `fn(err, res)`. | ||
* | ||
* @param {String} url | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
// generate HTTP verb methods | ||
request.get = function(url, fn){ | ||
var req = request('GET', url); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
methods.forEach(function(method){ | ||
var name = 'delete' == method | ||
? 'del' | ||
: method; | ||
/** | ||
* DELETE `url` with optional callback `fn(err, res)`. | ||
* | ||
* @param {String} url | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
request.del = function(url, fn){ | ||
var req = request('DELETE', url); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
/** | ||
* POST `url` with optional `data` and callback `fn(err, res)`. | ||
* | ||
* @param {String} url | ||
* @param {Mixed} data | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
request.post = function(url, data, fn){ | ||
var req = request('POST', url); | ||
if (data) req.send(data); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
/** | ||
* PUT `url` with optional `data` and callback `fn(err, res)`. | ||
* | ||
* @param {String} url | ||
* @param {Mixed} data | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
request.put = function(url, data, fn){ | ||
var req = request('PUT', url); | ||
if (data) req.send(data); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
method = method.toUpperCase(); | ||
request[name] = function(url, fn){ | ||
var req = request(method, url); | ||
fn && req.end(fn); | ||
return req; | ||
}; | ||
}); |
@@ -13,3 +13,6 @@ | ||
var utils = require('./utils') | ||
, Stream = require('stream').Stream; | ||
, Stream = require('stream').Stream | ||
, mime = require('mime') | ||
, path = require('path') | ||
, basename = path.basename; | ||
@@ -52,3 +55,3 @@ /** | ||
var boundary = utils.uid(32); | ||
this.req.set('Content-Type', 'multipart/form-data; boundary="' + boundary + '"'); | ||
this.req.set('Content-Type', 'multipart/form-data; boundary=' + boundary); | ||
this.req._boundary = boundary; | ||
@@ -68,3 +71,9 @@ }; | ||
if (!this._boundary) { | ||
this.request.write('\r\n--' + this.req._boundary + '\r\n'); | ||
// TODO: formidable bug | ||
if (!this.request._hasFirstBoundary) { | ||
this.request.write('--' + this.req._boundary + '\r\n'); | ||
this.request._hasFirstBoundary = true; | ||
} else { | ||
this.request.write('\r\n--' + this.req._boundary + '\r\n'); | ||
} | ||
this._boundary = true; | ||
@@ -77,2 +86,47 @@ } | ||
/** | ||
* Set _Content-Type_ response header passed through `mime.lookup()`. | ||
* | ||
* Examples: | ||
* | ||
* res.type('html'); | ||
* res.type('.html'); | ||
* | ||
* @param {String} type | ||
* @return {Part} | ||
* @api public | ||
*/ | ||
Part.prototype.type = function(type){ | ||
return this.set('Content-Type', mime.lookup(type)); | ||
}; | ||
/** | ||
* Set _Content-Disposition_ header field to _form-data_ | ||
* and set the _name_ param to the given string. | ||
* | ||
* @param {String} name | ||
* @return {Part} | ||
* @api public | ||
*/ | ||
Part.prototype.name = function(name){ | ||
this.set('Content-Disposition', 'form-data; name="' + name + '"'); | ||
return this; | ||
}; | ||
/** | ||
* Set _Content-Disposition_ header field to _attachment_ with `filename`. | ||
* | ||
* @param {String} filename | ||
* @return {Part} | ||
* @api public | ||
*/ | ||
Part.prototype.filename = function(filename){ | ||
this.type(filename); | ||
this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"'); | ||
return this; | ||
}; | ||
/** | ||
* Write `data` with `encoding`. | ||
@@ -95,21 +149,11 @@ * | ||
/** | ||
* Return a new `Part`. | ||
* End the part. | ||
* | ||
* @return {Part} | ||
* @api public | ||
*/ | ||
Part.prototype.part = function(){ | ||
return this.req.part(); | ||
Part.prototype.end = function(){ | ||
this.emit('end'); | ||
this.complete = true; | ||
}; | ||
/** | ||
* End the request. | ||
* | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
Part.prototype.end = function(fn){ | ||
return this.req.end(fn); | ||
}; |
@@ -207,2 +207,33 @@ | ||
/** | ||
* Return the mime type for the given `str`. | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
* @api private | ||
*/ | ||
function type(str){ | ||
return str.split(/ *; */).shift(); | ||
}; | ||
/** | ||
* Return header field parameters. | ||
* | ||
* @param {String} str | ||
* @return {Object} | ||
* @api private | ||
*/ | ||
function params(str){ | ||
return str.split(/ *; */).reduce(function(obj, str){ | ||
var parts = str.split(/ *= */) | ||
, key = parts.shift() | ||
, val = parts.shift(); | ||
if (key && val) obj[key] = val; | ||
return obj; | ||
}, {}); | ||
}; | ||
/** | ||
* Initialize a new `Response` with the given `xhr`. | ||
@@ -266,6 +297,6 @@ * | ||
* | ||
* - `.contentType` the content type without params | ||
* - `.type` the content type without params | ||
* | ||
* A response of "Content-Type: text/plain; charset=utf-8" | ||
* will provide you with a `.contentType` of "text/plain". | ||
* will provide you with a `.type` of "text/plain". | ||
* | ||
@@ -277,24 +308,9 @@ * @param {Object} header | ||
Response.prototype.setHeaderProperties = function(header){ | ||
// TODO: moar! | ||
var params = (this.header['content-type'] || '').split(/ *; */); | ||
this.contentType = params.shift(); | ||
this.setParams(params); | ||
}; | ||
// content-type | ||
var ct = this.header['content-type'] || ''; | ||
this.type = type(ct); | ||
/** | ||
* Create properties from `params`. | ||
* | ||
* For example "Content-Type: text/plain; charset=utf-8" | ||
* would provide `.charset` "utf-8". | ||
* | ||
* @param {Array} params | ||
* @api private | ||
*/ | ||
Response.prototype.setParams = function(params){ | ||
var param; | ||
for (var i = 0, len = params.length; i < len; ++i) { | ||
param = params[i].split(/ *= */); | ||
this[param[0]] = param[1]; | ||
} | ||
// params | ||
var obj = params(ct); | ||
for (var key in obj) this[key] = obj[key]; | ||
}; | ||
@@ -314,3 +330,3 @@ | ||
Response.prototype.parseBody = function(str){ | ||
var parse = exports.parse[this.contentType]; | ||
var parse = exports.parse[this.type]; | ||
return parse | ||
@@ -457,2 +473,19 @@ ? parse(str) | ||
/** | ||
* Add `obj` to the query-string, later formatted | ||
* in `.end()`. | ||
* | ||
* @param {Object} obj | ||
* @return {Request} for chaining | ||
* @api public | ||
*/ | ||
Request.prototype.query = function(obj){ | ||
this._query = this._query || {}; | ||
for (var key in obj) { | ||
this._query[key] = obj[key]; | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Send `data`, defaulting the `.type()` to "json" when | ||
@@ -504,2 +537,3 @@ * an object is given. | ||
Request.prototype.send = function(data){ | ||
if ('GET' == this.method) return this.query(data); | ||
var obj = isObject(data); | ||
@@ -516,3 +550,2 @@ | ||
if ('GET' == this.method) return this; | ||
if (!obj) return this; | ||
@@ -536,3 +569,4 @@ if (this.header['content-type']) return this; | ||
, xhr = this.xhr = getXHR() | ||
, data = this._data || null; | ||
, query = this._query | ||
, data = this._data; | ||
@@ -548,5 +582,7 @@ // store callback | ||
// querystring | ||
if ('GET' == this.method && null != data) { | ||
this.url += '?' + exports.serializeObject(data); | ||
data = null; | ||
if (query) { | ||
query = exports.serializeObject(query); | ||
this.url += ~this.url.indexOf('?') | ||
? '&' + query | ||
: '?' + query; | ||
} | ||
@@ -628,2 +664,20 @@ | ||
/** | ||
* GET `url` with optional callback `fn(res)`. | ||
* | ||
* @param {String} url | ||
* @param {Mixed} data | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
request.head = function(url, data, fn){ | ||
var req = request('HEAD', url); | ||
if (isFunction(data)) fn = data, data = null; | ||
if (data) req.send(data); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
/** | ||
* DELETE `url` with optional callback `fn(res)`. | ||
@@ -630,0 +684,0 @@ * |
{ | ||
"name": "superagent" | ||
, "version": "0.3.0" | ||
, "version": "0.4.0" | ||
, "description": "elegant progressive ajax client" | ||
, "keywords": ["http", "ajax", "request", "agent"] | ||
, "author": "TJ Holowaychuk <tj@vision-media.ca>" | ||
, "dependencies": { "qs": "0.3.2" } | ||
, "dependencies": { | ||
"qs": "0.4.2" | ||
, "formidable": "1.0.9" | ||
, "mime": "1.2.5" | ||
} | ||
, "devDependencies": { | ||
@@ -9,0 +13,0 @@ "express": "2.5.x" |
# SuperAgent | ||
SuperAgent is an elegant, small (~1.7kb compressed), progressive client-side HTTP request library. View the [docs](http://visionmedia.github.com/superagent/). | ||
SuperAgent is a small progressive client-side HTTP request library, and Node.js module with the same API, sporting many high-level HTTP client features. View the [docs](http://visionmedia.github.com/superagent/). | ||
@@ -56,3 +56,3 @@ ![super agent](http://f.cl.ly/items/3d282n3A0h0Z0K2w0q2a/Screenshot.png) | ||
.post('/api/pet') | ||
.data({ name: 'Manny', species: 'cat' }) | ||
.send({ name: 'Manny', species: 'cat' }) | ||
.set('X-API-Key', 'foobar') | ||
@@ -59,0 +59,0 @@ .set('Accept', 'application/json') |
@@ -382,2 +382,33 @@ | ||
/** | ||
* Return the mime type for the given `str`. | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
* @api private | ||
*/ | ||
function type(str){ | ||
return str.split(/ *; */).shift(); | ||
}; | ||
/** | ||
* Return header field parameters. | ||
* | ||
* @param {String} str | ||
* @return {Object} | ||
* @api private | ||
*/ | ||
function params(str){ | ||
return str.split(/ *; */).reduce(function(obj, str){ | ||
var parts = str.split(/ *= */) | ||
, key = parts.shift() | ||
, val = parts.shift(); | ||
if (key && val) obj[key] = val; | ||
return obj; | ||
}, {}); | ||
}; | ||
/** | ||
* Initialize a new `Response` with the given `xhr`. | ||
@@ -441,6 +472,6 @@ * | ||
* | ||
* - `.contentType` the content type without params | ||
* - `.type` the content type without params | ||
* | ||
* A response of "Content-Type: text/plain; charset=utf-8" | ||
* will provide you with a `.contentType` of "text/plain". | ||
* will provide you with a `.type` of "text/plain". | ||
* | ||
@@ -452,24 +483,9 @@ * @param {Object} header | ||
Response.prototype.setHeaderProperties = function(header){ | ||
// TODO: moar! | ||
var params = (this.header['content-type'] || '').split(/ *; */); | ||
this.contentType = params.shift(); | ||
this.setParams(params); | ||
}; | ||
// content-type | ||
var ct = this.header['content-type'] || ''; | ||
this.type = type(ct); | ||
/** | ||
* Create properties from `params`. | ||
* | ||
* For example "Content-Type: text/plain; charset=utf-8" | ||
* would provide `.charset` "utf-8". | ||
* | ||
* @param {Array} params | ||
* @api private | ||
*/ | ||
Response.prototype.setParams = function(params){ | ||
var param; | ||
for (var i = 0, len = params.length; i < len; ++i) { | ||
param = params[i].split(/ *= */); | ||
this[param[0]] = param[1]; | ||
} | ||
// params | ||
var obj = params(ct); | ||
for (var key in obj) this[key] = obj[key]; | ||
}; | ||
@@ -489,3 +505,3 @@ | ||
Response.prototype.parseBody = function(str){ | ||
var parse = exports.parse[this.contentType]; | ||
var parse = exports.parse[this.type]; | ||
return parse | ||
@@ -632,2 +648,19 @@ ? parse(str) | ||
/** | ||
* Add `obj` to the query-string, later formatted | ||
* in `.end()`. | ||
* | ||
* @param {Object} obj | ||
* @return {Request} for chaining | ||
* @api public | ||
*/ | ||
Request.prototype.query = function(obj){ | ||
this._query = this._query || {}; | ||
for (var key in obj) { | ||
this._query[key] = obj[key]; | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Send `data`, defaulting the `.type()` to "json" when | ||
@@ -679,2 +712,3 @@ * an object is given. | ||
Request.prototype.send = function(data){ | ||
if ('GET' == this.method) return this.query(data); | ||
var obj = isObject(data); | ||
@@ -691,3 +725,2 @@ | ||
if ('GET' == this.method) return this; | ||
if (!obj) return this; | ||
@@ -711,3 +744,4 @@ if (this.header['content-type']) return this; | ||
, xhr = this.xhr = getXHR() | ||
, data = this._data || null; | ||
, query = this._query | ||
, data = this._data; | ||
@@ -723,5 +757,7 @@ // store callback | ||
// querystring | ||
if ('GET' == this.method && null != data) { | ||
this.url += '?' + exports.serializeObject(data); | ||
data = null; | ||
if (query) { | ||
query = exports.serializeObject(query); | ||
this.url += ~this.url.indexOf('?') | ||
? '&' + query | ||
: '?' + query; | ||
} | ||
@@ -803,2 +839,20 @@ | ||
/** | ||
* GET `url` with optional callback `fn(res)`. | ||
* | ||
* @param {String} url | ||
* @param {Mixed} data | ||
* @param {Function} fn | ||
* @return {Request} | ||
* @api public | ||
*/ | ||
request.head = function(url, data, fn){ | ||
var req = request('HEAD', url); | ||
if (isFunction(data)) fn = data, data = null; | ||
if (data) req.send(data); | ||
if (fn) req.end(fn); | ||
return req; | ||
}; | ||
/** | ||
* DELETE `url` with optional callback `fn(res)`. | ||
@@ -805,0 +859,0 @@ * |
@@ -5,2 +5,2 @@ /** | ||
* Check if `obj` is an array. | ||
*/function isArray(obj){return"[object Array]"=={}.toString.call(obj)}function EventEmitter(){}EventEmitter.prototype.on=function(name,fn){this.$events||(this.$events={}),this.$events[name]?isArray(this.$events[name])?this.$events[name].push(fn):this.$events[name]=[this.$events[name],fn]:this.$events[name]=fn;return this},EventEmitter.prototype.addListener=EventEmitter.prototype.on,EventEmitter.prototype.once=function(name,fn){var self=this;function on(){self.removeListener(name,on),fn.apply(this,arguments)}on.listener=fn,this.on(name,on);return this},EventEmitter.prototype.removeListener=function(name,fn){if(this.$events&&this.$events[name]){var list=this.$events[name];if(isArray(list)){var pos=-1;for(var i=0,l=list.length;i<l;i++)if(list[i]===fn||list[i].listener&&list[i].listener===fn){pos=i;break}if(pos<0)return this;list.splice(pos,1),list.length||delete this.$events[name]}else(list===fn||list.listener&&list.listener===fn)&&delete this.$events[name]}return this},EventEmitter.prototype.removeAllListeners=function(name){if(name===undefined){this.$events={};return this}this.$events&&this.$events[name]&&(this.$events[name]=null);return this},EventEmitter.prototype.listeners=function(name){this.$events||(this.$events={}),this.$events[name]||(this.$events[name]=[]),isArray(this.$events[name])||(this.$events[name]=[this.$events[name]]);return this.$events[name]},EventEmitter.prototype.emit=function(name){if(!this.$events)return!1;var handler=this.$events[name];if(!handler)return!1;var args=[].slice.call(arguments,1);if("function"==typeof handler)handler.apply(this,args);else{if(!isArray(handler))return!1;var listeners=handler.slice();for(var i=0,l=listeners.length;i<l;i++)listeners[i].apply(this,args)}return!0};var superagent=function(exports){exports=request,exports.version="0.3.0";var noop=function(){};function getXHR(){if(window.XMLHttpRequest&&("file:"!=window.location.protocol||!window.ActiveXObject))return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(e){}return!1}var trim="".trim?function(s){return s.trim()}:function(s){return s.replace(/(^\s*|\s*$)/g,"")};function isFunction(obj){return"function"==typeof obj}function isObject(obj){return null!=obj&&"object"==typeof obj}function serialize(obj){if(!isObject(obj))return obj;var pairs=[];for(var key in obj)pairs.push(encodeURIComponent(key)+"="+encodeURIComponent(obj[key]));return pairs.join("&")}exports.serializeObject=serialize;function parseString(str){var obj={},pairs=str.split("&"),parts,pair;for(var i=0,len=pairs.length;i<len;++i)pair=pairs[i],parts=pair.split("="),obj[decodeURIComponent(parts[0])]=decodeURIComponent(parts[1]);return obj}exports.parseString=parseString,exports.types={html:"text/html",json:"application/json",urlencoded:"application/x-www-form-urlencoded","form-data":"application/x-www-form-urlencoded"},exports.serialize={"application/x-www-form-urlencoded":serialize,"application/json":JSON.stringify},exports.parse={"application/x-www-form-urlencoded":parseString,"application/json":JSON.parse};function parseHeader(str){var lines=str.split(/\r?\n/),fields={},index,line,field,val;lines.pop();for(var i=0,len=lines.length;i<len;++i)line=lines[i],index=line.indexOf(":"),field=line.slice(0,index).toLowerCase(),val=trim(line.slice(index+1)),fields[field]=val;return fields}function Response(xhr,options){options=options||{},this.xhr=xhr,this.text=xhr.responseText,this.setStatusProperties(xhr.status),this.header=parseHeader(xhr.getAllResponseHeaders()),this.setHeaderProperties(this.header),this.body=this.parseBody(this.text)}Response.prototype.setHeaderProperties=function(header){var params=(this.header["content-type"]||"").split(/ *; */);this.contentType=params.shift(),this.setParams(params)},Response.prototype.setParams=function(params){var param;for(var i=0,len=params.length;i<len;++i)param=params[i].split(/ *= */),this[param[0]]=param[1]},Response.prototype.parseBody=function(str){var parse=exports.parse[this.contentType];return parse?parse(str):null},Response.prototype.setStatusProperties=function(status){var type=status/100|0;this.status=status,this.statusType=type,this.info=1==type,this.ok=2==type,this.clientError=4==type,this.serverError=5==type,this.error=4==type||5==type,this.accepted=202==status,this.noContent=204==status||1223==status,this.badRequest=400==status,this.unauthorized=401==status,this.notAcceptable=406==status,this.notFound=404==status},exports.Response=Response;function Request(method,url){var self=this;EventEmitter.call(this),this.method=method,this.url=url,this.header={},this.set("X-Requested-With","XMLHttpRequest"),this.on("end",function(){self.callback(new Response(self.xhr))})}Request.prototype=new EventEmitter,Request.prototype.constructor=Request,Request.prototype.set=function(field,val){if(isObject(field)){for(var key in field)this.set(key,field[key]);return this}this.header[field.toLowerCase()]=val;return this},Request.prototype.type=function(type){this.set("Content-Type",exports.types[type]||type);return this},Request.prototype.send=function(data){var obj=isObject(data);if(obj&&isObject(this._data))for(var key in data)this._data[key]=data[key];else this._data=data;if("GET"==this.method)return this;if(!obj)return this;if(this.header["content-type"])return this;this.type("json");return this},Request.prototype.end=function(fn){var self=this,xhr=this.xhr=getXHR(),data=this._data||null;this.callback=fn||noop,xhr.onreadystatechange=function(){4==xhr.readyState&&self.emit("end")},"GET"==this.method&&null!=data&&(this.url+="?"+exports.serializeObject(data),data=null),xhr.open(this.method,this.url,!0);if("GET"!=this.method&&"HEAD"!=this.method){var serialize=exports.serialize[this.header["content-type"]];serialize&&(data=serialize(data))}for(var field in this.header)xhr.setRequestHeader(field,this.header[field]);xhr.send(data);return this},exports.Request=Request;function request(method,url){if("function"==typeof url)return(new Request("GET",method)).end(url);if(1==arguments.length)return new Request("GET",method);return new Request(method,url)}request.get=function(url,data,fn){var req=request("GET",url);isFunction(data)&&(fn=data,data=null),data&&req.send(data),fn&&req.end(fn);return req},request.del=function(url,fn){var req=request("DELETE",url);fn&&req.end(fn);return req},request.post=function(url,data,fn){var req=request("POST",url);data&&req.send(data),fn&&req.end(fn);return req},request.put=function(url,data,fn){var req=request("PUT",url);data&&req.send(data),fn&&req.end(fn);return req};return exports}({}) | ||
*/function isArray(obj){return"[object Array]"=={}.toString.call(obj)}function EventEmitter(){}EventEmitter.prototype.on=function(name,fn){return this.$events||(this.$events={}),this.$events[name]?isArray(this.$events[name])?this.$events[name].push(fn):this.$events[name]=[this.$events[name],fn]:this.$events[name]=fn,this},EventEmitter.prototype.addListener=EventEmitter.prototype.on,EventEmitter.prototype.once=function(name,fn){var self=this;function on(){self.removeListener(name,on),fn.apply(this,arguments)}return on.listener=fn,this.on(name,on),this},EventEmitter.prototype.removeListener=function(name,fn){if(this.$events&&this.$events[name]){var list=this.$events[name];if(isArray(list)){var pos=-1;for(var i=0,l=list.length;i<l;i++)if(list[i]===fn||list[i].listener&&list[i].listener===fn){pos=i;break}if(pos<0)return this;list.splice(pos,1),list.length||delete this.$events[name]}else(list===fn||list.listener&&list.listener===fn)&&delete this.$events[name]}return this},EventEmitter.prototype.removeAllListeners=function(name){return name===undefined?(this.$events={},this):(this.$events&&this.$events[name]&&(this.$events[name]=null),this)},EventEmitter.prototype.listeners=function(name){return this.$events||(this.$events={}),this.$events[name]||(this.$events[name]=[]),isArray(this.$events[name])||(this.$events[name]=[this.$events[name]]),this.$events[name]},EventEmitter.prototype.emit=function(name){if(!this.$events)return!1;var handler=this.$events[name];if(!handler)return!1;var args=[].slice.call(arguments,1);if("function"==typeof handler)handler.apply(this,args);else{if(!isArray(handler))return!1;var listeners=handler.slice();for(var i=0,l=listeners.length;i<l;i++)listeners[i].apply(this,args)}return!0};var superagent=function(exports){exports=request,exports.version="0.3.0";var noop=function(){};function getXHR(){if(window.XMLHttpRequest&&("file:"!=window.location.protocol||!window.ActiveXObject))return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(e){}return!1}var trim="".trim?function(s){return s.trim()}:function(s){return s.replace(/(^\s*|\s*$)/g,"")};function isFunction(obj){return"function"==typeof obj}function isObject(obj){return null!=obj&&"object"==typeof obj}function serialize(obj){if(!isObject(obj))return obj;var pairs=[];for(var key in obj)pairs.push(encodeURIComponent(key)+"="+encodeURIComponent(obj[key]));return pairs.join("&")}exports.serializeObject=serialize;function parseString(str){var obj={},pairs=str.split("&"),parts,pair;for(var i=0,len=pairs.length;i<len;++i)pair=pairs[i],parts=pair.split("="),obj[decodeURIComponent(parts[0])]=decodeURIComponent(parts[1]);return obj}exports.parseString=parseString,exports.types={html:"text/html",json:"application/json",urlencoded:"application/x-www-form-urlencoded","form-data":"application/x-www-form-urlencoded"},exports.serialize={"application/x-www-form-urlencoded":serialize,"application/json":JSON.stringify},exports.parse={"application/x-www-form-urlencoded":parseString,"application/json":JSON.parse};function parseHeader(str){var lines=str.split(/\r?\n/),fields={},index,line,field,val;lines.pop();for(var i=0,len=lines.length;i<len;++i)line=lines[i],index=line.indexOf(":"),field=line.slice(0,index).toLowerCase(),val=trim(line.slice(index+1)),fields[field]=val;return fields}function type(str){return str.split(/ *; */).shift()}function params(str){return str.split(/ *; */).reduce(function(obj,str){var parts=str.split(/ *= */),key=parts.shift(),val=parts.shift();return key&&val&&(obj[key]=val),obj},{})}function Response(xhr,options){options=options||{},this.xhr=xhr,this.text=xhr.responseText,this.setStatusProperties(xhr.status),this.header=parseHeader(xhr.getAllResponseHeaders()),this.setHeaderProperties(this.header),this.body=this.parseBody(this.text)}Response.prototype.setHeaderProperties=function(header){var ct=this.header["content-type"]||"";this.type=type(ct);var obj=params(ct);for(var key in obj)this[key]=obj[key]},Response.prototype.parseBody=function(str){var parse=exports.parse[this.type];return parse?parse(str):null},Response.prototype.setStatusProperties=function(status){var type=status/100|0;this.status=status,this.statusType=type,this.info=1==type,this.ok=2==type,this.clientError=4==type,this.serverError=5==type,this.error=4==type||5==type,this.accepted=202==status,this.noContent=204==status||1223==status,this.badRequest=400==status,this.unauthorized=401==status,this.notAcceptable=406==status,this.notFound=404==status},exports.Response=Response;function Request(method,url){var self=this;EventEmitter.call(this),this.method=method,this.url=url,this.header={},this.set("X-Requested-With","XMLHttpRequest"),this.on("end",function(){self.callback(new Response(self.xhr))})}Request.prototype=new EventEmitter,Request.prototype.constructor=Request,Request.prototype.set=function(field,val){if(isObject(field)){for(var key in field)this.set(key,field[key]);return this}return this.header[field.toLowerCase()]=val,this},Request.prototype.type=function(type){return this.set("Content-Type",exports.types[type]||type),this},Request.prototype.query=function(obj){this._query=this._query||{};for(var key in obj)this._query[key]=obj[key];return this},Request.prototype.send=function(data){if("GET"==this.method)return this.query(data);var obj=isObject(data);if(obj&&isObject(this._data))for(var key in data)this._data[key]=data[key];else this._data=data;return obj?this.header["content-type"]?this:(this.type("json"),this):this},Request.prototype.end=function(fn){var self=this,xhr=this.xhr=getXHR(),query=this._query,data=this._data;this.callback=fn||noop,xhr.onreadystatechange=function(){4==xhr.readyState&&self.emit("end")},query&&(query=exports.serializeObject(query),this.url+=~this.url.indexOf("?")?"&"+query:"?"+query),xhr.open(this.method,this.url,!0);if("GET"!=this.method&&"HEAD"!=this.method){var serialize=exports.serialize[this.header["content-type"]];serialize&&(data=serialize(data))}for(var field in this.header)xhr.setRequestHeader(field,this.header[field]);return xhr.send(data),this},exports.Request=Request;function request(method,url){return"function"==typeof url?(new Request("GET",method)).end(url):1==arguments.length?new Request("GET",method):new Request(method,url)}return request.get=function(url,data,fn){var req=request("GET",url);return isFunction(data)&&(fn=data,data=null),data&&req.send(data),fn&&req.end(fn),req},request.head=function(url,data,fn){var req=request("HEAD",url);return isFunction(data)&&(fn=data,data=null),data&&req.send(data),fn&&req.end(fn),req},request.del=function(url,fn){var req=request("DELETE",url);return fn&&req.end(fn),req},request.post=function(url,data,fn){var req=request("POST",url);return data&&req.send(data),fn&&req.end(fn),req},request.put=function(url,data,fn){var req=request("PUT",url);return data&&req.send(data),fn&&req.end(fn),req},exports}({}); |
@@ -40,4 +40,2 @@ | ||
// TODO: "response" event should be a Response | ||
describe('request', function(){ | ||
@@ -55,2 +53,12 @@ describe('with an object', function(){ | ||
describe('with a callback', function(){ | ||
it('should invoke .end()', function(done){ | ||
request | ||
.get('localhost:3000/login', function(res){ | ||
assert(res.status == 200); | ||
done(); | ||
}) | ||
}) | ||
}) | ||
describe('without a schema', function(){ | ||
@@ -167,3 +175,3 @@ it('should default to http', function(done){ | ||
.end(function(res){ | ||
res.should.be.json | ||
res.should.be.json; | ||
done(); | ||
@@ -170,0 +178,0 @@ }); |
@@ -18,7 +18,2 @@ | ||
it('should expose .types', function(){ | ||
Object.keys(request.types) | ||
.should.eql(['html', 'json', 'form']); | ||
}) | ||
it('should expose .serialize', function(){ | ||
@@ -25,0 +20,0 @@ Object.keys(request.serialize) |
@@ -19,4 +19,2 @@ | ||
// TODO: "response" event should be a Response | ||
describe('req.send(Object) as "form"', function(){ | ||
@@ -23,0 +21,0 @@ describe('with req.type() set to form', function(){ |
@@ -19,4 +19,2 @@ | ||
// TODO: "response" event should be a Response | ||
describe('req.send(Object) as "json"', function(){ | ||
@@ -23,0 +21,0 @@ it('should default to json', function(done){ |
@@ -8,2 +8,6 @@ | ||
function read(file) { | ||
return fs.readFileSync(file, 'utf8'); | ||
} | ||
app.post('/echo', function(req, res){ | ||
@@ -20,4 +24,2 @@ res.writeHead(200, req.headers); | ||
// TODO: "response" event should be a Response | ||
describe('Request', function(){ | ||
@@ -32,92 +34,94 @@ describe('#part()', function(){ | ||
}) | ||
}) | ||
describe('Part', function(){ | ||
describe('#pipe()', function(){ | ||
describe('with a single part', function(){ | ||
it('should construct a multipart request', function(){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
it('should default res.files to {}', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
req | ||
.part() | ||
.set('Content-Type', 'image/png') | ||
.write('some image data'); | ||
req.end(function(res){ | ||
res.files.should.eql({}); | ||
res.body.should.eql({}); | ||
done(); | ||
}); | ||
}) | ||
req.end(function(res){ | ||
var ct = res.header['content-type']; | ||
ct.should.include('multipart/form-data; boundary="'); | ||
describe('#field(name, value)', function(){ | ||
it('should set a multipart field value', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
var body = '\r\n'; | ||
body += '--' + boundary(ct) + '\r\n'; | ||
body += 'Content-Type: image/png\r\n'; | ||
body += '\r\n'; | ||
body += 'some image data'; | ||
body += '\r\n--' + boundary(ct) + '--'; | ||
req.field('user[name]', 'tobi'); | ||
req.field('user[age]', '2'); | ||
req.field('user[species]', 'ferret'); | ||
assert(body == res.text, 'invalid multipart response'); | ||
}); | ||
req.end(function(res){ | ||
res.body['user[name]'].should.equal('tobi'); | ||
res.body['user[age]'].should.equal('2'); | ||
res.body['user[species]'].should.equal('ferret'); | ||
done(); | ||
}); | ||
}) | ||
it('should work with file attachments', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
req.field('name', 'Tobi'); | ||
req.attach('test/node/fixtures/user.html', 'document'); | ||
req.field('species', 'ferret'); | ||
req.end(function(res){ | ||
res.body.name.should.equal('Tobi'); | ||
res.body.species.should.equal('ferret'); | ||
var html = res.files.document; | ||
html.name.should.equal('document'); | ||
html.type.should.equal('text/html'); | ||
read(html.path).should.equal('<h1>name</h1>'); | ||
done(); | ||
}) | ||
}) | ||
}) | ||
describe('with several parts', function(){ | ||
it('should construct a multipart request', function(done){ | ||
describe('#attach(file)', function(){ | ||
it('should attach a file', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
var req = request.post('http://localhost:3005/echo'); | ||
req.attach('test/node/fixtures/user.html'); | ||
req.attach('test/node/fixtures/user.json'); | ||
req.attach('test/node/fixtures/user.txt'); | ||
req.part() | ||
.set('Content-Type', 'image/png') | ||
.set('Content-Disposition', 'attachment') | ||
.write('some image data'); | ||
req.end(function(res){ | ||
var html = res.files['user.html']; | ||
var json = res.files['user.json']; | ||
var text = res.files['user.txt']; | ||
var part = req.part() | ||
.set('Content-Type', 'text/plain'); | ||
html.name.should.equal('user.html'); | ||
html.type.should.equal('text/html'); | ||
read(html.path).should.equal('<h1>name</h1>'); | ||
part.write('foo '); | ||
part.write('bar '); | ||
part.write('baz'); | ||
json.name.should.equal('user.json'); | ||
json.type.should.equal('application/json'); | ||
read(json.path).should.equal('{"name":"tobi"}'); | ||
req.end(function(res){ | ||
var ct = res.header['content-type']; | ||
ct.should.include('multipart/form-data; boundary="'); | ||
text.name.should.equal('user.txt'); | ||
text.type.should.equal('text/plain'); | ||
read(text.path).should.equal('Tobi'); | ||
var body = ''; | ||
body += '\r\n--' + boundary(ct) + '\r\n'; | ||
body += 'Content-Type: image/png\r\n'; | ||
body += 'Content-Disposition: attachment\r\n'; | ||
body += '\r\n'; | ||
body += 'some image data'; | ||
body += '\r\n--' + boundary(ct) + '\r\n'; | ||
body += 'Content-Type: text/plain\r\n'; | ||
body += '\r\n'; | ||
body += 'foo bar baz'; | ||
body += '\r\n--' + boundary(ct) + '--'; | ||
assert(body == res.text, 'invalid multipart response'); | ||
done(); | ||
}); | ||
done(); | ||
}) | ||
}) | ||
describe('with a Content-Type specified', function(){ | ||
it('should append the boundary', function(){ | ||
describe('when a file does not exist', function(){ | ||
it('should emit an error', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
req | ||
.type('multipart/form-data') | ||
.part() | ||
.set('Content-Type', 'image/png') | ||
.write('some image data'); | ||
req.attach('foo'); | ||
req.attach('bar'); | ||
req.attach('baz'); | ||
req.on('error', function(err){ | ||
err.message.should.include('ENOENT, no such file'); | ||
err.path.should.equal('foo'); | ||
done(); | ||
}); | ||
req.end(function(res){ | ||
var ct = res.header['content-type']; | ||
ct.should.include('multipart/form-data; boundary="'); | ||
var body = '\r\n'; | ||
body += '--' + boundary(ct) + '\r\n'; | ||
body += 'Content-Type: image/png\r\n'; | ||
body += '\r\n'; | ||
body += 'some image data'; | ||
body += '\r\n--' + boundary(ct) + '--'; | ||
assert(body == res.text, 'invalid multipart response'); | ||
assert(0, 'end() was called'); | ||
}); | ||
@@ -128,36 +132,127 @@ }) | ||
describe('#pipe(stream)', function(){ | ||
it('should write to the part', function(){ | ||
describe('#attach(file, filename)', function(){ | ||
it('should use the custom filename', function(done){ | ||
request | ||
.post(':3005/echo') | ||
.attach('test/node/fixtures/user.html', 'document') | ||
.end(function(res){ | ||
var html = res.files.document; | ||
html.name.should.equal('document'); | ||
html.type.should.equal('text/html'); | ||
read(html.path).should.equal('<h1>name</h1>'); | ||
done(); | ||
}) | ||
}) | ||
}) | ||
}) | ||
describe('Part', function(){ | ||
describe('with a single part', function(){ | ||
it('should construct a multipart request', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
req | ||
.part() | ||
.set('Content-Disposition', 'attachment; name="image"; filename="image.png"') | ||
.set('Content-Type', 'image/png') | ||
.write('some image data'); | ||
req.end(function(res){ | ||
var ct = res.header['content-type']; | ||
ct.should.include('multipart/form-data; boundary='); | ||
res.body.should.eql({}); | ||
res.files.image.name.should.equal('image.png'); | ||
res.files.image.type.should.equal('image/png'); | ||
done(); | ||
}); | ||
}) | ||
}) | ||
describe('with several parts', function(){ | ||
it('should construct a multipart request', function(done){ | ||
var req = request.post('http://localhost:3005/echo'); | ||
req.part() | ||
.set('Content-Type', 'image/png') | ||
.set('Content-Disposition', 'attachment; filename="myimage.png"') | ||
.write('some image data'); | ||
var part = req.part() | ||
.set('Content-Type', 'image/png') | ||
.set('Content-Disposition', 'attachment; filename="another.png"') | ||
part.write('random'); | ||
part.write('thing'); | ||
part.write('here'); | ||
req.part() | ||
.set('Content-Disposition', 'form-data; name="name"') | ||
.set('Content-Type', 'text/plain') | ||
.write('tobi'); | ||
req.end(function(res){ | ||
res.body.name.should.equal('tobi'); | ||
Object.keys(res.files).should.eql(['myimage.png', 'another.png']); | ||
done(); | ||
}); | ||
}) | ||
}) | ||
describe('with a Content-Type specified', function(){ | ||
it('should append the boundary', function(done){ | ||
var req = request | ||
.post('http://localhost:3005/echo') | ||
.type('multipart/form-data'); | ||
req | ||
.part() | ||
.set('Content-Type', 'text/plain') | ||
.set('Content-Disposition', 'form-data; name="name"') | ||
.write('Tobi'); | ||
req.end(function(res){ | ||
res.header['content-type'].should.include('boundary='); | ||
res.body.name.should.equal('Tobi'); | ||
done(); | ||
}); | ||
}) | ||
}) | ||
var stream = fs.createReadStream(__dirname + '/fixtures/user.html'); | ||
describe('#name(str)', function(){ | ||
it('should set Content-Disposition to form-data and name param', function(done){ | ||
var req = request | ||
.post('http://localhost:3005/echo'); | ||
req | ||
.part() | ||
.name('user[name]') | ||
.write('Tobi'); | ||
req.end(function(res){ | ||
res.body['user[name]'].should.equal('Tobi'); | ||
done(); | ||
}); | ||
}) | ||
}) | ||
var part = req.part(); | ||
part.set('Content-Type', 'text/html'); | ||
stream.pipe(part); | ||
req.on('response', function(res){ | ||
console.log(res.statusCode); | ||
console.log(res.text); | ||
describe('#filename(str)', function(){ | ||
it('should set Content-Disposition and Content-Type', function(done){ | ||
var req = request | ||
.post('http://localhost:3005/echo') | ||
.type('multipart/form-data'); | ||
req | ||
.part() | ||
.filename('path/to/my.txt') | ||
.write('Tobi'); | ||
req.end(function(res){ | ||
var file = res.files['my.txt']; | ||
file.name.should.equal('my.txt'); | ||
file.type.should.equal('text/plain'); | ||
done(); | ||
}); | ||
// req.end(function(res){ | ||
// console.log(res.text); | ||
// return | ||
// var ct = res.header['content-type']; | ||
// ct.should.include.string('multipart/form-data; boundary="'); | ||
// | ||
// var body = '\r\n'; | ||
// body += '--' + boundary(ct) + '\r\n'; | ||
// body += 'Content-Type: image/png\r\n'; | ||
// body += '\r\n'; | ||
// body += 'some image data'; | ||
// body += '\r\n--' + boundary(ct) + '--'; | ||
// | ||
// // assert(body == res.text, 'invalid multipart response'); | ||
// }); | ||
}) | ||
}) | ||
}) |
@@ -11,9 +11,11 @@ | ||
app.del('/', function(req, res){ | ||
res.send(req.query); | ||
}); | ||
app.listen(3006); | ||
// TODO: "response" event should be a Response | ||
describe('req.send(Object)', function(){ | ||
describe('on a GET request', function(){ | ||
it('should send x-www-form-urlencoded data', function(done){ | ||
it('should construct the query-string', function(done){ | ||
request | ||
@@ -29,3 +31,55 @@ .get('http://localhost:3006/') | ||
}) | ||
it('should append to the original query-string', function(done){ | ||
request | ||
.get('http://localhost:3006/?name=tobi') | ||
.send({ order: 'asc' }) | ||
.end(function(res) { | ||
res.body.should.eql({ name: 'tobi', order: 'asc' }); | ||
done(); | ||
}); | ||
}); | ||
it('should retain the original query-string', function(done){ | ||
request | ||
.get('http://localhost:3006/?name=tobi') | ||
.end(function(res) { | ||
res.body.should.eql({ name: 'tobi' }); | ||
done(); | ||
}); | ||
}); | ||
}) | ||
}) | ||
}) | ||
describe('req.query(Object)', function(){ | ||
it('should construct the query-string', function(done){ | ||
request | ||
.del('http://localhost:3006/') | ||
.query({ name: 'tobi' }) | ||
.query({ order: 'asc' }) | ||
.query({ limit: ['1', '2'] }) | ||
.end(function(res){ | ||
res.body.should.eql({ name: 'tobi', order: 'asc', limit: ['1', '2'] }); | ||
done(); | ||
}); | ||
}) | ||
it('should append to the original query-string', function(done){ | ||
request | ||
.del('http://localhost:3006/?name=tobi') | ||
.query({ order: 'asc' }) | ||
.end(function(res) { | ||
res.body.should.eql({ name: 'tobi', order: 'asc' }); | ||
done(); | ||
}); | ||
}); | ||
it('should retain the original query-string', function(done){ | ||
request | ||
.del('http://localhost:3006/?name=tobi') | ||
.end(function(res) { | ||
res.body.should.eql({ name: 'tobi' }); | ||
done(); | ||
}); | ||
}); | ||
}) |
@@ -26,4 +26,2 @@ | ||
// TODO: "response" event should be a Response | ||
describe('request', function(){ | ||
@@ -30,0 +28,0 @@ describe('on redirect', function(){ |
@@ -22,2 +22,3 @@ | ||
assert(res.ok, 'response should be ok'); | ||
assert(res.text, 'res.text'); | ||
next(); | ||
@@ -27,2 +28,11 @@ }); | ||
test('request() simple HEAD', function(next){ | ||
request.head('test.request.js').end(function(res){ | ||
assert(res instanceof request.Response, 'respond with Response'); | ||
assert(res.ok, 'response should be ok'); | ||
assert(!res.text, 'res.text'); | ||
next(); | ||
}); | ||
}); | ||
test('request() GET 5xx', function(next){ | ||
@@ -277,7 +287,7 @@ request('GET', '/error').end(function(res){ | ||
test('GET .contentType', function(next){ | ||
test('GET .type', function(next){ | ||
request | ||
.get('/pets') | ||
.end(function(res){ | ||
assert('application/json' == res.contentType); | ||
assert('application/json' == res.type); | ||
next(); | ||
@@ -344,8 +354,8 @@ }); | ||
test('GET querystring', function(next){ | ||
test('GET querystring object', function(next){ | ||
request | ||
.get('/querystring') | ||
.send('search=Manny&range=1..5') | ||
.send({ search: 'Manny' }) | ||
.end(function(res){ | ||
assert.eql(res.body, { search: 'Manny', range: '1..5' }); | ||
assert.eql(res.body, { search: 'Manny' }); | ||
next(); | ||
@@ -355,8 +365,8 @@ }); | ||
test('GET querystring object', function(next){ | ||
test('GET querystring append original', function(next){ | ||
request | ||
.get('/querystring') | ||
.send({ search: 'Manny' }) | ||
.get('/querystring?search=Manny') | ||
.send({ range: '1..5' }) | ||
.end(function(res){ | ||
assert.eql(res.body, { search: 'Manny' }); | ||
assert.eql(res.body, { search: 'Manny', range: '1..5' }); | ||
next(); | ||
@@ -380,3 +390,3 @@ }); | ||
request | ||
.get('/querystring', { search: 'Manny'}) | ||
.get('/querystring', { search: 'Manny' }) | ||
.end(function(res){ | ||
@@ -383,0 +393,0 @@ assert.eql(res.body, { search: 'Manny' }); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
477873
55
13269
3
7
4
+ Addedformidable@1.0.9
+ Addedmime@1.2.5
+ Addedformidable@1.0.9(transitive)
+ Addedmime@1.2.5(transitive)
+ Addedqs@0.4.2(transitive)
- Removedqs@0.3.2(transitive)
Updatedqs@0.4.2