1 | 'use strict'
|
2 |
|
3 | const t = require('tap')
|
4 | const test = t.test
|
5 | const sget = require('simple-get').concat
|
6 | const fs = require('fs')
|
7 | const resolve = require('path').resolve
|
8 | const zlib = require('zlib')
|
9 | const pump = require('pump')
|
10 | const Fastify = require('..')
|
11 | const errors = require('http-errors')
|
12 | const JSONStream = require('JSONStream')
|
13 | const send = require('send')
|
14 | const Readable = require('stream').Readable
|
15 | const split = require('split2')
|
16 |
|
17 | test('should respond with a stream', t => {
|
18 | t.plan(8)
|
19 | const fastify = Fastify()
|
20 |
|
21 | fastify.get('/', function (req, reply) {
|
22 | const stream = fs.createReadStream(__filename, 'utf8')
|
23 | reply.code(200).send(stream)
|
24 | })
|
25 |
|
26 | fastify.get('/error', function (req, reply) {
|
27 | const stream = fs.createReadStream('not-existing-file', 'utf8')
|
28 | reply.code(200).send(stream)
|
29 | })
|
30 |
|
31 | fastify.listen(0, err => {
|
32 | t.error(err)
|
33 | fastify.server.unref()
|
34 |
|
35 | sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) {
|
36 | t.error(err)
|
37 | t.strictEqual(response.headers['content-type'], 'application/octet-stream')
|
38 | t.strictEqual(response.statusCode, 200)
|
39 |
|
40 | fs.readFile(__filename, (err, expected) => {
|
41 | t.error(err)
|
42 | t.equal(expected.toString(), data.toString())
|
43 | })
|
44 | })
|
45 |
|
46 | sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) {
|
47 | t.error(err)
|
48 | t.strictEqual(response.statusCode, 500)
|
49 | })
|
50 | })
|
51 | })
|
52 |
|
53 | test('should trigger the onSend hook', t => {
|
54 | t.plan(4)
|
55 | const fastify = Fastify()
|
56 |
|
57 | fastify.get('/', (req, reply) => {
|
58 | reply.send(fs.createReadStream(__filename, 'utf8'))
|
59 | })
|
60 |
|
61 | fastify.addHook('onSend', (req, reply, payload, next) => {
|
62 | t.ok(payload._readableState)
|
63 | reply.header('Content-Type', 'application/javascript')
|
64 | next()
|
65 | })
|
66 |
|
67 | fastify.inject({
|
68 | url: '/'
|
69 | }, (err, res) => {
|
70 | t.error(err)
|
71 | t.strictEqual(res.headers['content-type'], 'application/javascript')
|
72 | t.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8'))
|
73 | fastify.close()
|
74 | })
|
75 | })
|
76 |
|
77 | test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', t => {
|
78 | t.plan(5)
|
79 | const fastify = Fastify()
|
80 |
|
81 | fastify.get('/', (req, reply) => {
|
82 | reply.send(fs.createReadStream('not-existing-file', 'utf8'))
|
83 | })
|
84 |
|
85 | let counter = 0
|
86 | fastify.addHook('onSend', (req, reply, payload, next) => {
|
87 | if (counter === 0) {
|
88 | t.ok(payload._readableState)
|
89 | } else if (counter === 1) {
|
90 | const error = JSON.parse(payload)
|
91 | t.strictEqual(error.statusCode, 500)
|
92 | }
|
93 | counter++
|
94 | next()
|
95 | })
|
96 |
|
97 | fastify.listen(0, err => {
|
98 | t.error(err)
|
99 |
|
100 | fastify.server.unref()
|
101 |
|
102 | sget(`http://localhost:${fastify.server.address().port}`, function (err, response) {
|
103 | t.error(err)
|
104 | t.strictEqual(response.statusCode, 500)
|
105 | })
|
106 | })
|
107 | })
|
108 |
|
109 | test('onSend hook stream', t => {
|
110 | t.plan(4)
|
111 | const fastify = Fastify()
|
112 |
|
113 | fastify.get('/', function (req, reply) {
|
114 | reply.send({ hello: 'world' })
|
115 | })
|
116 |
|
117 | fastify.addHook('onSend', (req, reply, payload, next) => {
|
118 | const gzStream = zlib.createGzip()
|
119 |
|
120 | reply.header('Content-Encoding', 'gzip')
|
121 | pump(
|
122 | fs.createReadStream(resolve(process.cwd() + '/test/stream.test.js'), 'utf8'),
|
123 | gzStream,
|
124 | t.error
|
125 | )
|
126 | next(null, gzStream)
|
127 | })
|
128 |
|
129 | fastify.inject({
|
130 | url: '/',
|
131 | method: 'GET'
|
132 | }, (err, res) => {
|
133 | t.error(err)
|
134 | t.strictEqual(res.headers['content-encoding'], 'gzip')
|
135 | const file = fs.readFileSync(resolve(process.cwd() + '/test/stream.test.js'), 'utf8')
|
136 | const payload = zlib.gunzipSync(res.rawPayload)
|
137 | t.strictEqual(payload.toString('utf-8'), file)
|
138 | fastify.close()
|
139 | })
|
140 | })
|
141 |
|
142 | test('Destroying streams prematurely', t => {
|
143 | t.plan(6)
|
144 |
|
145 | let fastify = null
|
146 | const logStream = split(JSON.parse)
|
147 | try {
|
148 | fastify = Fastify({
|
149 | logger: {
|
150 | stream: logStream,
|
151 | level: 'warn'
|
152 | }
|
153 | })
|
154 | } catch (e) {
|
155 | t.fail()
|
156 | }
|
157 | const stream = require('stream')
|
158 | const http = require('http')
|
159 |
|
160 |
|
161 | logStream.once('data', line => {
|
162 | t.equal(line.msg, 'response terminated with an error with headers already sent')
|
163 | t.equal(line.level, 40)
|
164 | })
|
165 |
|
166 | fastify.get('/', function (request, reply) {
|
167 | t.pass('Received request')
|
168 |
|
169 | var sent = false
|
170 | var reallyLongStream = new stream.Readable({
|
171 | read: function () {
|
172 | if (!sent) {
|
173 | this.push(Buffer.from('hello\n'))
|
174 | }
|
175 | sent = true
|
176 | }
|
177 | })
|
178 |
|
179 | reply.send(reallyLongStream)
|
180 | })
|
181 |
|
182 | fastify.listen(0, err => {
|
183 | t.error(err)
|
184 | fastify.server.unref()
|
185 |
|
186 | var port = fastify.server.address().port
|
187 |
|
188 | http.get(`http://localhost:${port}`, function (response) {
|
189 | t.strictEqual(response.statusCode, 200)
|
190 | response.on('readable', function () {
|
191 | response.destroy()
|
192 | })
|
193 |
|
194 |
|
195 | response.on('aborted', function () {
|
196 | t.pass('Response closed')
|
197 | })
|
198 | })
|
199 | })
|
200 | })
|
201 |
|
202 | test('Destroying streams prematurely should call close method', t => {
|
203 | t.plan(7)
|
204 |
|
205 | let fastify = null
|
206 | const logStream = split(JSON.parse)
|
207 | try {
|
208 | fastify = Fastify({
|
209 | logger: {
|
210 | stream: logStream,
|
211 | level: 'warn'
|
212 | }
|
213 | })
|
214 | } catch (e) {
|
215 | t.fail()
|
216 | }
|
217 | const stream = require('stream')
|
218 | const http = require('http')
|
219 |
|
220 |
|
221 | logStream.once('data', line => {
|
222 | t.equal(line.msg, 'response terminated with an error with headers already sent')
|
223 | t.equal(line.level, 40)
|
224 | })
|
225 |
|
226 | fastify.get('/', function (request, reply) {
|
227 | t.pass('Received request')
|
228 |
|
229 | var sent = false
|
230 | var reallyLongStream = new stream.Readable({
|
231 | read: function () {
|
232 | if (!sent) {
|
233 | this.push(Buffer.from('hello\n'))
|
234 | }
|
235 | sent = true
|
236 | }
|
237 | })
|
238 | reallyLongStream.destroy = undefined
|
239 | reallyLongStream.close = () => t.ok('called')
|
240 | reply.send(reallyLongStream)
|
241 | })
|
242 |
|
243 | fastify.listen(0, err => {
|
244 | t.error(err)
|
245 | fastify.server.unref()
|
246 |
|
247 | var port = fastify.server.address().port
|
248 |
|
249 | http.get(`http://localhost:${port}`, function (response) {
|
250 | t.strictEqual(response.statusCode, 200)
|
251 | response.on('readable', function () {
|
252 | response.destroy()
|
253 | })
|
254 |
|
255 | response.on('aborted', function () {
|
256 | t.pass('Response closed')
|
257 | })
|
258 | })
|
259 | })
|
260 | })
|
261 |
|
262 | test('Destroying streams prematurely should call abort method', t => {
|
263 | t.plan(7)
|
264 |
|
265 | let fastify = null
|
266 | const logStream = split(JSON.parse)
|
267 | try {
|
268 | fastify = Fastify({
|
269 | logger: {
|
270 | stream: logStream,
|
271 | level: 'warn'
|
272 | }
|
273 | })
|
274 | } catch (e) {
|
275 | t.fail()
|
276 | }
|
277 | const stream = require('stream')
|
278 | const http = require('http')
|
279 |
|
280 |
|
281 | logStream.once('data', line => {
|
282 | t.equal(line.msg, 'response terminated with an error with headers already sent')
|
283 | t.equal(line.level, 40)
|
284 | })
|
285 |
|
286 | fastify.get('/', function (request, reply) {
|
287 | t.pass('Received request')
|
288 |
|
289 | var sent = false
|
290 | var reallyLongStream = new stream.Readable({
|
291 | read: function () {
|
292 | if (!sent) {
|
293 | this.push(Buffer.from('hello\n'))
|
294 | }
|
295 | sent = true
|
296 | }
|
297 | })
|
298 | reallyLongStream.destroy = undefined
|
299 | reallyLongStream.close = undefined
|
300 | reallyLongStream.abort = () => t.ok('called')
|
301 | reply.send(reallyLongStream)
|
302 | })
|
303 |
|
304 | fastify.listen(0, err => {
|
305 | t.error(err)
|
306 | fastify.server.unref()
|
307 |
|
308 | var port = fastify.server.address().port
|
309 |
|
310 | http.get(`http://localhost:${port}`, function (response) {
|
311 | t.strictEqual(response.statusCode, 200)
|
312 | response.on('readable', function () {
|
313 | response.destroy()
|
314 | })
|
315 |
|
316 | response.on('aborted', function () {
|
317 | t.pass('Response closed')
|
318 | })
|
319 | })
|
320 | })
|
321 | })
|
322 |
|
323 | test('should respond with a stream1', t => {
|
324 | t.plan(5)
|
325 | const fastify = Fastify()
|
326 |
|
327 | fastify.get('/', function (req, reply) {
|
328 | const stream = JSONStream.stringify()
|
329 | reply.code(200).type('application/json').send(stream)
|
330 | stream.write({ hello: 'world' })
|
331 | stream.end({ a: 42 })
|
332 | })
|
333 |
|
334 | fastify.listen(0, err => {
|
335 | t.error(err)
|
336 | fastify.server.unref()
|
337 |
|
338 | sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) {
|
339 | t.error(err)
|
340 | t.strictEqual(response.headers['content-type'], 'application/json')
|
341 | t.strictEqual(response.statusCode, 200)
|
342 | t.deepEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }])
|
343 | })
|
344 | })
|
345 | })
|
346 |
|
347 | test('return a 404 if the stream emits a 404 error', t => {
|
348 | t.plan(5)
|
349 |
|
350 | const fastify = Fastify()
|
351 |
|
352 | fastify.get('/', function (request, reply) {
|
353 | t.pass('Received request')
|
354 |
|
355 | var reallyLongStream = new Readable({
|
356 | read: function () {
|
357 | setImmediate(() => {
|
358 | this.emit('error', new errors.NotFound())
|
359 | })
|
360 | }
|
361 | })
|
362 |
|
363 | reply.send(reallyLongStream)
|
364 | })
|
365 |
|
366 | fastify.listen(0, err => {
|
367 | t.error(err)
|
368 | fastify.server.unref()
|
369 |
|
370 | var port = fastify.server.address().port
|
371 |
|
372 | sget(`http://localhost:${port}`, function (err, response) {
|
373 | t.error(err)
|
374 | t.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
|
375 | t.strictEqual(response.statusCode, 404)
|
376 | })
|
377 | })
|
378 | })
|
379 |
|
380 | test('should support send module 200 and 404', t => {
|
381 | t.plan(8)
|
382 | const fastify = Fastify()
|
383 |
|
384 | fastify.get('/', function (req, reply) {
|
385 | const stream = send(req.req, __filename)
|
386 | reply.code(200).send(stream)
|
387 | })
|
388 |
|
389 | fastify.get('/error', function (req, reply) {
|
390 | const stream = send(req.req, 'non-existing-file')
|
391 | reply.code(200).send(stream)
|
392 | })
|
393 |
|
394 | fastify.listen(0, err => {
|
395 | t.error(err)
|
396 | fastify.server.unref()
|
397 |
|
398 | sget(`http://localhost:${fastify.server.address().port}`, function (err, response, data) {
|
399 | t.error(err)
|
400 | t.strictEqual(response.headers['content-type'], 'application/octet-stream')
|
401 | t.strictEqual(response.statusCode, 200)
|
402 |
|
403 | fs.readFile(__filename, (err, expected) => {
|
404 | t.error(err)
|
405 | t.equal(expected.toString(), data.toString())
|
406 | })
|
407 | })
|
408 |
|
409 | sget(`http://localhost:${fastify.server.address().port}/error`, function (err, response) {
|
410 | t.error(err)
|
411 | t.strictEqual(response.statusCode, 404)
|
412 | })
|
413 | })
|
414 | })
|