UNPKG

lil-http

Version:

Tiny, lightweight, full featured HTTP client

327 lines (283 loc) 8.46 kB
/*! lil-http - v0.1.17 - MIT License - https://github.com/lil-js/http */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['exports'], factory) } else if (typeof exports === 'object') { factory(exports) if (typeof module === 'object' && module !== null) { module.exports = exports = exports.http } } else { factory((root.lil = root.lil || {})) } }(this, function (exports) { 'use strict' var VERSION = '0.1.17' var toStr = Object.prototype.toString var slicer = Array.prototype.slice var hasOwn = Object.prototype.hasOwnProperty var hasBind = typeof Function.prototype.bind === 'function' var origin = location.origin var originRegex = /^(http[s]?:\/\/[a-z0-9\-\.\:]+)[\/]?/i var jsonMimeRegex = /application\/json/ var hasDomainRequest = typeof XDomainRequest !== 'undefined' var noop = function () {} var defaults = { method: 'GET', timeout: 30 * 1000, auth: null, data: null, headers: null, withCredentials: false, responseType: 'text' } function isObj (o) { return o && toStr.call(o) === '[object Object]' || false } function assign (target) { var i, l, x, cur, args = slicer.call(arguments).slice(1) for (i = 0, l = args.length; i < l; i += 1) { cur = args[i] for (x in cur) if (hasOwn.call(cur, x)) target[x] = cur[x] } return target } function once (fn) { var called = false return function () { if (called === false) { called = true fn.apply(null, arguments) } } } function setHeaders (xhr, headers) { if (!isObj(headers)) return // Set default content type headers['Content-Type'] = headers['Content-Type'] || headers['content-type'] || http.defaultContent var buf = Object.keys(headers).reduce(function (buf, field) { var lowerField = field.toLowerCase() // Remove duplicated headers if (lowerField !== field) { if (hasOwn.call(headers, lowerField)) { delete headers[lowerField] delete buf[lowerField] } } buf[field] = headers[field] return buf }, {}) Object.keys(buf).forEach(function (field) { xhr.setRequestHeader(field, buf[field]) }) } function getHeaders (xhr) { var headers = {}, rawHeaders = xhr.getAllResponseHeaders().trim().split('\n') rawHeaders.forEach(function (header) { var split = header.trim().split(':') var key = split.shift().trim() var value = split.join(':').trim() headers[key] = value }) return headers } function isJSONResponse (xhr) { return jsonMimeRegex.test(xhr.getResponseHeader('Content-Type')) } function encodeParams (params) { return Object.getOwnPropertyNames(params).filter(function (name) { return params[name] !== undefined }).map(function (name) { var value = (params[name] === null) ? '' : params[name] return encodeURIComponent(name) + (value ? '=' + encodeURIComponent(value) : '') }).join('&').replace(/%20/g, '+') } function parseData (xhr) { var data = null if (xhr.responseType === 'text') { data = xhr.responseText if (isJSONResponse(xhr) && data) data = JSON.parse(data) } else { data = xhr.response } return data } function getStatus (status) { return status === 1223 ? 204 : status // IE9 fix } function buildResponse (xhr) { var response = { xhr: xhr, status: getStatus(xhr.status), statusText: xhr.statusText, data: null, headers: {} } if (xhr.readyState === 4) { response.data = parseData(xhr) response.headers = getHeaders(xhr) } return response } function buildErrorResponse (xhr, error) { var response = buildResponse(xhr) response.error = error if (error.stack) response.stack = error.stack return response } function cleanReferences (xhr) { xhr.onreadystatechange = xhr.onerror = xhr.ontimeout = null } function isValidResponseStatus (xhr) { var status = getStatus(xhr.status) return status >= 200 && status < 300 || status === 304 } function onError (xhr, cb) { return once(function (err) { cb(buildErrorResponse(xhr, err), null) }) } function onLoad (config, xhr, cb) { return function (ev) { if (xhr.readyState === 4) { cleanReferences(xhr) if (isValidResponseStatus(xhr)) { cb(null, buildResponse(xhr)) } else { onError(xhr, cb)(ev) } } } } function isCrossOrigin (url) { var match = url.match(originRegex) return match && match[1] === origin } function getURL (config) { var url = config.url if (isObj(config.params)) { url += (url.indexOf('?') === -1 ? '?' : '&') + encodeParams(config.params) } return url } function XHRFactory (url) { if (hasDomainRequest && isCrossOrigin(url)) { return new XDomainRequest() } else { return new XMLHttpRequest() } } function createClient (config) { var method = (config.method || 'GET').toUpperCase() var auth = config.auth var url = getURL(config) if (!url || typeof url !== 'string') { throw new TypeError('Missing required request URL') } var xhr = XHRFactory(url) if (auth) { xhr.open(method, url, true, auth.user, auth.password) } else { xhr.open(method, url) } xhr.withCredentials = config.withCredentials xhr.responseType = config.responseType xhr.timeout = config.timeout setHeaders(xhr, config.headers) return xhr } function updateProgress (xhr, cb) { return function (ev) { if (ev.lengthComputable) { cb(ev, ev.loaded / ev.total) } else { cb(ev) } } } function hasContentTypeHeader (config) { return config && isObj(config.headers) && (config.headers['content-type'] || config.headers['Content-Type']) || false } function buildPayload (xhr, config) { var data = config.data if (isObj(config.data) || Array.isArray(config.data)) { if (hasContentTypeHeader(config) === false) { xhr.setRequestHeader('Content-Type', 'application/json') } data = JSON.stringify(config.data) } return data } function timeoutResolver (cb, timeoutId) { return function () { clearTimeout(timeoutId) cb.apply(null, arguments) } } function request (config, cb, progress) { var xhr = createClient(config) var data = buildPayload(xhr, config) var errorHandler = onError(xhr, cb) if (hasBind) { xhr.ontimeout = errorHandler } else { var timeoutId = setTimeout(function abort () { if (xhr.readyState !== 4) { xhr.abort() } }, config.timeout) cb = timeoutResolver(cb, timeoutId) errorHandler = onError(xhr, cb) } xhr.onreadystatechange = onLoad(config, xhr, cb) xhr.onerror = errorHandler if (typeof progress === 'function') { xhr.onprogress = updateProgress(xhr, progress) } try { xhr.send(data || null) } catch (e) { errorHandler(e) } return { xhr: xhr, config: config } } function requestFactory (method) { return function (url, options, cb, progress) { var i, l, cur = null var config = assign({}, defaults, { method: method }) var args = slicer.call(arguments) for (i = 0, l = args.length; i < l; i += 1) { cur = args[i] if (typeof cur === 'function') { if (args.length === (i + 1) && typeof args[i - 1] === 'function') { progress = cur } else { cb = cur } } else if (isObj(cur)) { assign(config, cur) } else if (typeof cur === 'string' && !config.url) { config.url = cur } } return request(config, cb || noop, progress) } } function http (config, data, cb, progress) { return requestFactory('GET').apply(null, arguments) } http.VERSION = VERSION http.defaults = defaults http.defaultContent = 'text/plain' http.get = requestFactory('GET') http.post = requestFactory('POST') http.put = requestFactory('PUT') http.patch = requestFactory('PATCH') http.head = requestFactory('HEAD') http.delete = http.del = requestFactory('DELETE') return exports.http = http }))