租房小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
8.8 KiB

3 months ago
  1. var capability = require('./capability');
  2. var inherits = require('inherits');
  3. var response = require('./response');
  4. var stream = require('readable-stream');
  5. var toArrayBuffer = require('to-arraybuffer');
  6. var IncomingMessage = response.IncomingMessage;
  7. var rStates = response.readyStates;
  8. function decideMode(preferBinary, useFetch) {
  9. if (capability.fetch && useFetch) {
  10. return 'fetch';
  11. } else if (capability.mozchunkedarraybuffer) {
  12. return 'moz-chunked-arraybuffer';
  13. } else if (capability.msstream) {
  14. return 'ms-stream';
  15. } else if (capability.arraybuffer && preferBinary) {
  16. return 'arraybuffer';
  17. } else if (capability.vbArray && preferBinary) {
  18. return 'text:vbarray';
  19. } else {
  20. return 'text';
  21. }
  22. }
  23. var ClientRequest = (module.exports = function (opts) {
  24. var self = this;
  25. stream.Writable.call(self);
  26. self._opts = opts;
  27. self._body = [];
  28. self._headers = {};
  29. if (opts.auth) self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64'));
  30. Object.keys(opts.headers).forEach(function (name) {
  31. self.setHeader(name, opts.headers[name]);
  32. });
  33. var preferBinary;
  34. var useFetch = true;
  35. if (opts.mode === 'disable-fetch' || ('requestTimeout' in opts && !capability.abortController)) {
  36. // If the use of XHR should be preferred. Not typically needed.
  37. useFetch = false;
  38. preferBinary = true;
  39. } else if (opts.mode === 'prefer-streaming') {
  40. // If streaming is a high priority but binary compatibility and
  41. // the accuracy of the 'content-type' header aren't
  42. preferBinary = false;
  43. } else if (opts.mode === 'allow-wrong-content-type') {
  44. // If streaming is more important than preserving the 'content-type' header
  45. preferBinary = !capability.overrideMimeType;
  46. } else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') {
  47. // Use binary if text streaming may corrupt data or the content-type header, or for speed
  48. preferBinary = true;
  49. } else {
  50. throw new Error('Invalid value for opts.mode');
  51. }
  52. self._mode = decideMode(preferBinary, useFetch);
  53. self._fetchTimer = null;
  54. self.on('finish', function () {
  55. self._onFinish();
  56. });
  57. });
  58. inherits(ClientRequest, stream.Writable);
  59. ClientRequest.prototype.setHeader = function (name, value) {
  60. var self = this;
  61. var lowerName = name.toLowerCase();
  62. // This check is not necessary, but it prevents warnings from browsers about setting unsafe
  63. // headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but
  64. // http-browserify did it, so I will too.
  65. if (unsafeHeaders.indexOf(lowerName) !== -1) return;
  66. self._headers[lowerName] = {
  67. name: name,
  68. value: value
  69. };
  70. };
  71. ClientRequest.prototype.getHeader = function (name) {
  72. var header = this._headers[name.toLowerCase()];
  73. if (header) return header.value;
  74. return null;
  75. };
  76. ClientRequest.prototype.removeHeader = function (name) {
  77. var self = this;
  78. delete self._headers[name.toLowerCase()];
  79. };
  80. ClientRequest.prototype._onFinish = function () {
  81. var self = this;
  82. if (self._destroyed) return;
  83. var opts = self._opts;
  84. var headersObj = self._headers;
  85. var body = null;
  86. if (opts.method !== 'GET' && opts.method !== 'HEAD') {
  87. if (capability.arraybuffer) {
  88. body = toArrayBuffer(Buffer.concat(self._body));
  89. } else if (capability.blobConstructor) {
  90. body = new global.Blob(
  91. self._body.map(function (buffer) {
  92. return toArrayBuffer(buffer);
  93. }),
  94. {
  95. type: (headersObj['content-type'] || {}).value || ''
  96. }
  97. );
  98. } else {
  99. // get utf8 string
  100. body = Buffer.concat(self._body).toString();
  101. }
  102. }
  103. // create flattened list of headers
  104. var headersList = [];
  105. Object.keys(headersObj).forEach(function (keyName) {
  106. var name = headersObj[keyName].name;
  107. var value = headersObj[keyName].value;
  108. if (Array.isArray(value)) {
  109. value.forEach(function (v) {
  110. headersList.push([name, v]);
  111. });
  112. } else {
  113. headersList.push([name, value]);
  114. }
  115. });
  116. if (self._mode === 'fetch') {
  117. var signal = null;
  118. var fetchTimer = null;
  119. if (capability.abortController) {
  120. var controller = new AbortController();
  121. signal = controller.signal;
  122. self._fetchAbortController = controller;
  123. if ('requestTimeout' in opts && opts.requestTimeout !== 0) {
  124. self._fetchTimer = global.setTimeout(function () {
  125. self.emit('requestTimeout');
  126. if (self._fetchAbortController) self._fetchAbortController.abort();
  127. }, opts.requestTimeout);
  128. }
  129. }
  130. global
  131. .fetch(self._opts.url, {
  132. method: self._opts.method,
  133. headers: headersList,
  134. body: body || undefined,
  135. mode: 'cors',
  136. credentials: opts.withCredentials ? 'include' : 'same-origin',
  137. signal: signal
  138. })
  139. .then(
  140. function (response) {
  141. self._fetchResponse = response;
  142. self._connect();
  143. },
  144. function (reason) {
  145. global.clearTimeout(self._fetchTimer);
  146. if (!self._destroyed) self.emit('error', reason);
  147. }
  148. );
  149. } else {
  150. var xhr = (self._xhr = new global.XMLHttpRequest());
  151. try {
  152. xhr.open(self._opts.method, self._opts.url, true);
  153. } catch (err) {
  154. process.nextTick(function () {
  155. self.emit('error', err);
  156. });
  157. return;
  158. }
  159. // Can't set responseType on really old browsers
  160. if ('responseType' in xhr) xhr.responseType = self._mode.split(':')[0];
  161. if ('withCredentials' in xhr) xhr.withCredentials = !!opts.withCredentials;
  162. if (self._mode === 'text' && 'overrideMimeType' in xhr) xhr.overrideMimeType('text/plain; charset=x-user-defined');
  163. if ('requestTimeout' in opts) {
  164. xhr.timeout = opts.requestTimeout;
  165. xhr.ontimeout = function () {
  166. self.emit('requestTimeout');
  167. };
  168. }
  169. headersList.forEach(function (header) {
  170. xhr.setRequestHeader(header[0], header[1]);
  171. });
  172. self._response = null;
  173. xhr.onreadystatechange = function () {
  174. switch (xhr.readyState) {
  175. case rStates.LOADING:
  176. case rStates.DONE:
  177. self._onXHRProgress();
  178. break;
  179. }
  180. };
  181. // Necessary for streaming in Firefox, since xhr.response is ONLY defined
  182. // in onprogress, not in onreadystatechange with xhr.readyState = 3
  183. if (self._mode === 'moz-chunked-arraybuffer') {
  184. xhr.onprogress = function () {
  185. self._onXHRProgress();
  186. };
  187. }
  188. xhr.onerror = function () {
  189. if (self._destroyed) return;
  190. self.emit('error', new Error('XHR error'));
  191. };
  192. try {
  193. xhr.send(body);
  194. } catch (err) {
  195. process.nextTick(function () {
  196. self.emit('error', err);
  197. });
  198. return;
  199. }
  200. }
  201. };
  202. /**
  203. * Checks if xhr.status is readable and non-zero, indicating no error.
  204. * Even though the spec says it should be available in readyState 3,
  205. * accessing it throws an exception in IE8
  206. */
  207. function statusValid(xhr) {
  208. try {
  209. var status = xhr.status;
  210. return status !== null && status !== 0;
  211. } catch (e) {
  212. return false;
  213. }
  214. }
  215. ClientRequest.prototype._onXHRProgress = function () {
  216. var self = this;
  217. if (!statusValid(self._xhr) || self._destroyed) return;
  218. if (!self._response) self._connect();
  219. self._response._onXHRProgress();
  220. };
  221. ClientRequest.prototype._connect = function () {
  222. var self = this;
  223. if (self._destroyed) return;
  224. self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode, self._fetchTimer);
  225. self._response.on('error', function (err) {
  226. self.emit('error', err);
  227. });
  228. self.emit('response', self._response);
  229. };
  230. ClientRequest.prototype._write = function (chunk, encoding, cb) {
  231. var self = this;
  232. self._body.push(chunk);
  233. cb();
  234. };
  235. ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function () {
  236. var self = this;
  237. self._destroyed = true;
  238. global.clearTimeout(self._fetchTimer);
  239. if (self._response) self._response._destroyed = true;
  240. if (self._xhr) self._xhr.abort();
  241. else if (self._fetchAbortController) self._fetchAbortController.abort();
  242. };
  243. ClientRequest.prototype.end = function (data, encoding, cb) {
  244. var self = this;
  245. if (typeof data === 'function') {
  246. cb = data;
  247. data = undefined;
  248. }
  249. stream.Writable.prototype.end.call(self, data, encoding, cb);
  250. };
  251. ClientRequest.prototype.flushHeaders = function () {};
  252. ClientRequest.prototype.setTimeout = function () {};
  253. ClientRequest.prototype.setNoDelay = function () {};
  254. ClientRequest.prototype.setSocketKeepAlive = function () {};
  255. // Taken from http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader%28%29-method
  256. var unsafeHeaders = [
  257. 'accept-charset',
  258. 'accept-encoding',
  259. 'access-control-request-headers',
  260. 'access-control-request-method',
  261. 'connection',
  262. 'content-length',
  263. 'cookie',
  264. 'cookie2',
  265. 'date',
  266. 'dnt',
  267. 'expect',
  268. 'host',
  269. 'keep-alive',
  270. 'origin',
  271. 'referer',
  272. 'te',
  273. 'trailer',
  274. 'transfer-encoding',
  275. 'upgrade',
  276. 'user-agent',
  277. 'via'
  278. ];