response.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. var capability = require('./capability')
  2. var inherits = require('inherits')
  3. var stream = require('readable-stream')
  4. var rStates = exports.readyStates = {
  5. UNSENT: 0,
  6. OPENED: 1,
  7. HEADERS_RECEIVED: 2,
  8. LOADING: 3,
  9. DONE: 4
  10. }
  11. var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, resetTimers) {
  12. var self = this
  13. stream.Readable.call(self)
  14. self._mode = mode
  15. self.headers = {}
  16. self.rawHeaders = []
  17. self.trailers = {}
  18. self.rawTrailers = []
  19. // Fake the 'close' event, but only once 'end' fires
  20. self.on('end', function () {
  21. // The nextTick is necessary to prevent the 'request' module from causing an infinite loop
  22. process.nextTick(function () {
  23. self.emit('close')
  24. })
  25. })
  26. if (mode === 'fetch') {
  27. self._fetchResponse = response
  28. self.url = response.url
  29. self.statusCode = response.status
  30. self.statusMessage = response.statusText
  31. response.headers.forEach(function (header, key){
  32. self.headers[key.toLowerCase()] = header
  33. self.rawHeaders.push(key, header)
  34. })
  35. if (capability.writableStream) {
  36. var writable = new WritableStream({
  37. write: function (chunk) {
  38. resetTimers(false)
  39. return new Promise(function (resolve, reject) {
  40. if (self._destroyed) {
  41. reject()
  42. } else if(self.push(Buffer.from(chunk))) {
  43. resolve()
  44. } else {
  45. self._resumeFetch = resolve
  46. }
  47. })
  48. },
  49. close: function () {
  50. resetTimers(true)
  51. if (!self._destroyed)
  52. self.push(null)
  53. },
  54. abort: function (err) {
  55. resetTimers(true)
  56. if (!self._destroyed)
  57. self.emit('error', err)
  58. }
  59. })
  60. try {
  61. response.body.pipeTo(writable).catch(function (err) {
  62. resetTimers(true)
  63. if (!self._destroyed)
  64. self.emit('error', err)
  65. })
  66. return
  67. } catch (e) {} // pipeTo method isn't defined. Can't find a better way to feature test this
  68. }
  69. // fallback for when writableStream or pipeTo aren't available
  70. var reader = response.body.getReader()
  71. function read () {
  72. reader.read().then(function (result) {
  73. if (self._destroyed)
  74. return
  75. resetTimers(result.done)
  76. if (result.done) {
  77. self.push(null)
  78. return
  79. }
  80. self.push(Buffer.from(result.value))
  81. read()
  82. }).catch(function (err) {
  83. resetTimers(true)
  84. if (!self._destroyed)
  85. self.emit('error', err)
  86. })
  87. }
  88. read()
  89. } else {
  90. self._xhr = xhr
  91. self._pos = 0
  92. self.url = xhr.responseURL
  93. self.statusCode = xhr.status
  94. self.statusMessage = xhr.statusText
  95. var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
  96. headers.forEach(function (header) {
  97. var matches = header.match(/^([^:]+):\s*(.*)/)
  98. if (matches) {
  99. var key = matches[1].toLowerCase()
  100. if (key === 'set-cookie') {
  101. if (self.headers[key] === undefined) {
  102. self.headers[key] = []
  103. }
  104. self.headers[key].push(matches[2])
  105. } else if (self.headers[key] !== undefined) {
  106. self.headers[key] += ', ' + matches[2]
  107. } else {
  108. self.headers[key] = matches[2]
  109. }
  110. self.rawHeaders.push(matches[1], matches[2])
  111. }
  112. })
  113. self._charset = 'x-user-defined'
  114. if (!capability.overrideMimeType) {
  115. var mimeType = self.rawHeaders['mime-type']
  116. if (mimeType) {
  117. var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/)
  118. if (charsetMatch) {
  119. self._charset = charsetMatch[1].toLowerCase()
  120. }
  121. }
  122. if (!self._charset)
  123. self._charset = 'utf-8' // best guess
  124. }
  125. }
  126. }
  127. inherits(IncomingMessage, stream.Readable)
  128. IncomingMessage.prototype._read = function () {
  129. var self = this
  130. var resolve = self._resumeFetch
  131. if (resolve) {
  132. self._resumeFetch = null
  133. resolve()
  134. }
  135. }
  136. IncomingMessage.prototype._onXHRProgress = function (resetTimers) {
  137. var self = this
  138. var xhr = self._xhr
  139. var response = null
  140. switch (self._mode) {
  141. case 'text':
  142. response = xhr.responseText
  143. if (response.length > self._pos) {
  144. var newData = response.substr(self._pos)
  145. if (self._charset === 'x-user-defined') {
  146. var buffer = Buffer.alloc(newData.length)
  147. for (var i = 0; i < newData.length; i++)
  148. buffer[i] = newData.charCodeAt(i) & 0xff
  149. self.push(buffer)
  150. } else {
  151. self.push(newData, self._charset)
  152. }
  153. self._pos = response.length
  154. }
  155. break
  156. case 'arraybuffer':
  157. if (xhr.readyState !== rStates.DONE || !xhr.response)
  158. break
  159. response = xhr.response
  160. self.push(Buffer.from(new Uint8Array(response)))
  161. break
  162. case 'moz-chunked-arraybuffer': // take whole
  163. response = xhr.response
  164. if (xhr.readyState !== rStates.LOADING || !response)
  165. break
  166. self.push(Buffer.from(new Uint8Array(response)))
  167. break
  168. case 'ms-stream':
  169. response = xhr.response
  170. if (xhr.readyState !== rStates.LOADING)
  171. break
  172. var reader = new global.MSStreamReader()
  173. reader.onprogress = function () {
  174. if (reader.result.byteLength > self._pos) {
  175. self.push(Buffer.from(new Uint8Array(reader.result.slice(self._pos))))
  176. self._pos = reader.result.byteLength
  177. }
  178. }
  179. reader.onload = function () {
  180. resetTimers(true)
  181. self.push(null)
  182. }
  183. // reader.onerror = ??? // TODO: this
  184. reader.readAsArrayBuffer(response)
  185. break
  186. }
  187. // The ms-stream case handles end separately in reader.onload()
  188. if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
  189. resetTimers(true)
  190. self.push(null)
  191. }
  192. }