response.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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) {
  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. // TODO: this doesn't respect backpressure. Once WritableStream is available, this can be fixed
  36. var reader = response.body.getReader()
  37. function read () {
  38. reader.read().then(function (result) {
  39. if (self._destroyed)
  40. return
  41. if (result.done) {
  42. self.push(null)
  43. return
  44. }
  45. self.push(new Buffer(result.value))
  46. read()
  47. }).catch(function(err) {
  48. self.emit('error', err)
  49. })
  50. }
  51. read()
  52. } else {
  53. self._xhr = xhr
  54. self._pos = 0
  55. self.url = xhr.responseURL
  56. self.statusCode = xhr.status
  57. self.statusMessage = xhr.statusText
  58. var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
  59. headers.forEach(function (header) {
  60. var matches = header.match(/^([^:]+):\s*(.*)/)
  61. if (matches) {
  62. var key = matches[1].toLowerCase()
  63. if (key === 'set-cookie') {
  64. if (self.headers[key] === undefined) {
  65. self.headers[key] = []
  66. }
  67. self.headers[key].push(matches[2])
  68. } else if (self.headers[key] !== undefined) {
  69. self.headers[key] += ', ' + matches[2]
  70. } else {
  71. self.headers[key] = matches[2]
  72. }
  73. self.rawHeaders.push(matches[1], matches[2])
  74. }
  75. })
  76. self._charset = 'x-user-defined'
  77. if (!capability.overrideMimeType) {
  78. var mimeType = self.rawHeaders['mime-type']
  79. if (mimeType) {
  80. var charsetMatch = mimeType.match(/;\s*charset=([^;])(;|$)/)
  81. if (charsetMatch) {
  82. self._charset = charsetMatch[1].toLowerCase()
  83. }
  84. }
  85. if (!self._charset)
  86. self._charset = 'utf-8' // best guess
  87. }
  88. }
  89. }
  90. inherits(IncomingMessage, stream.Readable)
  91. IncomingMessage.prototype._read = function () {}
  92. IncomingMessage.prototype._onXHRProgress = function () {
  93. var self = this
  94. var xhr = self._xhr
  95. var response = null
  96. switch (self._mode) {
  97. case 'text:vbarray': // For IE9
  98. if (xhr.readyState !== rStates.DONE)
  99. break
  100. try {
  101. // This fails in IE8
  102. response = new global.VBArray(xhr.responseBody).toArray()
  103. } catch (e) {}
  104. if (response !== null) {
  105. self.push(new Buffer(response))
  106. break
  107. }
  108. // Falls through in IE8
  109. case 'text':
  110. try { // This will fail when readyState = 3 in IE9. Switch mode and wait for readyState = 4
  111. response = xhr.responseText
  112. } catch (e) {
  113. self._mode = 'text:vbarray'
  114. break
  115. }
  116. if (response.length > self._pos) {
  117. var newData = response.substr(self._pos)
  118. if (self._charset === 'x-user-defined') {
  119. var buffer = new Buffer(newData.length)
  120. for (var i = 0; i < newData.length; i++)
  121. buffer[i] = newData.charCodeAt(i) & 0xff
  122. self.push(buffer)
  123. } else {
  124. self.push(newData, self._charset)
  125. }
  126. self._pos = response.length
  127. }
  128. break
  129. case 'arraybuffer':
  130. if (xhr.readyState !== rStates.DONE || !xhr.response)
  131. break
  132. response = xhr.response
  133. self.push(new Buffer(new Uint8Array(response)))
  134. break
  135. case 'moz-chunked-arraybuffer': // take whole
  136. response = xhr.response
  137. if (xhr.readyState !== rStates.LOADING || !response)
  138. break
  139. self.push(new Buffer(new Uint8Array(response)))
  140. break
  141. case 'ms-stream':
  142. response = xhr.response
  143. if (xhr.readyState !== rStates.LOADING)
  144. break
  145. var reader = new global.MSStreamReader()
  146. reader.onprogress = function () {
  147. if (reader.result.byteLength > self._pos) {
  148. self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
  149. self._pos = reader.result.byteLength
  150. }
  151. }
  152. reader.onload = function () {
  153. self.push(null)
  154. }
  155. // reader.onerror = ??? // TODO: this
  156. reader.readAsArrayBuffer(response)
  157. break
  158. }
  159. // The ms-stream case handles end separately in reader.onload()
  160. if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
  161. self.push(null)
  162. }
  163. }