Spaces:
Configuration error
Configuration error
import * as capability from './capability'; | |
import {inherits} from 'util'; | |
import {IncomingMessage, readyStates as rStates} from './response'; | |
import {Writable} from 'stream'; | |
import toArrayBuffer from './to-arraybuffer'; | |
function decideMode(preferBinary, useFetch) { | |
if (capability.hasFetch && useFetch) { | |
return 'fetch' | |
} else if (capability.mozchunkedarraybuffer) { | |
return 'moz-chunked-arraybuffer' | |
} else if (capability.msstream) { | |
return 'ms-stream' | |
} else if (capability.arraybuffer && preferBinary) { | |
return 'arraybuffer' | |
} else if (capability.vbArray && preferBinary) { | |
return 'text:vbarray' | |
} else { | |
return 'text' | |
} | |
} | |
export default ClientRequest; | |
function ClientRequest(opts) { | |
var self = this | |
Writable.call(self) | |
self._opts = opts | |
self._body = [] | |
self._headers = {} | |
if (opts.auth) | |
self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64')) | |
Object.keys(opts.headers).forEach(function(name) { | |
self.setHeader(name, opts.headers[name]) | |
}) | |
var preferBinary | |
var useFetch = true | |
if (opts.mode === 'disable-fetch') { | |
// If the use of XHR should be preferred and includes preserving the 'content-type' header | |
useFetch = false | |
preferBinary = true | |
} else if (opts.mode === 'prefer-streaming') { | |
// If streaming is a high priority but binary compatibility and | |
// the accuracy of the 'content-type' header aren't | |
preferBinary = false | |
} else if (opts.mode === 'allow-wrong-content-type') { | |
// If streaming is more important than preserving the 'content-type' header | |
preferBinary = !capability.overrideMimeType | |
} else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') { | |
// Use binary if text streaming may corrupt data or the content-type header, or for speed | |
preferBinary = true | |
} else { | |
throw new Error('Invalid value for opts.mode') | |
} | |
self._mode = decideMode(preferBinary, useFetch) | |
self.on('finish', function() { | |
self._onFinish() | |
}) | |
} | |
inherits(ClientRequest, Writable) | |
// Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method | |
var unsafeHeaders = [ | |
'accept-charset', | |
'accept-encoding', | |
'access-control-request-headers', | |
'access-control-request-method', | |
'connection', | |
'content-length', | |
'cookie', | |
'cookie2', | |
'date', | |
'dnt', | |
'expect', | |
'host', | |
'keep-alive', | |
'origin', | |
'referer', | |
'te', | |
'trailer', | |
'transfer-encoding', | |
'upgrade', | |
'user-agent', | |
'via' | |
] | |
ClientRequest.prototype.setHeader = function(name, value) { | |
var self = this | |
var lowerName = name.toLowerCase() | |
// This check is not necessary, but it prevents warnings from browsers about setting unsafe | |
// headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but | |
// http-browserify did it, so I will too. | |
if (unsafeHeaders.indexOf(lowerName) !== -1) | |
return | |
self._headers[lowerName] = { | |
name: name, | |
value: value | |
} | |
} | |
ClientRequest.prototype.getHeader = function(name) { | |
var self = this | |
return self._headers[name.toLowerCase()].value | |
} | |
ClientRequest.prototype.removeHeader = function(name) { | |
var self = this | |
delete self._headers[name.toLowerCase()] | |
} | |
ClientRequest.prototype._onFinish = function() { | |
var self = this | |
if (self._destroyed) | |
return | |
var opts = self._opts | |
var headersObj = self._headers | |
var body | |
if (opts.method === 'POST' || opts.method === 'PUT' || opts.method === 'PATCH') { | |
if (capability.blobConstructor()) { | |
body = new global.Blob(self._body.map(function(buffer) { | |
return toArrayBuffer(buffer) | |
}), { | |
type: (headersObj['content-type'] || {}).value || '' | |
}) | |
} else { | |
// get utf8 string | |
body = Buffer.concat(self._body).toString() | |
} | |
} | |
if (self._mode === 'fetch') { | |
var headers = Object.keys(headersObj).map(function(name) { | |
return [headersObj[name].name, headersObj[name].value] | |
}) | |
global.fetch(self._opts.url, { | |
method: self._opts.method, | |
headers: headers, | |
body: body, | |
mode: 'cors', | |
credentials: opts.withCredentials ? 'include' : 'same-origin' | |
}).then(function(response) { | |
self._fetchResponse = response | |
self._connect() | |
}, function(reason) { | |
self.emit('error', reason) | |
}) | |
} else { | |
var xhr = self._xhr = new global.XMLHttpRequest() | |
try { | |
xhr.open(self._opts.method, self._opts.url, true) | |
} catch (err) { | |
process.nextTick(function() { | |
self.emit('error', err) | |
}) | |
return | |
} | |
// Can't set responseType on really old browsers | |
if ('responseType' in xhr) | |
xhr.responseType = self._mode.split(':')[0] | |
if ('withCredentials' in xhr) | |
xhr.withCredentials = !!opts.withCredentials | |
if (self._mode === 'text' && 'overrideMimeType' in xhr) | |
xhr.overrideMimeType('text/plain; charset=x-user-defined') | |
Object.keys(headersObj).forEach(function(name) { | |
xhr.setRequestHeader(headersObj[name].name, headersObj[name].value) | |
}) | |
self._response = null | |
xhr.onreadystatechange = function() { | |
switch (xhr.readyState) { | |
case rStates.LOADING: | |
case rStates.DONE: | |
self._onXHRProgress() | |
break | |
} | |
} | |
// Necessary for streaming in Firefox, since xhr.response is ONLY defined | |
// in onprogress, not in onreadystatechange with xhr.readyState = 3 | |
if (self._mode === 'moz-chunked-arraybuffer') { | |
xhr.onprogress = function() { | |
self._onXHRProgress() | |
} | |
} | |
xhr.onerror = function() { | |
if (self._destroyed) | |
return | |
self.emit('error', new Error('XHR error')) | |
} | |
try { | |
xhr.send(body) | |
} catch (err) { | |
process.nextTick(function() { | |
self.emit('error', err) | |
}) | |
return | |
} | |
} | |
} | |
/** | |
* Checks if xhr.status is readable and non-zero, indicating no error. | |
* Even though the spec says it should be available in readyState 3, | |
* accessing it throws an exception in IE8 | |
*/ | |
function statusValid(xhr) { | |
try { | |
var status = xhr.status | |
return (status !== null && status !== 0) | |
} catch (e) { | |
return false | |
} | |
} | |
ClientRequest.prototype._onXHRProgress = function() { | |
var self = this | |
if (!statusValid(self._xhr) || self._destroyed) | |
return | |
if (!self._response) | |
self._connect() | |
self._response._onXHRProgress() | |
} | |
ClientRequest.prototype._connect = function() { | |
var self = this | |
if (self._destroyed) | |
return | |
self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode) | |
self.emit('response', self._response) | |
} | |
ClientRequest.prototype._write = function(chunk, encoding, cb) { | |
var self = this | |
self._body.push(chunk) | |
cb() | |
} | |
ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function() { | |
var self = this | |
self._destroyed = true | |
if (self._response) | |
self._response._destroyed = true | |
if (self._xhr) | |
self._xhr.abort() | |
// Currently, there isn't a way to truly abort a fetch. | |
// If you like bikeshedding, see https://github.com/whatwg/fetch/issues/27 | |
} | |
ClientRequest.prototype.end = function(data, encoding, cb) { | |
var self = this | |
if (typeof data === 'function') { | |
cb = data | |
data = undefined | |
} | |
Writable.prototype.end.call(self, data, encoding, cb) | |
} | |
ClientRequest.prototype.flushHeaders = function() {} | |
ClientRequest.prototype.setTimeout = function() {} | |
ClientRequest.prototype.setNoDelay = function() {} | |
ClientRequest.prototype.setSocketKeepAlive = function() {} | |