nodulator
Version:
Complete NodeJS Framework for Restfull APIs
175 lines (145 loc) • 4.89 kB
JavaScript
/*
* Copyright 2012-2014 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/
(function (define, global) {
'use strict';
define(function (require) {
var when, UrlBuilder, normalizeHeaderName, responsePromise, client, headerSplitRE;
when = require('when');
UrlBuilder = require('../UrlBuilder');
normalizeHeaderName = require('../util/normalizeHeaderName');
responsePromise = require('../util/responsePromise');
client = require('../client');
// according to the spec, the line break is '\r\n', but doesn't hold true in practice
headerSplitRE = /[\r|\n]+/;
function parseHeaders(raw) {
// Note: Set-Cookie will be removed by the browser
var headers = {};
if (!raw) { return headers; }
raw.trim().split(headerSplitRE).forEach(function (header) {
var boundary, name, value;
boundary = header.indexOf(':');
name = normalizeHeaderName(header.substring(0, boundary).trim());
value = header.substring(boundary + 1).trim();
if (headers[name]) {
if (Array.isArray(headers[name])) {
// add to an existing array
headers[name].push(value);
}
else {
// convert single value to array
headers[name] = [headers[name], value];
}
}
else {
// new, single value
headers[name] = value;
}
});
return headers;
}
function safeMixin(target, source) {
Object.keys(source || {}).forEach(function (prop) {
// make sure the property already exists as
// IE 6 will blow up if we add a new prop
if (source.hasOwnProperty(prop) && prop in target) {
try {
target[prop] = source[prop];
}
catch (e) {
// ignore, expected for some properties at some points in the request lifecycle
}
}
});
return target;
}
return client(function xhr(request) {
return responsePromise.promise(function (resolve, reject) {
/*jshint maxcomplexity:20 */
var client, method, url, headers, entity, headerName, response, XMLHttpRequest;
request = typeof request === 'string' ? { path: request } : request || {};
response = { request: request };
if (request.canceled) {
response.error = 'precanceled';
reject(response);
return;
}
entity = request.entity;
request.method = request.method || (entity ? 'POST' : 'GET');
method = request.method;
url = response.url = new UrlBuilder(request.path || '', request.params).build();
XMLHttpRequest = request.engine || global.XMLHttpRequest;
if (!XMLHttpRequest) {
reject({ request: request, url: url, error: 'xhr-not-available' });
return;
}
try {
client = response.raw = new XMLHttpRequest();
// mixin extra request properties before and after opening the request as some properties require being set at different phases of the request
safeMixin(client, request.mixin);
client.open(method, url, true);
safeMixin(client, request.mixin);
headers = request.headers;
for (headerName in headers) {
/*jshint forin:false */
if (headerName === 'Content-Type' && headers[headerName] === 'multipart/form-data') {
// XMLHttpRequest generates its own Content-Type header with the
// appropriate multipart boundary when sending multipart/form-data.
continue;
}
client.setRequestHeader(headerName, headers[headerName]);
}
request.canceled = false;
request.cancel = function cancel() {
request.canceled = true;
client.abort();
reject(response);
};
client.onreadystatechange = function (/* e */) {
if (request.canceled) { return; }
if (client.readyState === (XMLHttpRequest.DONE || 4)) {
response.status = {
code: client.status,
text: client.statusText
};
response.headers = parseHeaders(client.getAllResponseHeaders());
response.entity = client.responseText;
if (response.status.code > 0) {
// check status code as readystatechange fires before error event
resolve(response);
}
else {
// give the error callback a chance to fire before resolving
// requests for file:// URLs do not have a status code
setTimeout(function () {
resolve(response);
}, 0);
}
}
};
try {
client.onerror = function (/* e */) {
response.error = 'loaderror';
reject(response);
};
}
catch (e) {
// IE 6 will not support error handling
}
client.send(entity);
}
catch (e) {
response.error = 'loaderror';
reject(response);
}
});
});
});
}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); },
typeof window !== 'undefined' ? window : void 0
// Boilerplate for AMD and Node
));