|
|
- var capability = require('./capability');
- var inherits = require('inherits');
- var stream = require('readable-stream');
-
- var rStates = (exports.readyStates = {
- UNSENT: 0,
- OPENED: 1,
- HEADERS_RECEIVED: 2,
- LOADING: 3,
- DONE: 4
- });
-
- var IncomingMessage = (exports.IncomingMessage = function (xhr, response, mode, fetchTimer) {
- var self = this;
- stream.Readable.call(self);
-
- self._mode = mode;
- self.headers = {};
- self.rawHeaders = [];
- self.trailers = {};
- self.rawTrailers = [];
-
- // Fake the 'close' event, but only once 'end' fires
- self.on('end', function () {
- // The nextTick is necessary to prevent the 'request' module from causing an infinite loop
- process.nextTick(function () {
- self.emit('close');
- });
- });
-
- if (mode === 'fetch') {
- self._fetchResponse = response;
-
- self.url = response.url;
- self.statusCode = response.status;
- self.statusMessage = response.statusText;
-
- response.headers.forEach(function (header, key) {
- self.headers[key.toLowerCase()] = header;
- self.rawHeaders.push(key, header);
- });
-
- if (capability.writableStream) {
- var writable = new WritableStream({
- write: function (chunk) {
- return new Promise(function (resolve, reject) {
- if (self._destroyed) {
- reject();
- } else if (self.push(new Buffer(chunk))) {
- resolve();
- } else {
- self._resumeFetch = resolve;
- }
- });
- },
- close: function () {
- global.clearTimeout(fetchTimer);
- if (!self._destroyed) self.push(null);
- },
- abort: function (err) {
- if (!self._destroyed) self.emit('error', err);
- }
- });
-
- try {
- response.body.pipeTo(writable).catch(function (err) {
- global.clearTimeout(fetchTimer);
- if (!self._destroyed) self.emit('error', err);
- });
- return;
- } catch (e) {} // pipeTo method isn't defined. Can't find a better way to feature test this
- }
- // fallback for when writableStream or pipeTo aren't available
- var reader = response.body.getReader();
- function read() {
- reader
- .read()
- .then(function (result) {
- if (self._destroyed) return;
- if (result.done) {
- global.clearTimeout(fetchTimer);
- self.push(null);
- return;
- }
- self.push(new Buffer(result.value));
- read();
- })
- .catch(function (err) {
- global.clearTimeout(fetchTimer);
- if (!self._destroyed) self.emit('error', err);
- });
- }
- read();
- } else {
- self._xhr = xhr;
- self._pos = 0;
-
- self.url = xhr.responseURL;
- self.statusCode = xhr.status;
- self.statusMessage = xhr.statusText;
- var headers = xhr.getAllResponseHeaders().split(/\r?\n/);
- headers.forEach(function (header) {
- var matches = header.match(/^([^:]+):\s*(.*)/);
- if (matches) {
- var key = matches[1].toLowerCase();
- if (key === 'set-cookie') {
- if (self.headers[key] === undefined) {
- self.headers[key] = [];
- }
- self.headers[key].push(matches[2]);
- } else if (self.headers[key] !== undefined) {
- self.headers[key] += ', ' + matches[2];
- } else {
- self.headers[key] = matches[2];
- }
- self.rawHeaders.push(matches[1], matches[2]);
- }
- });
-
- self._charset = 'x-user-defined';
- if (!capability.overrideMimeType) {
- var mimeType = self.rawHeaders['mime-type'];
- if (mimeType) {
- var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/);
- if (charsetMatch) {
- self._charset = charsetMatch[1].toLowerCase();
- }
- }
- if (!self._charset) self._charset = 'utf-8'; // best guess
- }
- }
- });
-
- inherits(IncomingMessage, stream.Readable);
-
- IncomingMessage.prototype._read = function () {
- var self = this;
-
- var resolve = self._resumeFetch;
- if (resolve) {
- self._resumeFetch = null;
- resolve();
- }
- };
-
- IncomingMessage.prototype._onXHRProgress = function () {
- var self = this;
-
- var xhr = self._xhr;
-
- var response = null;
- switch (self._mode) {
- case 'text:vbarray': // For IE9
- if (xhr.readyState !== rStates.DONE) break;
- try {
- // This fails in IE8
- response = new global.VBArray(xhr.responseBody).toArray();
- } catch (e) {}
- if (response !== null) {
- self.push(new Buffer(response));
- break;
- }
- // Falls through in IE8
- case 'text':
- try {
- // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
- response = xhr.responseText;
- } catch (e) {
- self._mode = 'text:vbarray';
- break;
- }
- if (response.length > self._pos) {
- var newData = response.substr(self._pos);
- if (self._charset === 'x-user-defined') {
- var buffer = new Buffer(newData.length);
- for (var i = 0; i < newData.length; i++) buffer[i] = newData.charCodeAt(i) & 0xff;
-
- self.push(buffer);
- } else {
- self.push(newData, self._charset);
- }
- self._pos = response.length;
- }
- break;
- case 'arraybuffer':
- if (xhr.readyState !== rStates.DONE || !xhr.response) break;
- response = xhr.response;
- self.push(new Buffer(new Uint8Array(response)));
- break;
- case 'moz-chunked-arraybuffer': // take whole
- response = xhr.response;
- if (xhr.readyState !== rStates.LOADING || !response) break;
- self.push(new Buffer(new Uint8Array(response)));
- break;
- case 'ms-stream':
- response = xhr.response;
- if (xhr.readyState !== rStates.LOADING) break;
- var reader = new global.MSStreamReader();
- reader.onprogress = function () {
- if (reader.result.byteLength > self._pos) {
- self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))));
- self._pos = reader.result.byteLength;
- }
- };
- reader.onload = function () {
- self.push(null);
- };
- // reader.onerror = ??? // TODO: this
- reader.readAsArrayBuffer(response);
- break;
- }
-
- // The ms-stream case handles end separately in reader.onload()
- if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
- self.push(null);
- }
- };
|