response.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import {overrideMimeType} from './capability';
  2. import {inherits} from 'util';
  3. import {Readable} from 'stream';
  4. var rStates = {
  5. UNSENT: 0,
  6. OPENED: 1,
  7. HEADERS_RECEIVED: 2,
  8. LOADING: 3,
  9. DONE: 4
  10. }
  11. export {
  12. rStates as readyStates
  13. };
  14. export function IncomingMessage(xhr, response, mode) {
  15. var self = this
  16. Readable.call(self)
  17. self._mode = mode
  18. self.headers = {}
  19. self.rawHeaders = []
  20. self.trailers = {}
  21. self.rawTrailers = []
  22. // Fake the 'close' event, but only once 'end' fires
  23. self.on('end', function() {
  24. // The nextTick is necessary to prevent the 'request' module from causing an infinite loop
  25. process.nextTick(function() {
  26. self.emit('close')
  27. })
  28. })
  29. var read;
  30. if (mode === 'fetch') {
  31. self._fetchResponse = response
  32. self.url = response.url
  33. self.statusCode = response.status
  34. self.statusMessage = response.statusText
  35. // backwards compatible version of for (<item> of <iterable>):
  36. // for (var <item>,_i,_it = <iterable>[Symbol.iterator](); <item> = (_i = _it.next()).value,!_i.done;)
  37. for (var header, _i, _it = response.headers[Symbol.iterator](); header = (_i = _it.next()).value, !_i.done;) {
  38. self.headers[header[0].toLowerCase()] = header[1]
  39. self.rawHeaders.push(header[0], header[1])
  40. }
  41. // TODO: this doesn't respect backpressure. Once WritableStream is available, this can be fixed
  42. var reader = response.body.getReader()
  43. read = function () {
  44. reader.read().then(function(result) {
  45. if (self._destroyed)
  46. return
  47. if (result.done) {
  48. self.push(null)
  49. return
  50. }
  51. self.push(new Buffer(result.value))
  52. read()
  53. })
  54. }
  55. read()
  56. } else {
  57. self._xhr = xhr
  58. self._pos = 0
  59. self.url = xhr.responseURL
  60. self.statusCode = xhr.status
  61. self.statusMessage = xhr.statusText
  62. var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
  63. headers.forEach(function(header) {
  64. var matches = header.match(/^([^:]+):\s*(.*)/)
  65. if (matches) {
  66. var key = matches[1].toLowerCase()
  67. if (key === 'set-cookie') {
  68. if (self.headers[key] === undefined) {
  69. self.headers[key] = []
  70. }
  71. self.headers[key].push(matches[2])
  72. } else if (self.headers[key] !== undefined) {
  73. self.headers[key] += ', ' + matches[2]
  74. } else {
  75. self.headers[key] = matches[2]
  76. }
  77. self.rawHeaders.push(matches[1], matches[2])
  78. }
  79. })
  80. self._charset = 'x-user-defined'
  81. if (!overrideMimeType) {
  82. var mimeType = self.rawHeaders['mime-type']
  83. if (mimeType) {
  84. var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/)
  85. if (charsetMatch) {
  86. self._charset = charsetMatch[1].toLowerCase()
  87. }
  88. }
  89. if (!self._charset)
  90. self._charset = 'utf-8' // best guess
  91. }
  92. }
  93. }
  94. inherits(IncomingMessage, Readable)
  95. IncomingMessage.prototype._read = function() {}
  96. IncomingMessage.prototype._onXHRProgress = function() {
  97. var self = this
  98. var xhr = self._xhr
  99. var response = null
  100. switch (self._mode) {
  101. case 'text:vbarray': // For IE9
  102. if (xhr.readyState !== rStates.DONE)
  103. break
  104. try {
  105. // This fails in IE8
  106. response = new global.VBArray(xhr.responseBody).toArray()
  107. } catch (e) {
  108. // pass
  109. }
  110. if (response !== null) {
  111. self.push(new Buffer(response))
  112. break
  113. }
  114. // Falls through in IE8
  115. case 'text':
  116. try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
  117. response = xhr.responseText
  118. } catch (e) {
  119. self._mode = 'text:vbarray'
  120. break
  121. }
  122. if (response.length > self._pos) {
  123. var newData = response.substr(self._pos)
  124. if (self._charset === 'x-user-defined') {
  125. var buffer = new Buffer(newData.length)
  126. for (var i = 0; i < newData.length; i++)
  127. buffer[i] = newData.charCodeAt(i) & 0xff
  128. self.push(buffer)
  129. } else {
  130. self.push(newData, self._charset)
  131. }
  132. self._pos = response.length
  133. }
  134. break
  135. case 'arraybuffer':
  136. if (xhr.readyState !== rStates.DONE || !xhr.response)
  137. break
  138. response = xhr.response
  139. self.push(new Buffer(new Uint8Array(response)))
  140. break
  141. case 'moz-chunked-arraybuffer': // take whole
  142. response = xhr.response
  143. if (xhr.readyState !== rStates.LOADING || !response)
  144. break
  145. self.push(new Buffer(new Uint8Array(response)))
  146. break
  147. case 'ms-stream':
  148. response = xhr.response
  149. if (xhr.readyState !== rStates.LOADING)
  150. break
  151. var reader = new global.MSStreamReader()
  152. reader.onprogress = function() {
  153. if (reader.result.byteLength > self._pos) {
  154. self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
  155. self._pos = reader.result.byteLength
  156. }
  157. }
  158. reader.onload = function() {
  159. self.push(null)
  160. }
  161. // reader.onerror = ??? // TODO: this
  162. reader.readAsArrayBuffer(response)
  163. break
  164. }
  165. // The ms-stream case handles end separately in reader.onload()
  166. if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
  167. self.push(null)
  168. }
  169. }