go-fetch
Version:
A pluggable HTTP client.
332 lines (274 loc) • 8.66 kB
JavaScript
var util = require('util');
var httprequest = require('no-frills-request');
var emitter = require('emitter-on-steroids');
var Url = require('./Url');
var Request = require('./Request');
var Response = require('./Response');
var Stream = require('./Stream');
var SteroidEvent = require('emitter-on-steroids/event');
/**
* Event
* @constructor
* @param {Object} options
* @param {Object} options.name
* @param {Request} options.request
* @param {Response} options.response
* @param {Object} [options.emitter]
*/
function Event(options) {
SteroidEvent.call(this, options.name, options.emitter);
this.request = options.request;
this.response = options.response;
}
util.inherits(Event, SteroidEvent);
Event.stoppable = SteroidEvent.stoppable;
Event.preventable = SteroidEvent.preventable;
//TODO: move the URL to another package, don't return the response, only require a callback - simpler!
/**
* A HTTP client
* @constructor
* @extends emitter
* @param {Object} options
* @param {string} [options.https_protocol]
* @param {boolean} [options.https_ignore_errors]
* @returns {Client}
*/
function Client(options) {
if (!(this instanceof Client)) {
return new Client(options);
}
emitter.call(this);
/**
* @private
* @type {{https_protocol?: string, https_ignore_errors?: boolean}}
*/
this.options = options;
}
emitter(Client.prototype);
Client.Url = Url;
Client.Request = Request;
Client.Response = Response;
Client.Event = Event;
/**
* Apply a plugin
* @param {function(Client)} plugin
* @returns {Client}
*/
Client.prototype.use = function(plugin) {
plugin(this);
return this;
};
/**
* Send a request
* @param {Request} request The request
* @param {function(Error, Response)} [callback] A callback - called when the response is received or there is an error sending the request or receiving the response
* @returns {Response}
*/
Client.prototype.send = function(request, callback) {
var self = this;
//create a new response
var response = new Response();
//forward events to the client
request
.on('sent', function() {
self.emit('sent', this);
})
.on('error', function(error) {
self.emit('error', error);
})
;
//forward events to the client
response
.on('received', function() {
self.emit('received', this);
})
.on('error', function(error) {
self.emit('error', error);
})
;
//register a callback
if (callback) {
//assert callback is a function
if (typeof(callback) !== 'function') {
throw new Error('Callback must be a function');
}
//notify callback of request errors
request.on('error', function(error) {
callback(error);
});
//notify callback of response errors
response.on('error', function(error) {
callback(error);
});
//notify callback that the response has been received
response.on('received', function() {
callback(undefined, this);
});
}
//allow the plugins to do stuff before the request is sent
var beforeEvent = Event.stoppable(Event.preventable(new Event({
name: 'before',
request: request,
response: response,
emitter: this
})));
this.emit(beforeEvent, function(err, event) {
if (err) event.request.emit('error', err);
if (event.isDefaultPrevented()) {
event.request.emit('sent');
//allow the plugins to do stuff after the response is received
var event = Event.stoppable(new Event({
name: 'after',
request: request,
response: response,
emitter: this
}));
self.emit(event, function(err, event) {
if (err) response.emit('error', err);
//tell the user the response has been received from the server
event.response.emit('received');
});
} else {
//create the request
var req = httprequest.create(
event.request.getMethod(),
event.request.getUrl().toString(),
event.request.getHeaders(),
self.options
);
//notify the user when the request has been sent to the server
req.on('finish', function() {
event.request.emit('sent');
});
//notify the user when an error occurred whilst sending the request
req.on('error', function(err) {
event.request.emit('error', err);
});
//wrap the response and notify the user when the response has been received
req.on('response', function(res) {
//listen for errors
res.on('error', function(err) {
response.emit('error', err);
});
//set the response properties
event.response
.setStatus(res.statusCode)
.setHeaders(res.headers)
.setBody(new Stream(res))
;
//allow the plugins to do stuff after the response is received
var afterEvent = Event.stoppable(Event.preventable(new Event({
name: 'after',
request: event.request,
response: event.response,
emitter: this
})));
self.emit(afterEvent, function(err, event) {
if (err) event.response.emit('error', err);
//tell the user the response has been received from the server
event.response.emit('received');
});
});
//send the request
req.send(event.request.getBody());
}
});
if (callback) {
return this;
} else {
return response;
}
};
/**
* Create a new request... and send it if a callback is provided
* @param {string} method
* @param {string} url
* @param {Object} [headers]
* @param {Object} [body]
* @param {function(Error, Response)} [callback]
* @returns {Client|Request}
*/
Client.prototype.request = function(method, url, headers, body, callback) {
//figure out which optional arguments were provided
if (arguments.length >= 3 && typeof(arguments[arguments.length-1]) === 'function') {
//send the request and return the result to the callback
if (arguments.length === 3) {
callback = headers;
body = undefined;
headers = {};
} else if (arguments.length === 4) {
callback = body;
body = undefined;
}
if (typeof(callback) !== 'function') {
throw new Error('Callback must be a function.');
}
}
//create a new request
var request = new Request();
request
.setMethod(method)
.setUrl(url)
.setHeaders(headers || {})
.setBody(body)
;
if (callback) {
//send the request
this.send(request, callback);
return this;
} else {
var self = this;
/**
* Send the request
* @returns {Response}
*/
request.send = function() {
return self.send(this);
};
return request;
}
};
/**
* Create a new GET request
* @param {string} url
* @param {Object} [headers]
* @param {function(Error, Response)} [callback]
* @returns {Client|Request}
*/
Client.prototype.get = function(url, headers, callback) {
return this.request.apply(this, ['GET'].concat(Array.prototype.slice.call(arguments)));
};
/**
* Create a new GET request
* @param {string} url
* @param {Object} [headers]
* @param {Object} [data]
* @param {function(Error, Response)} [callback]
* @returns {Client|Request}
*/
Client.prototype.post = function(url, headers, data, callback) {
return this.request.apply(this, ['POST'].concat(Array.prototype.slice.call(arguments)));
};
/**
* Create a new PUT request
* @param {string} url
* @param {Object} [headers]
* @param {Object} [data]
* @param {function(Error, Response)} [callback]
* @returns {Client}
*/
Client.prototype.put = function(url, headers, data, callback) {
return this.request.apply(this, ['PUT'].concat(Array.prototype.slice.call(arguments)));
};
/**
* Create a new DELETE request
* @param {string} url
* @param {Object} [headers]
* @param {Object} [data]
* @param {function(Error, Response)} [callback]
* @returns {Client}
*/
Client.prototype.delete = function(url, headers, data, callback) {
return this.request.apply(this, ['DELETE'].concat(Array.prototype.slice.call(arguments)));
};
module.exports = Client;