Created
June 18, 2015 14:14
-
-
Save bjconlan/ff6cea2a9fa90c5f5537 to your computer and use it in GitHub Desktop.
Nodejs implementation of angular $httpProvider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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