Skip to content

Instantly share code, notes, and snippets.

@bjconlan
Created June 18, 2015 14:14
Show Gist options
  • Save bjconlan/ff6cea2a9fa90c5f5537 to your computer and use it in GitHub Desktop.
Save bjconlan/ff6cea2a9fa90c5f5537 to your computer and use it in GitHub Desktop.
Nodejs implementation of angular $httpProvider
'use strict';
var _ = require('underscore');
var http = require('http');
var https = require('https');
var url = require('url');
var querystring = require('querystring');
/**
* A http client library implementation encapsulating nodes http.request object.
*
* The current API conforms to the ng-di module loader with usage:
*
* ```javascript
* var di = require('ng-di');
* var when = require('when'); // or require('q');
*
* di.module('projectModule', [])
* .constant('$q', when)
* .provider('$http', http.provider); // NOTE the injected '$q' is required.
* ```
*/
module.exports = {
/**
* This module exports a provider object used by the ng-di depenency injection
* module. It is part of the core angular library (for the client) and hence
* attempts to conform to for api consitence.
*
* The provider requires that a promise implementation is resolvable using the
* standard angular '$q' service name.
*/
provider: function () {
return {
defaults: {
headers: {
common: { 'Accept': 'application/json, text/plain, */*' },
patch: { 'Content-Type': 'application/json;charset=utf-8' },
post: { 'Content-Type': 'application/json;charset=utf-8' },
put: { 'Content-Type': 'application/json;charset=utf-8' }
}
},
/**
* Provider construction function
*/
$get: ['$q', function ($q) { // todo cacheFactory service support
var defaults = this.defaults;
/**
* A simple wrapper around nodes http response object that provides a consistent
* wrapper with the angular client api.
*
* @param {String|Object} options the options can either be a url or an object of
* configuration parameters. These consist of:
*
* method {String} - A string of either DELETE, GET, HEAD, PATCH PUT, POST (defaults to GET)
* url {String} - The url endpoint to which the http client will attempt to communicate
* params {Object.<String|Object>} - A query string ie: 'age=10&name=Bill' or an object representation
* data {String|Object} - Data to be sent in the body of the request.
* headers {Object} - A collection of header fields to be sent along with the default headers.
*
* TODO:
* timeout {Number} - The number in milliseconds to wait for a response defaults to 0 (unlimited).
* withCredientials {boolean} -
*/
function httpRequest(options) {
options = _.extend({
method: 'get',
url: '',
params: null,
data: '',
headers: {},
timeout: 0
}, options);
var deferred = $q.defer();
// append content length to the headers if data is present
if (options.data) {
if (_.isObject(options.data)) { // convert to string
options.data = JSON.stringify(options.data);
}
options.headers['Content-Length'] = Buffer.byteLength(options.data);
}
var urlObj = url.parse(options.url);
var requestOptions = _.extend(urlObj, {
method: options.method.toUpperCase(),
headers: _.extend({}, defaults.headers.common, defaults.headers[options.method], options.headers)
});
// set the path for the request (url.parse uses pathname)
requestOptions.path = urlObj.pathname + '?' + querystring.stringify(_.extend(urlObj.search || urlObj.query || {}, options.params));
// use https if specified otherwise fallback to http. FIXME (hanlding local requests url: '/user/1'?)
var buf = '';
var request = (requestOptions.protocol === 'https:' ? https : http).request(requestOptions, function (response) {
response.on('data', function (chunk) {
buf += chunk;
});
response.on('end', function () {
deferred.resolve({
data: buf,
status: response.statusCode,
headers: function (header) { return header ? response.headers[header] : response.headers; },
config: requestOptions
});
});
});
request.on('error', function (err) {
deferred.reject(err);
});
if (options.data) {
request.write(options.data);
}
request.end();
return deferred.promise; // TODO: provide success/failure decomposing promise handlers (needs to know underlying $q impl)
}
/**
* Decorate the httpRequest function with convenience methods akin to those used in angular.
*
* TODO add jsonp function in the spirit of a consistent angular api.
*/
_.each({ 'delete': 0, 'get': 0, 'head': 0, 'patch': 1, 'post': 1, 'put': 1 }, function (sendBody, method) {
if (sendBody) {
httpRequest[method] = function (url, data, options) {
return httpRequest(_.extend(options || {}, { method: method, url: url, data: data }));
};
} else {
httpRequest[method] = function (url, options) {
return httpRequest(_.extend(options || {}, { method: method, url: url }));
};
}
});
return httpRequest;
}]
};
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment