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.

response.js 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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, fetchTimer) {
  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. return new Promise(function (resolve, reject) {
  39. if (self._destroyed) {
  40. reject()
  41. } else if(self.push(new Buffer(chunk))) {
  42. resolve()
  43. } else {
  44. self._resumeFetch = resolve
  45. }
  46. })
  47. },
  48. close: function () {
  49. global.clearTimeout(fetchTimer)
  50. if (!self._destroyed)
  51. self.push(null)
  52. },
  53. abort: function (err) {
  54. if (!self._destroyed)
  55. self.emit('error', err)
  56. }
  57. })
  58. try {
  59. response.body.pipeTo(writable).catch(function (err) {
  60. global.clearTimeout(fetchTimer)
  61. if (!self._destroyed)
  62. self.emit('error', err)
  63. })
  64. return
  65. } catch (e) {} // pipeTo method isn't defined. Can't find a better way to feature test this
  66. }
  67. // fallback for when writableStream or pipeTo aren't available
  68. var reader = response.body.getReader()
  69. function read () {
  70. reader.read().then(function (result) {
  71. if (self._destroyed)
  72. return
  73. if (result.done) {
  74. global.clearTimeout(fetchTimer)
  75. self.push(null)
  76. return
  77. }
  78. self.push(new Buffer(result.value))
  79. read()
  80. }).catch(function (err) {
  81. global.clearTimeout(fetchTimer)
  82. if (!self._destroyed)
  83. self.emit('error', err)
  84. })
  85. }
  86. read()
  87. } else {
  88. self._xhr = xhr
  89. self._pos = 0
  90. self.url = xhr.responseURL
  91. self.statusCode = xhr.status
  92. self.statusMessage = xhr.statusText
  93. var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
  94. headers.forEach(function (header) {
  95. var matches = header.match(/^([^:]+):\s*(.*)/)
  96. if (matches) {
  97. var key = matches[1].toLowerCase()
  98. if (key === 'set-cookie') {
  99. if (self.headers[key] === undefined) {
  100. self.headers[key] = []
  101. }
  102. self.headers[key].push(matches[2])
  103. } else if (self.headers[key] !== undefined) {
  104. self.headers[key] += ', ' + matches[2]
  105. } else {
  106. self.headers[key] = matches[2]
  107. }
  108. self.rawHeaders.push(matches[1], matches[2])
  109. }
  110. })
  111. self._charset = 'x-user-defined'
  112. if (!capability.overrideMimeType) {
  113. var mimeType = self.rawHeaders['mime-type']
  114. if (mimeType) {
  115. var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/)
  116. if (charsetMatch) {
  117. self._charset = charsetMatch[1].toLowerCase()
  118. }
  119. }
  120. if (!self._charset)
  121. self._charset = 'utf-8' // best guess
  122. }
  123. }
  124. }
  125. inherits(IncomingMessage, stream.Readable)
  126. IncomingMessage.prototype._read = function () {
  127. var self = this
  128. var resolve = self._resumeFetch
  129. if (resolve) {
  130. self._resumeFetch = null
  131. resolve()
  132. }
  133. }
  134. IncomingMessage.prototype._onXHRProgress = function () {
  135. var self = this
  136. var xhr = self._xhr
  137. var response = null
  138. switch (self._mode) {
  139. case 'text:vbarray': // For IE9
  140. if (xhr.readyState !== rStates.DONE)
  141. break
  142. try {
  143. // This fails in IE8
  144. response = new global.VBArray(xhr.responseBody).toArray()
  145. } catch (e) {}
  146. if (response !== null) {
  147. self.push(new Buffer(response))
  148. break
  149. }
  150. // Falls through in IE8
  151. case 'text':
  152. try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
  153. response = xhr.responseText
  154. } catch (e) {
  155. self._mode = 'text:vbarray'
  156. break
  157. }
  158. if (response.length > self._pos) {
  159. var newData = response.substr(self._pos)
  160. if (self._charset === 'x-user-defined') {
  161. var buffer = new Buffer(newData.length)
  162. for (var i = 0; i < newData.length; i++)
  163. buffer[i] = newData.charCodeAt(i) & 0xff
  164. self.push(buffer)
  165. } else {
  166. self.push(newData, self._charset)
  167. }
  168. self._pos = response.length
  169. }
  170. break
  171. case 'arraybuffer':
  172. if (xhr.readyState !== rStates.DONE || !xhr.response)
  173. break
  174. response = xhr.response
  175. self.push(new Buffer(new Uint8Array(response)))
  176. break
  177. case 'moz-chunked-arraybuffer': // take whole
  178. response = xhr.response
  179. if (xhr.readyState !== rStates.LOADING || !response)
  180. break
  181. self.push(new Buffer(new Uint8Array(response)))
  182. break
  183. case 'ms-stream':
  184. response = xhr.response
  185. if (xhr.readyState !== rStates.LOADING)
  186. break
  187. var reader = new global.MSStreamReader()
  188. reader.onprogress = function () {
  189. if (reader.result.byteLength > self._pos) {
  190. self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
  191. self._pos = reader.result.byteLength
  192. }
  193. }
  194. reader.onload = function () {
  195. self.push(null)
  196. }
  197. // reader.onerror = ??? // TODO: this
  198. reader.readAsArrayBuffer(response)
  199. break
  200. }
  201. // The ms-stream case handles end separately in reader.onload()
  202. if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
  203. self.push(null)
  204. }
  205. }