httpism
Version:
HTTP client with middleware and good defaults
799 lines (667 loc) • 18.9 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var httpism = require('./httpism');
var utils = require('./middlewareUtils');
var qs = require('qs');
function json(request, next) {
if (request.body instanceof Object) {
request.body = JSON.stringify(request.body);
utils.setHeaderTo(request, "content-type", "application/json");
}
utils.setHeaderTo(request, "accept", "application/json");
return next().then(function(response) {
if (utils.shouldParseAs(response, "json", request)) {
response.body = JSON.parse(response.body);
}
return response;
});
}
function text(request, next) {
if (typeof request.body === 'string') {
utils.setHeaderTo(request, "content-type", "text/plain;charset=UTF-8");
}
return next();
}
function form(request, next) {
if (request.options.form && request.body instanceof Object) {
setBodyToString(request, qs.stringify(request.body));
utils.setHeaderTo(request, "content-type", "application/x-www-form-urlencoded");
}
return next().then(function(response) {
if (utils.shouldParseAs(response, "form", request)) {
response.body = qs.parse(response.body);
}
return response;
});
}
function setBodyToString(r, s) {
r.body = s;
}
function parseHeaders(headers) {
var object = {};
var lines = headers.split('\n');
for(var n = 0; n < lines.length; n++) {
var line = lines[n];
var match = /^(.*?):(.*)/.exec(line);
if (match) {
object[match[1].toLowerCase()] = match[2].trim();
}
}
return object;
}
function setHeaders(headers, xhr) {
var headerNames = Object.keys(headers);
for (var n = 0; n < headerNames.length; n++) {
var headerName = headerNames[n];
xhr.setRequestHeader(headerName, headers[headerName]);
}
}
function responseUrl(xhr, requestUrl) {
var origin = location.origin;
var responseUrl = xhr.responseURL;
if (responseUrl) {
if (responseUrl.substring(0, origin.length) == origin) {
return responseUrl.substring(origin.length);
} else {
return responseUrl;
}
} else {
return requestUrl;
}
}
function send(request) {
var xhr = new XMLHttpRequest();
var reject;
var promise = new Promise(function (fulfil, _reject) {
reject = _reject;
xhr.open(request.method, request.url, true);
xhr.onload = function (event) {
var statusCode = xhr.status;
var response = {
body: statusCode == 204? undefined: xhr.responseText,
headers: parseHeaders(xhr.getAllResponseHeaders()),
statusCode: statusCode,
url: responseUrl(xhr, request.url),
xhr: xhr,
statusText: xhr.statusText
};
fulfil(response);
};
xhr.onerror = function (error) {
reject(new Error('failed to connect to ' + request.method + ' ' + request.url));
};
if (!isCrossDomain(request.url) && !request.headers['x-requested-with']) {
request.headers['x-requested-with'] = 'XMLHttpRequest'
}
setHeaders(request.headers, xhr);
xhr.withCredentials = !!request.options.withCredentials;
xhr.send(request.body);
});
function abort() {
xhr.abort();
var error = new Error('aborted connection to ' + request.method + ' ' + request.url);
error.aborted = true;
reject(error);
}
addAbortToPromise(promise, abort);
return promise;
}
function isCrossDomain(url) {
return /^https?:\/\//.test(url);
}
function addAbortToPromise(promise, abort) {
var then = promise.then;
promise.then = function () {
var p = then.apply(this, arguments);
p.abort = abort;
addAbortToPromise(p, abort);
return p;
};
}
module.exports = httpism(
undefined,
{},
[
utils.exception,
form,
json,
text,
utils.querystring,
send
]
);
},{"./httpism":2,"./middlewareUtils":4,"qs":5}],2:[function(require,module,exports){
var merge = require('./merge');
var resolveUrl = require('./resolveUrl');
var utils = require('./middlewareUtils');
function client(url, options, middlewares) {
return new Httpism(url, options, middlewares);
}
function Httpism(url, options, middlewares) {
this.url = url;
this._options = options;
this.middlewares = middlewares;
}
Httpism.prototype.send = function(method, url, body, _options, api) {
var options = merge(_options, this._options)
var request = {
method: method,
url: resolveUrl(this.url, url),
headers: options.headers || {},
body: body,
options: options
};
var self = this;
function sendToMiddleware(index) {
if (index < self.middlewares.length) {
var middleware = self.middlewares[index];
return middleware(request, function () { return sendToMiddleware(index + 1); }, self);
}
}
return sendToMiddleware(0).then(function (response) {
return makeResponse(self, response);
}, function (e) {
if (e.redirectResponse) {
return e.redirectResponse;
} else {
throw e;
}
});
};
function makeResponse(api, response) {
return utils.extend(new Httpism(api.url, api._options, api.middlewares), response);
}
Httpism.prototype.api = function (url, options, middlewares) {
var args = parseClientArguments(url, options, middlewares);
return new Httpism(
resolveUrl(this.url, args.url),
merge(args.options, this._options),
args.middlewares
? args.middlewares.concat(this.middlewares)
: this.middlewares
);
};
function addMethod(method) {
Httpism.prototype[method] = function (url, options) {
return this.send(method, url, undefined, options, this);
};
}
function addMethodWithBody(method) {
Httpism.prototype[method] = function (url, body, options) {
return this.send(method, url, body, options, this);
};
}
addMethod('get');
addMethod('delete');
addMethod('head');
addMethodWithBody('post');
addMethodWithBody('put');
addMethodWithBody('patch');
addMethodWithBody('options');
function resolveUrl(base, url) {
if (base) {
return resolveUrl(base, url);
} else {
return url;
}
}
function parseClientArguments() {
var url, options, middlewares;
for(var n = 0; n < arguments.length; n++) {
var arg = arguments[n];
if (typeof arg === 'string') {
url = arg;
} else if (typeof arg === 'function') {
middlewares = [arg];
} else if (arg instanceof Array) {
middlewares = arg;
} else if (arg instanceof Object) {
options = arg;
}
}
return {
url: url,
options: options,
middlewares: middlewares
};
}
module.exports = client;
},{"./merge":3,"./middlewareUtils":4,"./resolveUrl":6}],3:[function(require,module,exports){
module.exports = function(x, y) {
if (x && y) {
var r = {};
Object.keys(y).forEach(function (ykey) {
r[ykey] = y[ykey];
});
Object.keys(x).forEach(function (xkey) {
r[xkey] = x[xkey];
});
return r;
} else if (y) {
return y;
} else {
return x;
}
};
},{}],4:[function(require,module,exports){
var merge = require("./merge");
var qs = require('qs');
module.exports.setHeaderTo = function (request, header, value) {
if (!request.headers[header]) {
return request.headers[header] = value;
}
};
var responseBodyTypes = {
json: function(response) {
return contentTypeIs(response, "application/json");
},
text: function(response) {
return contentTypeIsText(response) || contentTypeIs(response, "application/javascript");
},
form: function(response) {
return contentTypeIs(response, "application/x-www-form-urlencoded");
},
stream: function(response) {
return false;
}
};
function contentTypeIs(response, expectedContentType) {
var re = new RegExp("^\\s*" + expectedContentType + "\\s*($|;)");
return re.test(response.headers["content-type"]);
}
function contentTypeIsText(response) {
return contentTypeIs(response, "text/.*");
}
module.exports.shouldParseAs = function(response, type, request, options) {
var contentType = options !== undefined && options.hasOwnProperty(options, "contentType") && options.contentType !== undefined ? options.contentType : undefined;
if (request.options.responseBody) {
return type === request.options.responseBody;
} else {
var bodyType = responseBodyTypes[type];
if (bodyType) {
return bodyType(response);
}
}
};
function extend(object, extension) {
var keys = Object.keys(extension);
for (var n = 0; n < keys.length; n++) {
var key = keys[n];
object[key] = extension[key];
}
return object;
}
exports.extend = extend;
exports.exception = function(request, next) {
return next().then(function(response) {
if (response.statusCode >= 400 && request.options.exceptions !== false) {
var msg = request.method.toUpperCase() + " " + request.url + " => " + response.statusCode + " " + response.statusText;
var error = extend(new Error(msg), response);
throw error;
} else {
return response;
}
});
};
exports.querystring = function(request, next) {
if (request.options.querystring instanceof Object) {
var split = request.url.split("?");
var path = split[0];
var querystring = qs.parse(split[1]);
var mergedQueryString = merge(request.options.querystring, querystring);
request.url = path + "?" + qs.stringify(mergedQueryString);
}
return next();
};
},{"./merge":3,"qs":5}],5:[function(require,module,exports){
/**
* Object#toString() ref for stringify().
*/
var toString = Object.prototype.toString;
/**
* Object#hasOwnProperty ref
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Array#indexOf shim.
*/
var indexOf = typeof Array.prototype.indexOf === 'function'
? function(arr, el) { return arr.indexOf(el); }
: function(arr, el) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === el) return i;
}
return -1;
};
/**
* Array.isArray shim.
*/
var isArray = Array.isArray || function(arr) {
return toString.call(arr) == '[object Array]';
};
/**
* Object.keys shim.
*/
var objectKeys = Object.keys || function(obj) {
var ret = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
ret.push(key);
}
}
return ret;
};
/**
* Array#forEach shim.
*/
var forEach = typeof Array.prototype.forEach === 'function'
? function(arr, fn) { return arr.forEach(fn); }
: function(arr, fn) {
for (var i = 0; i < arr.length; i++) fn(arr[i]);
};
/**
* Array#reduce shim.
*/
var reduce = function(arr, fn, initial) {
if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
var res = initial;
for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
return res;
};
/**
* Cache non-integer test regexp.
*/
var isint = /^[0-9]+$/;
function promote(parent, key) {
if (parent[key].length == 0) return parent[key] = {}
var t = {};
for (var i in parent[key]) {
if (hasOwnProperty.call(parent[key], i)) {
t[i] = parent[key][i];
}
}
parent[key] = t;
return t;
}
function parse(parts, parent, key, val) {
var part = parts.shift();
// illegal
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
// end
if (!part) {
if (isArray(parent[key])) {
parent[key].push(val);
} else if ('object' == typeof parent[key]) {
parent[key] = val;
} else if ('undefined' == typeof parent[key]) {
parent[key] = val;
} else {
parent[key] = [parent[key], val];
}
// array
} else {
var obj = parent[key] = parent[key] || [];
if (']' == part) {
if (isArray(obj)) {
if ('' != val) obj.push(val);
} else if ('object' == typeof obj) {
obj[objectKeys(obj).length] = val;
} else {
obj = parent[key] = [parent[key], val];
}
// prop
} else if (~indexOf(part, ']')) {
part = part.substr(0, part.length - 1);
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
// key
} else {
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
}
}
}
/**
* Merge parent key/val pair.
*/
function merge(parent, key, val){
if (~indexOf(key, ']')) {
var parts = key.split('[')
, len = parts.length
, last = len - 1;
parse(parts, parent, 'base', val);
// optimize
} else {
if (!isint.test(key) && isArray(parent.base)) {
var t = {};
for (var k in parent.base) t[k] = parent.base[k];
parent.base = t;
}
set(parent.base, key, val);
}
return parent;
}
/**
* Compact sparse arrays.
*/
function compact(obj) {
if ('object' != typeof obj) return obj;
if (isArray(obj)) {
var ret = [];
for (var i in obj) {
if (hasOwnProperty.call(obj, i)) {
ret.push(obj[i]);
}
}
return ret;
}
for (var key in obj) {
obj[key] = compact(obj[key]);
}
return obj;
}
/**
* Parse the given obj.
*/
function parseObject(obj){
var ret = { base: {} };
forEach(objectKeys(obj), function(name){
merge(ret, name, obj[name]);
});
return compact(ret.base);
}
/**
* Parse the given str.
*/
function parseString(str){
var ret = reduce(String(str).split('&'), function(ret, pair){
var eql = indexOf(pair, '=')
, brace = lastBraceInKey(pair)
, key = pair.substr(0, brace || eql)
, val = pair.substr(brace || eql, pair.length)
, val = val.substr(indexOf(val, '=') + 1, val.length);
// ?foo
if ('' == key) key = pair, val = '';
if ('' == key) return ret;
return merge(ret, decode(key), decode(val));
}, { base: {} }).base;
return compact(ret);
}
/**
* Parse the given query `str` or `obj`, returning an object.
*
* @param {String} str | {Object} obj
* @return {Object}
* @api public
*/
exports.parse = function(str){
if (null == str || '' == str) return {};
return 'object' == typeof str
? parseObject(str)
: parseString(str);
};
/**
* Turn the given `obj` into a query string
*
* @param {Object} obj
* @return {String}
* @api public
*/
var stringify = exports.stringify = function(obj, prefix) {
if (isArray(obj)) {
return stringifyArray(obj, prefix);
} else if ('[object Object]' == toString.call(obj)) {
return stringifyObject(obj, prefix);
} else if ('string' == typeof obj) {
return stringifyString(obj, prefix);
} else {
return prefix + '=' + encodeURIComponent(String(obj));
}
};
/**
* Stringify the given `str`.
*
* @param {String} str
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyString(str, prefix) {
if (!prefix) throw new TypeError('stringify expects an object');
return prefix + '=' + encodeURIComponent(str);
}
/**
* Stringify the given `arr`.
*
* @param {Array} arr
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyArray(arr, prefix) {
var ret = [];
if (!prefix) throw new TypeError('stringify expects an object');
for (var i = 0; i < arr.length; i++) {
ret.push(stringify(arr[i], prefix + '[' + i + ']'));
}
return ret.join('&');
}
/**
* Stringify the given `obj`.
*
* @param {Object} obj
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyObject(obj, prefix) {
var ret = []
, keys = objectKeys(obj)
, key;
for (var i = 0, len = keys.length; i < len; ++i) {
key = keys[i];
if ('' == key) continue;
if (null == obj[key]) {
ret.push(encodeURIComponent(key) + '=');
} else {
ret.push(stringify(obj[key], prefix
? prefix + '[' + encodeURIComponent(key) + ']'
: encodeURIComponent(key)));
}
}
return ret.join('&');
}
/**
* Set `obj`'s `key` to `val` respecting
* the weird and wonderful syntax of a qs,
* where "foo=bar&foo=baz" becomes an array.
*
* @param {Object} obj
* @param {String} key
* @param {String} val
* @api private
*/
function set(obj, key, val) {
var v = obj[key];
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
if (undefined === v) {
obj[key] = val;
} else if (isArray(v)) {
v.push(val);
} else {
obj[key] = [v, val];
}
}
/**
* Locate last brace in `str` within the key.
*
* @param {String} str
* @return {Number}
* @api private
*/
function lastBraceInKey(str) {
var len = str.length
, brace
, c;
for (var i = 0; i < len; ++i) {
c = str[i];
if (']' == c) brace = false;
if ('[' == c) brace = true;
if ('=' == c && !brace) return i;
}
}
/**
* Decode `str`.
*
* @param {String} str
* @return {String}
* @api private
*/
function decode(str) {
try {
return decodeURIComponent(str.replace(/\+/g, ' '));
} catch (err) {
return str;
}
}
},{}],6:[function(require,module,exports){
// from https://gist.github.com/Yaffle/1088850
/*jslint regexp: true, white: true, maxerr: 50, indent: 2 */
function parseURI(url) {
var m = String(url).replace(/^\s+|\s+$/g, '').match(/^([^:\/?#]+:)?(\/\/(?:[^:@]*(?::[^:@]*)?@)?(([^:\/?#]*)(?::(\d*))?))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?/);
// authority = '//' + user + ':' + pass '@' + hostname + ':' port
return (m ? {
href : m[0] || '',
protocol : m[1] || '',
authority: m[2] || '',
host : m[3] || '',
hostname : m[4] || '',
port : m[5] || '',
pathname : m[6] || '',
search : m[7] || '',
hash : m[8] || ''
} : null);
}
module.exports = function (base, href) {// RFC 3986
function removeDotSegments(input) {
var output = [];
input.replace(/^(\.\.?(\/|$))+/, '')
.replace(/\/(\.(\/|$))+/g, '/')
.replace(/\/\.\.$/, '/../')
.replace(/\/?[^\/]*/g, function (p) {
if (p === '/..') {
output.pop();
} else {
output.push(p);
}
});
return output.join('').replace(/^\//, input.charAt(0) === '/' ? '/' : '');
}
href = parseURI(href || '');
base = parseURI(base || '');
return !href || !base ? null : (href.protocol || base.protocol) +
(href.protocol || href.authority ? href.authority : base.authority) +
removeDotSegments(href.protocol || href.authority || href.pathname.charAt(0) === '/' ? href.pathname : (href.pathname ? ((base.authority && !base.pathname ? '/' : '') + base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1) + href.pathname) : base.pathname)) +
(href.protocol || href.authority || href.pathname ? href.search : (href.search || base.search)) +
href.hash;
};
},{}]},{},[1]);