CentraRequest.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. const path = require('path')
  2. const http = require('http')
  3. const https = require('https')
  4. const followRedirects = require('follow-redirects')
  5. const qs = require('querystring')
  6. const zlib = require('zlib')
  7. const {URL} = require('url')
  8. const CentraResponse = require('./CentraResponse.js')
  9. const supportedCompressions = ['gzip', 'deflate', 'br']
  10. const useRequest = (protocol, maxRedirects) => {
  11. let httpr
  12. let httpsr
  13. if (maxRedirects <= 0) {
  14. httpr = http.request
  15. httpsr = https.request
  16. }
  17. else {
  18. httpr = followRedirects.http.request
  19. httpsr = followRedirects.https.request
  20. }
  21. if (protocol === 'http:') {
  22. return httpr
  23. }
  24. else if (protocol === 'https:') {
  25. return httpsr
  26. }
  27. else throw new Error('Bad URL protocol: ' + protocol)
  28. }
  29. module.exports = class CentraRequest {
  30. constructor (url, method = 'GET') {
  31. this.url = typeof url === 'string' ? new URL(url) : url
  32. this.method = method
  33. this.data = null
  34. this.sendDataAs = null
  35. this.reqHeaders = {}
  36. this.streamEnabled = false
  37. this.compressionEnabled = false
  38. this.timeoutTime = null
  39. this.coreOptions = {}
  40. this.maxRedirects = 0
  41. this.resOptions = {
  42. 'maxBuffer': 50 * 1000000 // 50 MB
  43. }
  44. return this
  45. }
  46. followRedirects(n) {
  47. this.maxRedirects = n
  48. return this
  49. }
  50. query (a1, a2) {
  51. if (typeof a1 === 'object') {
  52. Object.keys(a1).forEach((queryKey) => {
  53. this.url.searchParams.append(queryKey, a1[queryKey])
  54. })
  55. }
  56. else this.url.searchParams.append(a1, a2)
  57. return this
  58. }
  59. path (relativePath) {
  60. this.url.pathname = path.join(this.url.pathname, relativePath)
  61. return this
  62. }
  63. body (data, sendAs) {
  64. this.sendDataAs = typeof data === 'object' && !sendAs && !Buffer.isBuffer(data) ? 'json' : (sendAs ? sendAs.toLowerCase() : 'buffer')
  65. this.data = this.sendDataAs === 'form' ? qs.stringify(data) : (this.sendDataAs === 'json' ? JSON.stringify(data) : data)
  66. return this
  67. }
  68. header (a1, a2) {
  69. if (typeof a1 === 'object') {
  70. Object.keys(a1).forEach((headerName) => {
  71. this.reqHeaders[headerName.toLowerCase()] = a1[headerName]
  72. })
  73. }
  74. else this.reqHeaders[a1.toLowerCase()] = a2
  75. return this
  76. }
  77. timeout (timeout) {
  78. this.timeoutTime = timeout
  79. return this
  80. }
  81. option (name, value) {
  82. this.coreOptions[name] = value
  83. return this
  84. }
  85. stream () {
  86. this.streamEnabled = true
  87. return this
  88. }
  89. compress () {
  90. this.compressionEnabled = true
  91. if (!this.reqHeaders['accept-encoding']) this.reqHeaders['accept-encoding'] = supportedCompressions.join(', ')
  92. return this
  93. }
  94. send () {
  95. return new Promise((resolve, reject) => {
  96. if (this.data) {
  97. if (!this.reqHeaders.hasOwnProperty('content-type')) {
  98. if (this.sendDataAs === 'json') {
  99. this.reqHeaders['content-type'] = 'application/json'
  100. }
  101. else if (this.sendDataAs === 'form') {
  102. this.reqHeaders['content-type'] = 'application/x-www-form-urlencoded'
  103. }
  104. }
  105. if (!this.reqHeaders.hasOwnProperty('content-length')) {
  106. this.reqHeaders['content-length'] = Buffer.byteLength(this.data)
  107. }
  108. }
  109. const options = Object.assign({
  110. 'protocol': this.url.protocol,
  111. 'host': this.url.hostname.replace('[', '').replace(']', ''),
  112. 'port': this.url.port,
  113. 'path': this.url.pathname + (this.url.search === null ? '' : this.url.search),
  114. 'method': this.method,
  115. 'headers': this.reqHeaders,
  116. 'maxRedirects': this.maxRedirects
  117. }, this.coreOptions)
  118. let req
  119. const resHandler = (res) => {
  120. let stream = res
  121. if (this.compressionEnabled) {
  122. if (res.headers['content-encoding'] === 'gzip') {
  123. stream = res.pipe(zlib.createGunzip())
  124. }
  125. else if (res.headers['content-encoding'] === 'deflate') {
  126. stream = res.pipe(zlib.createInflate())
  127. }
  128. else if (res.headers['content-encoding'] === 'br') {
  129. stream = res.pipe(zlib.createBrotliDecompress())
  130. }
  131. }
  132. let centraRes
  133. if (this.streamEnabled) {
  134. resolve(stream)
  135. }
  136. else {
  137. centraRes = new CentraResponse(res, this.resOptions)
  138. stream.on('error', (err) => {
  139. reject(err)
  140. })
  141. stream.on('aborted', () => {
  142. reject(new Error('Server aborted request'))
  143. })
  144. stream.on('data', (chunk) => {
  145. centraRes._addChunk(chunk)
  146. if (this.resOptions.maxBuffer !== null && centraRes.body.length > this.resOptions.maxBuffer) {
  147. stream.destroy()
  148. reject('Received a response which was longer than acceptable when buffering. (' + this.body.length + ' bytes)')
  149. }
  150. })
  151. stream.on('end', () => {
  152. resolve(centraRes)
  153. })
  154. }
  155. }
  156. const request = useRequest(this.url.protocol, this.maxRedirects)
  157. req = request(options, resHandler)
  158. if (this.timeoutTime) {
  159. req.setTimeout(this.timeoutTime, () => {
  160. req.abort()
  161. if (!this.streamEnabled) {
  162. reject(new Error('Timeout reached'))
  163. }
  164. })
  165. }
  166. req.on('error', (err) => {
  167. reject(err)
  168. })
  169. if (this.data) req.write(this.data)
  170. req.end()
  171. })
  172. }
  173. }