fastify 4.23.2 → 4.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/docs/Guides/Ecosystem.md +6 -0
  3. package/docs/Reference/Hooks.md +1 -0
  4. package/docs/Reference/Plugins.md +1 -1
  5. package/docs/Reference/Reply.md +4 -3
  6. package/docs/Reference/Request.md +3 -2
  7. package/docs/Reference/Server.md +31 -3
  8. package/docs/Reference/Type-Providers.md +2 -2
  9. package/docs/Reference/TypeScript.md +21 -7
  10. package/fastify.d.ts +2 -2
  11. package/fastify.js +8 -1
  12. package/lib/contentTypeParser.js +1 -1
  13. package/lib/reply.js +20 -3
  14. package/lib/reqIdGenFactory.js +15 -9
  15. package/lib/request.js +1 -1
  16. package/lib/route.js +28 -7
  17. package/lib/schemas.js +3 -3
  18. package/lib/warnings.js +3 -1
  19. package/package.json +33 -33
  20. package/test/404s.test.js +31 -39
  21. package/test/async-await.test.js +1 -1
  22. package/test/async-dispose.test.js +21 -0
  23. package/test/build-certificate.js +90 -1
  24. package/test/close-pipelining.test.js +5 -5
  25. package/test/close.test.js +1 -5
  26. package/test/constrained-routes.test.js +127 -3
  27. package/test/custom-http-server.test.js +94 -91
  28. package/test/custom-parser.0.test.js +21 -47
  29. package/test/custom-parser.1.test.js +10 -732
  30. package/test/custom-parser.2.test.js +102 -0
  31. package/test/custom-parser.3.test.js +245 -0
  32. package/test/custom-parser.4.test.js +239 -0
  33. package/test/custom-parser.5.test.js +149 -0
  34. package/test/head.test.js +204 -0
  35. package/test/helper.js +30 -8
  36. package/test/hooks-async.test.js +163 -13
  37. package/test/hooks.on-listen.test.js +7 -6
  38. package/test/hooks.test.js +4 -15
  39. package/test/http2/closing.test.js +7 -15
  40. package/test/https/custom-https-server.test.js +43 -40
  41. package/test/input-validation.js +3 -3
  42. package/test/internals/reply.test.js +33 -4
  43. package/test/listen.1.test.js +101 -0
  44. package/test/listen.2.test.js +103 -0
  45. package/test/listen.3.test.js +87 -0
  46. package/test/listen.4.test.js +164 -0
  47. package/test/listen.deprecated.test.js +3 -9
  48. package/test/logger/instantiation.test.js +347 -0
  49. package/test/logger/logger-test-utils.js +47 -0
  50. package/test/logger/logging.test.js +406 -0
  51. package/test/logger/options.test.js +500 -0
  52. package/test/logger/request.test.js +292 -0
  53. package/test/logger/response.test.js +184 -0
  54. package/test/plugin.1.test.js +249 -0
  55. package/test/plugin.2.test.js +328 -0
  56. package/test/plugin.3.test.js +311 -0
  57. package/test/plugin.4.test.js +416 -0
  58. package/test/reply-code.test.js +64 -0
  59. package/test/reply-trailers.test.js +1 -2
  60. package/test/route.1.test.js +309 -0
  61. package/test/route.2.test.js +99 -0
  62. package/test/route.3.test.js +205 -0
  63. package/test/route.4.test.js +131 -0
  64. package/test/route.5.test.js +230 -0
  65. package/test/route.6.test.js +306 -0
  66. package/test/route.7.test.js +370 -0
  67. package/test/route.8.test.js +142 -0
  68. package/test/stream.1.test.js +108 -0
  69. package/test/stream.2.test.js +119 -0
  70. package/test/stream.3.test.js +192 -0
  71. package/test/stream.4.test.js +223 -0
  72. package/test/stream.5.test.js +194 -0
  73. package/test/trust-proxy.test.js +2 -4
  74. package/test/types/reply.test-d.ts +3 -3
  75. package/test/types/request.test-d.ts +9 -9
  76. package/test/types/type-provider.test-d.ts +89 -0
  77. package/test/types/using.test-d.ts +14 -0
  78. package/test/upgrade.test.js +3 -3
  79. package/types/context.d.ts +9 -2
  80. package/types/instance.d.ts +4 -1
  81. package/types/plugin.d.ts +2 -1
  82. package/types/reply.d.ts +2 -2
  83. package/types/request.d.ts +3 -3
  84. package/types/route.d.ts +5 -5
  85. package/test/listen.test.js +0 -427
  86. package/test/plugin.test.js +0 -1275
  87. package/test/route.test.js +0 -1762
  88. package/test/serial/logger.0.test.js +0 -866
  89. package/test/serial/logger.1.test.js +0 -862
  90. package/test/stream.test.js +0 -816
  91. /package/test/{serial → logger}/tap-parallel-not-ok +0 -0
