odin/node_modules/requests/index.js
2022-11-26 15:56:34 +01:00

324 lines
8.1 KiB
JavaScript

'use strict';
var Requested = require('./requested')
, listeners = require('loads')
, send = require('xhr-send')
, hang = require('hang')
, AXO = require('axo')
, XMLHttpRequest = require('node-http-xhr');
/**
* RequestS(tream).
*
* Options:
*
* - streaming: Should the request be streaming.
* - method: Which HTTP method should be used.
* - headers: Additional request headers.
* - mode: Enable CORS mode.
* - body: The payload for the request.
*
* @constructor
* @param {String} url The URL we want to request.
* @param {Object} options Various of request options.
* @api public
*/
var Requests = module.exports = Requested.extend({
constructor: function bobthebuilder(url, options) {
if (!(this instanceof Requests)) return new Requests(url, options);
Requested.apply(this, arguments);
},
/**
* The offset of data that we've already previously read
*
* @type {Number}
* @private
*/
offset: 0,
/**
* The requests instance has been fully initialized.
*
* @param {String} url The URL we need to connect to.
* @api private
*/
initialize: function initialize(url) {
this.socket = Requests[Requests.method](this);
//
// Open the socket BEFORE adding any properties to the instance as this might
// trigger a thrown `InvalidStateError: An attempt was made to use an object
// that is not, or is no longer, usable` error in FireFox:
//
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=707484
//
this.socket.open(this.method.toUpperCase(), url, true);
//
// Register this as an active HTTP request.
//
Requests.active[this.id] = this;
},
/**
* Initialize and start requesting the supplied resource.
*
* @param {Object} options Passed in defaults.
* @api private
*/
open: function open() {
var what
, slice = true
, requests = this
, socket = requests.socket;
requests.on('stream', function stream(data) {
if (!slice) {
return requests.emit('data', data);
}
//
// Please note that we need to use a method here that works on both string
// as well as ArrayBuffer's as we have no certainty that we're receiving
// text.
//
var chunk = data.slice(requests.offset);
requests.offset = data.length;
requests.emit('data', chunk);
});
requests.on('end', function cleanup() {
delete Requests.active[requests.id];
});
if (this.timeout) {
socket.timeout = +this.timeout;
}
if ('cors' === this.mode.toLowerCase() && 'withCredentials' in socket) {
socket.withCredentials = true;
}
//
// ActiveXObject will throw an `Type Mismatch` exception when setting the to
// an null-value and to be consistent with all XHR implementations we're going
// to cast the value to a string.
//
// While we don't technically support the XDomainRequest of IE, we do want to
// double check that the setRequestHeader is available before adding headers.
//
// Chrome has a bug where it will actually append values to the header instead
// of overriding it. So if you do a double setRequestHeader(Content-Type) with
// text/plain and with text/plain again, it will end up as `text/plain,
// text/plain` as header value. This is why use a headers object as it
// already eliminates duplicate headers.
//
for (what in this.headers) {
if (this.headers[what] !== undefined && this.socket.setRequestHeader) {
this.socket.setRequestHeader(what, this.headers[what] +'');
}
}
//
// Set the correct responseType method.
//
if (requests.streaming) {
if (!this.body || 'string' === typeof this.body) {
if ('multipart' in socket) {
socket.multipart = true;
slice = false;
} else if (Requests.type.mozchunkedtext) {
socket.responseType = 'moz-chunked-text';
slice = false;
}
} else {
if (Requests.type.mozchunkedarraybuffer) {
socket.responseType = 'moz-chunked-arraybuffer';
} else if (Requests.type.msstream) {
socket.responseType = 'ms-stream';
}
}
}
listeners(socket, requests, requests.streaming);
requests.emit('before', socket);
send(socket, this.body, hang(function send(err) {
if (err) {
requests.emit('error', err);
requests.emit('end', err);
}
requests.emit('send');
}));
},
/**
* Completely destroy the running XHR and release of the internal references.
*
* @returns {Boolean} Successful destruction
* @api public
*/
destroy: function destroy() {
if (!this.socket) return false;
this.emit('destroy');
this.socket.abort();
this.removeAllListeners();
this.headers = {};
this.socket = null;
this.body = null;
delete Requests.active[this.id];
return true;
}
});
/**
* Create a new XMLHttpRequest.
*
* @returns {XMLHttpRequest}
* @api private
*/
Requests.XHR = function create() {
try { return new XMLHttpRequest(); }
catch (e) {}
};
/**
* Create a new ActiveXObject which can be used for XHR.
*
* @returns {ActiveXObject}
* @api private
*/
Requests.AXO = function create() {
var ids = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'Microsoft.XMLHTTP']
, id;
while (ids.length) {
id = ids.shift();
try { return new AXO(id); }
catch (e) {}
}
};
/**
* Requests that are currently running.
*
* @type {Object}
* @private
*/
Requests.active = {};
/**
* The type of technology we are using to establish a working Ajax connection.
* This can either be:
*
* - XHR: XMLHttpRequest
* - AXO: ActiveXObject
*
* This is also used as internal optimization so we can easily get the correct
* constructor as we've already feature detected it.
*
* @type {String}
* @public
*/
Requests.method = !!Requests.XHR() ? 'XHR' : (!!Requests.AXO() ? 'AXO' : '');
/**
* Boolean indicating
*
* @type {Boolean}
* @public
*/
Requests.supported = !!Requests.method;
/**
* The different type of `responseType` parsers that are supported in this XHR
* implementation.
*
* @type {Object}
* @public
*/
Requests.type = 'XHR' === Requests.method ? (function detect() {
var types = 'arraybuffer,blob,document,json,text,moz-blob,moz-chunked-text,moz-chunked-arraybuffer,ms-stream'.split(',')
, supported = {}
, type, xhr, prop;
while (types.length) {
type = types.pop();
prop = type.replace(/-/g, '');
xhr = Requests.XHR();
//
// Older versions of Firefox/IE11 will throw an error because previous
// version of the specification do not support setting `responseType`
// before the request is opened. Thus, we open the request here.
//
// Note that `open()` does not actually open any connections; it just
// initializes the request object.
//
try {
// Try opening a request to current page.
xhr.open('get', '/', true);
} catch (e) {
// In JSDOM the above will fail because it only supports full URLs, so
// try opening a request to localhost.
try {
xhr.open('get', 'http://localhost/', true);
} catch (err) {
supported[prop] = false;
continue;
}
}
try {
xhr.responseType = type;
supported[prop] = 'response' in xhr && xhr.responseType === type;
} catch (e) {
supported[prop] = false;
}
xhr = null;
}
return supported;
}()) : {};
/**
* Do we support streaming response parsing.
*
* @type {Boolean}
* @private
*/
Requests.streaming = 'XHR' === Requests.method && (
'multipart' in XMLHttpRequest.prototype
|| Requests.type.mozchunkedarraybuffer
|| Requests.type.mozchunkedtext
|| Requests.type.msstream
|| Requests.type.mozblob
);
//
// IE has a bug which causes IE10 to freeze when close WebPage during an XHR
// request: https://support.microsoft.com/kb/2856746
//
// The solution is to completely clean up all active running requests.
//
if (global.attachEvent) global.attachEvent('onunload', function reap() {
for (var id in Requests.active) {
Requests.active[id].destroy();
}
});
//
// Expose the Requests library.
//
module.exports = Requests;