@@ -0,0 +1,164 @@
1
+ 'use strict'
2
+
3
+ const { test, before } = require('tap')
4
+ const dns = require('node:dns').promises
5
+ const dnsCb = require('node:dns')
6
+ const sget = require('simple-get').concat
7
+ const Fastify = require('../fastify')
8
+ const helper = require('./helper')
9
+
10
+ let localhostForURL
11
+
12
+ function getUrl (fastify, lookup) {
13
+ const { port } = fastify.server.address()
14
+ if (lookup.family === 6) {
15
+ return `http://[${lookup.address}]:${port}/`
16
+ } else {
17
+ return `http://${lookup.address}:${port}/`
18
+ }
19
+ }
20
+
21
+ before(async function () {
22
+ [, localhostForURL] = await helper.getLoopbackHost()
23
+ })
24
+
25
+ test('listen twice on the same port without callback rejects', t => {
26
+ t.plan(1)
27
+ const fastify = Fastify()
28
+ t.teardown(fastify.close.bind(fastify))
29
+
30
+ fastify.listen({ port: 0 })
31
+ .then(() => {
32
+ const s2 = Fastify()
33
+ t.teardown(s2.close.bind(s2))
34
+ s2.listen({ port: fastify.server.address().port })
35
+ .catch(err => {
36
+ t.ok(err)
37
+ })
38
+ })
39
+ .catch(err => t.error(err))
40
+ })
41
+
42
+ test('listen twice on the same port without callback rejects with (address)', t => {
43
+ t.plan(2)
44
+ const fastify = Fastify()
45
+ t.teardown(fastify.close.bind(fastify))
46
+ fastify.listen({ port: 0 })
47
+ .then(address => {
48
+ const s2 = Fastify()
49
+ t.teardown(s2.close.bind(s2))
50
+ t.equal(address, `http://${localhostForURL}:${fastify.server.address().port}`)
51
+ s2.listen({ port: fastify.server.address().port })
52
+ .catch(err => {
53
+ t.ok(err)
54
+ })
55
+ })
56
+ .catch(err => t.error(err))
57
+ })
58
+
59
+ test('listen on invalid port without callback rejects', t => {
60
+ const fastify = Fastify()
61
+ t.teardown(fastify.close.bind(fastify))
62
+ return fastify.listen({ port: -1 })
63
+ .catch(err => {
64
+ t.ok(err)
65
+ return true
66
+ })
67
+ })
68
+
69
+ test('listen logs the port as info', t => {
70
+ t.plan(1)
71
+ const fastify = Fastify()
72
+ t.teardown(fastify.close.bind(fastify))
73
+
74
+ const msgs = []
75
+ fastify.log.info = function (msg) {
76
+ msgs.push(msg)
77
+ }
78
+
79
+ fastify.listen({ port: 0 })
80
+ .then(() => {
81
+ t.ok(/http:\/\//.test(msgs[0]))
82
+ })
83
+ })
84
+
85
+ test('listen on localhost binds IPv4 and IPv6 - promise interface', async t => {
86
+ const localAddresses = await dns.lookup('localhost', { all: true })
87
+ t.plan(2 * localAddresses.length)
88
+
89
+ const app = Fastify()
90
+ app.get('/', async () => 'hello localhost')
91
+ t.teardown(app.close.bind(app))
92
+ await app.listen({ port: 0, host: 'localhost' })
93
+
94
+ for (const lookup of localAddresses) {
95
+ await new Promise((resolve, reject) => {
96
+ sget({
97
+ method: 'GET',
98
+ url: getUrl(app, lookup)
99
+ }, (err, response, body) => {
100
+ if (err) { return reject(err) }
101
+ t.equal(response.statusCode, 200)
102
+ t.same(body.toString(), 'hello localhost')
103
+ resolve()
104
+ })
105
+ })
106
+ }
107
+ })
108
+
109
+ test('listen on localhost binds to all interfaces (both IPv4 and IPv6 if present) - callback interface', t => {
110
+ dnsCb.lookup('localhost', { all: true }, (err, lookups) => {
111
+ t.plan(2 + (3 * lookups.length))
112
+ t.error(err)
113
+
114
+ const app = Fastify()
115
+ app.get('/', async () => 'hello localhost')
116
+ app.listen({ port: 0, host: 'localhost' }, (err) => {
117
+ t.error(err)
118
+ t.teardown(app.close.bind(app))
119
+
120
+ for (const lookup of lookups) {
121
+ sget({
122
+ method: 'GET',
123
+ url: getUrl(app, lookup)
124
+ }, (err, response, body) => {
125
+ t.error(err)
126
+ t.equal(response.statusCode, 200)
127
+ t.same(body.toString(), 'hello localhost')
128
+ })
129
+ }
130
+ })
131
+ })
132
+ })
133
+
134
+ test('addresses getter', async t => {
135
+ let localAddresses = await dns.lookup('localhost', { all: true })
136
+
137
+ t.plan(4)
138
+ const app = Fastify()
139
+ app.get('/', async () => 'hello localhost')
140
+
141
+ t.same(app.addresses(), [], 'before ready')
142
+ await app.ready()
143
+
144
+ t.same(app.addresses(), [], 'after ready')
145
+ await app.listen({ port: 0, host: 'localhost' })
146
+
147
+ // fix citgm
148
+ // dns lookup may have duplicated addresses (rhel8-s390x rhel8-ppc64le debian10-x64)
149
+
150
+ localAddresses = [...new Set([...localAddresses.map(a => JSON.stringify({
151
+ address: a.address,
152
+ family: typeof a.family === 'number' ? 'IPv' + a.family : a.family
153
+ }))])].sort()
154
+
155
+ const appAddresses = app.addresses().map(a => JSON.stringify({
156
+ address: a.address,
157
+ family: typeof a.family === 'number' ? 'IPv' + a.family : a.family
158
+ })).sort()
159
+
160
+ t.same(appAddresses, localAddresses, 'after listen')
161
+
162
+ await app.close()
163
+ t.same(app.addresses(), [], 'after close')
164
+ })
@@ -4,22 +4,16 @@
4
4
  // removed when the deprecation is complete.
5
5
 
6
6
  const { test, before } = require('tap')
7
- const dns = require('node:dns').promises
8
7
  const Fastify = require('..')
8
+ const helper = require('./helper')
9
9
 
10
10
  let localhost
11
11
  let localhostForURL
12
12
 
13
13
  process.removeAllListeners('warning')
14
14
 
15
- before(async function (t) {
16
- const lookup = await dns.lookup('localhost')
17
- localhost = lookup.address
18
- if (lookup.family === 6) {
19
- localhostForURL = `[${lookup.address}]`
20
- } else {
21
- localhostForURL = localhost
22
- }
15
+ before(async () => {
16
+ [localhost, localhostForURL] = await helper.getLoopbackHost()
23
17
  })
24
18
 
25
19
  test('listen accepts a port and a callback', t => {
@@ -0,0 +1,347 @@
1
+ 'use strict'
2
+
3
+ const stream = require('node:stream')
4
+ const os = require('node:os')
5
+ const fs = require('node:fs')
6
+
7
+ const t = require('tap')
8
+ const split = require('split2')
9
+
10
+ const { streamSym } = require('pino/lib/symbols')
11
+
12
+ const Fastify = require('../../fastify')
13
+ const helper = require('../helper')
14
+ const { FST_ERR_LOG_INVALID_LOGGER } = require('../../lib/errors')
15
+ const { once, on } = stream
16
+ const { createTempFile, request } = require('./logger-test-utils')
17
+
18
+ t.test('logger instantiation', (t) => {
19
+ t.setTimeout(60000)
20
+
21
+ let localhost
22
+ let localhostForURL
23
+
24
+ t.plan(11)
25
+ t.before(async function () {
26
+ [localhost, localhostForURL] = await helper.getLoopbackHost()
27
+ })
28
+
29
+ t.test('can use external logger instance', async (t) => {
30
+ const lines = [/^Server listening at /, /^incoming request$/, /^log success$/, /^request completed$/]
31
+ t.plan(lines.length + 1)
32
+
33
+ const stream = split(JSON.parse)
34
+
35
+ const logger = require('pino')(stream)
36
+
37
+ const fastify = Fastify({ logger })
38
+ t.teardown(fastify.close.bind(fastify))
39
+
40
+ fastify.get('/foo', function (req, reply) {
41
+ t.ok(req.log)
42
+ req.log.info('log success')
43
+ reply.send({ hello: 'world' })
44
+ })
45
+
46
+ await fastify.listen({ port: 0, host: localhost })
47
+
48
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo')
49
+
50
+ for await (const [line] of on(stream, 'data')) {
51
+ const regex = lines.shift()
52
+ t.ok(regex.test(line.msg), '"' + line.msg + '" dont match "' + regex + '"')
53
+ if (lines.length === 0) break
54
+ }
55
+ })
56
+
57
+ t.test('should create a default logger if provided one is invalid', (t) => {
58
+ t.plan(8)
59
+
60
+ const logger = new Date()
61
+
62
+ const fastify = Fastify({ logger })
63
+ t.teardown(fastify.close.bind(fastify))
64
+
65
+ t.equal(typeof fastify.log, 'object')
66
+ t.equal(typeof fastify.log.fatal, 'function')
67
+ t.equal(typeof fastify.log.error, 'function')
68
+ t.equal(typeof fastify.log.warn, 'function')
69
+ t.equal(typeof fastify.log.info, 'function')
70
+ t.equal(typeof fastify.log.debug, 'function')
71
+ t.equal(typeof fastify.log.trace, 'function')
72
+ t.equal(typeof fastify.log.child, 'function')
73
+ })
74
+
75
+ t.test('expose the logger', async (t) => {
76
+ t.plan(2)
77
+ const stream = split(JSON.parse)
78
+ const fastify = Fastify({
79
+ logger: {
80
+ stream,
81
+ level: 'info'
82
+ }
83
+ })
84
+ t.teardown(fastify.close.bind(fastify))
85
+
86
+ await fastify.ready()
87
+
88
+ t.ok(fastify.log)
89
+ t.same(typeof fastify.log, 'object')
90
+ })
91
+
92
+ t.test('Wrap IPv6 address in listening log message', async (t) => {
93
+ t.plan(1)
94
+
95
+ const interfaces = os.networkInterfaces()
96
+ const ipv6 = Object.keys(interfaces)
97
+ .filter(name => name.substr(0, 2) === 'lo')
98
+ .map(name => interfaces[name])
99
+ .reduce((list, set) => list.concat(set), [])
100
+ .filter(info => info.family === 'IPv6')
101
+ .map(info => info.address)
102
+ .shift()
103
+
104
+ if (ipv6 === undefined) {
105
+ t.pass('No IPv6 loopback interface')
106
+ } else {
107
+ const stream = split(JSON.parse)
108
+ const fastify = Fastify({
109
+ logger: {
110
+ stream,
111
+ level: 'info'
112
+ }
113
+ })
114
+ t.teardown(fastify.close.bind(fastify))
115
+
116
+ await fastify.ready()
117
+ await fastify.listen({ port: 0, host: ipv6 })
118
+
119
+ {
120
+ const [line] = await once(stream, 'data')
121
+ t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`)
122
+ }
123
+ }
124
+ })
125
+
126
+ t.test('Do not wrap IPv4 address', async (t) => {
127
+ t.plan(1)
128
+ const stream = split(JSON.parse)
129
+ const fastify = Fastify({
130
+ logger: {
131
+ stream,
132
+ level: 'info'
133
+ }
134
+ })
135
+ t.teardown(fastify.close.bind(fastify))
136
+
137
+ await fastify.ready()
138
+ await fastify.listen({ port: 0, host: '127.0.0.1' })
139
+
140
+ {
141
+ const [line] = await once(stream, 'data')
142
+ t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`)
143
+ }
144
+ })
145
+
146
+ t.test('file option', async (t) => {
147
+ const lines = [
148
+ { msg: /Server listening at/ },
149
+ { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' },
150
+ { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' }
151
+ ]
152
+
153
+ const { file, cleanup } = createTempFile(t)
154
+ if (process.env.CITGM) { fs.writeFileSync(file, '') }
155
+
156
+ const fastify = Fastify({
157
+ logger: { file }
158
+ })
159
+
160
+ t.teardown(async () => {
161
+ await helper.sleep(250)
162
+ // may fail on win
163
+ try {
164
+ // cleanup the file after sonic-boom closed
165
+ // otherwise we may face racing condition
166
+ fastify.log[streamSym].once('close', cleanup)
167
+ // we must flush the stream ourself
168
+ // otherwise buffer may whole sonic-boom
169
+ fastify.log[streamSym].flushSync()
170
+ // end after flushing to actually close file
171
+ fastify.log[streamSym].end()
172
+ } catch (err) {
173
+ console.warn(err)
174
+ }
175
+ })
176
+ t.teardown(fastify.close.bind(fastify))
177
+
178
+ fastify.get('/', function (req, reply) {
179
+ t.ok(req.log)
180
+ reply.send({ hello: 'world' })
181
+ })
182
+
183
+ await fastify.ready()
184
+ await fastify.listen({ port: 0, host: localhost })
185
+ await request(`http://${localhostForURL}:` + fastify.server.address().port)
186
+
187
+ await helper.sleep(250)
188
+
189
+ const log = fs.readFileSync(file, 'utf8').split('\n')
190
+ // strip last line
191
+ log.pop()
192
+
193
+ let id
194
+ for (let line of log) {
195
+ line = JSON.parse(line)
196
+ if (id === undefined && line.reqId) id = line.reqId
197
+ if (id !== undefined && line.reqId) t.equal(line.reqId, id)
198
+ t.match(line, lines.shift())
199
+ }
200
+ })
201
+
202
+ t.test('should be able to use a custom logger', (t) => {
203
+ t.plan(7)
204
+
205
+ const logger = {
206
+ fatal: (msg) => { t.equal(msg, 'fatal') },
207
+ error: (msg) => { t.equal(msg, 'error') },
208
+ warn: (msg) => { t.equal(msg, 'warn') },
209
+ info: (msg) => { t.equal(msg, 'info') },
210
+ debug: (msg) => { t.equal(msg, 'debug') },
211
+ trace: (msg) => { t.equal(msg, 'trace') },
212
+ child: () => logger
213
+ }
214
+
215
+ const fastify = Fastify({ logger })
216
+ t.teardown(fastify.close.bind(fastify))
217
+
218
+ fastify.log.fatal('fatal')
219
+ fastify.log.error('error')
220
+ fastify.log.warn('warn')
221
+ fastify.log.info('info')
222
+ fastify.log.debug('debug')
223
+ fastify.log.trace('trace')
224
+ const child = fastify.log.child()
225
+ t.equal(child, logger)
226
+ })
227
+
228
+ t.test('should throw in case a partially matching logger is provided', async (t) => {
229
+ t.plan(1)
230
+
231
+ try {
232
+ const fastify = Fastify({ logger: console })
233
+ await fastify.ready()
234
+ } catch (err) {
235
+ t.equal(
236
+ err instanceof FST_ERR_LOG_INVALID_LOGGER,
237
+ true,
238
+ "Invalid logger object provided. The logger instance should have these functions(s): 'fatal,child'."
239
+ )
240
+ }
241
+ })
242
+
243
+ t.test('can use external logger instance with custom serializer', async (t) => {
244
+ const lines = [['level', 30], ['req', { url: '/foo' }], ['level', 30], ['res', { statusCode: 200 }]]
245
+ t.plan(lines.length + 1)
246
+
247
+ const stream = split(JSON.parse)
248
+ const logger = require('pino')({
249
+ level: 'info',
250
+ serializers: {
251
+ req: function (req) {
252
+ return {
253
+ url: req.url
254
+ }
255
+ }
256
+ }
257
+ }, stream)
258
+
259
+ const fastify = Fastify({
260
+ logger
261
+ })
262
+ t.teardown(fastify.close.bind(fastify))
263
+
264
+ fastify.get('/foo', function (req, reply) {
265
+ t.ok(req.log)
266
+ req.log.info('log success')
267
+ reply.send({ hello: 'world' })
268
+ })
269
+
270
+ await fastify.ready()
271
+ await fastify.listen({ port: 0, host: localhost })
272
+
273
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/foo')
274
+
275
+ for await (const [line] of on(stream, 'data')) {
276
+ const check = lines.shift()
277
+ const key = check[0]
278
+ const value = check[1]
279
+ t.same(line[key], value)
280
+ if (lines.length === 0) break
281
+ }
282
+ })
283
+
284
+ t.test('The logger should accept custom serializer', async (t) => {
285
+ const lines = [
286
+ { msg: /^Server listening at / },
287
+ { req: { url: '/custom' }, msg: 'incoming request' },
288
+ { res: { statusCode: 500 }, msg: 'kaboom' },
289
+ { res: { statusCode: 500 }, msg: 'request completed' }
290
+ ]
291
+ t.plan(lines.length + 1)
292
+
293
+ const stream = split(JSON.parse)
294
+ const fastify = Fastify({
295
+ logger: {
296
+ stream,
297
+ level: 'info',
298
+ serializers: {
299
+ req: function (req) {
300
+ return {
301
+ url: req.url
302
+ }
303
+ }
304
+ }
305
+ }
306
+ })
307
+ t.teardown(fastify.close.bind(fastify))
308
+
309
+ fastify.get('/custom', function (req, reply) {
310
+ t.ok(req.log)
311
+ reply.send(new Error('kaboom'))
312
+ })
313
+
314
+ await fastify.ready()
315
+ await fastify.listen({ port: 0, host: localhost })
316
+
317
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/custom')
318
+
319
+ for await (const [line] of on(stream, 'data')) {
320
+ t.match(line, lines.shift())
321
+ if (lines.length === 0) break
322
+ }
323
+ })
324
+
325
+ t.test('should throw in case the external logger provided does not have a child method', async (t) => {
326
+ t.plan(1)
327
+ const loggerInstance = {
328
+ info: console.info,
329
+ error: console.error,
330
+ debug: console.debug,
331
+ fatal: console.error,
332
+ warn: console.warn,
333
+ trace: console.trace
334
+ }
335
+
336
+ try {
337
+ const fastify = Fastify({ logger: loggerInstance })
338
+ await fastify.ready()
339
+ } catch (err) {
340
+ t.equal(
341
+ err instanceof FST_ERR_LOG_INVALID_LOGGER,
342
+ true,
343
+ "Invalid logger object provided. The logger instance should have these functions(s): 'child'."
344
+ )
345
+ }
346
+ })
347
+ })
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const http = require('node:http')
4
+ const os = require('node:os')
5
+ const fs = require('node:fs')
6
+
7
+ const path = require('node:path')
8
+
9
+ function createDeferredPromise () {
10
+ const promise = {}
11
+ promise.promise = new Promise(function (resolve) {
12
+ promise.resolve = resolve
13
+ })
14
+ return promise
15
+ }
16
+
17
+ let count = 0
18
+ function createTempFile () {
19
+ const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${count++}`)
20
+ function cleanup () {
21
+ try {
22
+ fs.unlinkSync(file)
23
+ } catch { }
24
+ }
25
+ return { file, cleanup }
26
+ }
27
+
28
+ function request (url, cleanup = () => { }) {
29
+ const promise = createDeferredPromise()
30
+ http.get(url, (res) => {
31
+ const chunks = []
32
+ // we consume the response
33
+ res.on('data', function (chunk) {
34
+ chunks.push(chunk)
35
+ })
36
+ res.once('end', function () {
37
+ cleanup(res, Buffer.concat(chunks).toString())
38
+ promise.resolve()
39
+ })
40
+ })
41
+ return promise.promise
42
+ }
43
+
44
+ module.exports = {
45
+ request,
46
+ createTempFile
47
+ }