fastify 3.27.3 → 3.29.0

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.
@@ -13,6 +13,9 @@
13
13
  - [.getHeaders()](#getheaders)
14
14
  - [.removeHeader(key)](#removeheaderkey)
15
15
  - [.hasHeader(key)](#hasheaderkey)
16
+ - [.trailer(key, function)](#trailerkey-function)
17
+ - [.hasTrailer(key)](#hastrailerkey)
18
+ - [.removeTrailer(key)](#removetrailerkey)
16
19
  - [.redirect([code,] dest)](#redirectcode--dest)
17
20
  - [.callNotFound()](#callnotfound)
18
21
  - [.getResponseTime()](#getresponsetime)
@@ -47,6 +50,9 @@ object that exposes the following functions and properties:
47
50
  - `.getHeaders()` - Gets a shallow copy of all current response headers.
48
51
  - `.removeHeader(key)` - Remove the value of a previously set header.
49
52
  - `.hasHeader(name)` - Determine if a header has been set.
53
+ - `.trailer(key, function)` - Sets a response trailer.
54
+ - `.hasTrailer(key)` - Determine if a trailer has been set.
55
+ - `.removeTrailer(key)` - Remove the value of a previously set trailer.
50
56
  - `.type(value)` - Sets the header `Content-Type`.
51
57
  - `.redirect([code,] dest)` - Redirect to the specified url, the status code is
52
58
  optional (default to `302`).
@@ -199,6 +205,49 @@ reply.getHeader('x-foo') // undefined
199
205
 
200
206
  Returns a boolean indicating if the specified header has been set.
201
207
 
208
+ ### .trailer(key, function)
209
+ <a id="trailer"></a>
210
+
211
+ Sets a response trailer. Trailer usually used when you want some header that require heavy resources to be sent after the `data`, for example `Server-Timing`, `Etag`. It can ensure the client get the response data as soon as possible.
212
+
213
+ *Note: The header `Transfer-Encoding: chunked` will be added once you use the trailer. It is a hard requipment for using trailer in Node.js.*
214
+
215
+ *Note: Currently, the computation function only supports synchronous function. That means `async-await` and `promise` are not supported.*
216
+
217
+ ```js
218
+ reply.trailer('server-timing', function() {
219
+ return 'db;dur=53, app;dur=47.2'
220
+ })
221
+
222
+ const { createHash } = require('crypto')
223
+ // trailer function also recieve two argument
224
+ // @param {object} reply fastify reply
225
+ // @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
226
+ reply.trailer('content-md5', function(reply, payload) {
227
+ const hash = createHash('md5')
228
+ hash.update(payload)
229
+ return hash.disgest('hex')
230
+ })
231
+ ```
232
+
233
+ ### .hasTrailer(key)
234
+ <a id="hasTrailer"></a>
235
+
236
+ Returns a boolean indicating if the specified trailer has been set.
237
+
238
+ ### .removeTrailer(key)
239
+ <a id="removeTrailer"></a>
240
+
241
+ Remove the value of a previously set trailer.
242
+ ```js
243
+ reply.trailer('server-timing', function() {
244
+ return 'db;dur=53, app;dur=47.2'
245
+ })
246
+ reply.removeTrailer('server-timing')
247
+ reply.getTrailer('server-timing') // undefined
248
+ ```
249
+
250
+
202
251
  ### .redirect([code ,] dest)
203
252
  <a id="redirect"></a>
204
253
 
package/fastify.d.ts CHANGED
@@ -9,7 +9,7 @@ import { FastifyLoggerInstance, FastifyLoggerOptions } from './types/logger'
9
9
  import { FastifyInstance } from './types/instance'
10
10
  import { FastifyServerFactory } from './types/serverFactory'
11
11
  import { Options as AjvOptions } from '@fastify/ajv-compiler'
12
- import { FastifyError } from 'fastify-error'
12
+ import { FastifyError } from '@fastify/error'
13
13
  import { FastifyReply } from './types/reply'
14
14
  import { FastifySchemaValidationError } from './types/schema'
15
15
  import { ConstructorAction, ProtoAction } from "./types/content-type-parser";
@@ -163,7 +163,7 @@ export type FastifyServerOptions<
163
163
 
164
164
  type TrustProxyFunction = (address: string, hop: number) => boolean
165
165
 
166
- declare module 'fastify-error' {
166
+ declare module '@fastify/error' {
167
167
  interface FastifyError {
168
168
  validation?: ValidationResult[];
169
169
  }
@@ -188,7 +188,7 @@ export { FastifyContext, FastifyContextConfig } from './types/context'
188
188
  export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
189
189
  export * from './types/register'
190
190
  export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser'
191
- export { FastifyError } from 'fastify-error'
191
+ export { FastifyError } from '@fastify/error'
192
192
  export { FastifySchema, FastifySchemaCompiler } from './types/schema'
193
193
  export { HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault } from './types/utils'
194
194
  export * from './types/hooks'
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '3.27.3'
3
+ const VERSION = '3.29.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
package/lib/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const createError = require('fastify-error')
3
+ const createError = require('@fastify/error')
4
4
  const codes = {
5
5
  /**
6
6
  * Basic
@@ -147,6 +147,14 @@ const codes = {
147
147
  'FST_ERR_BAD_STATUS_CODE',
148
148
  'Called reply with an invalid status code: %s'
149
149
  ),
150
+ FST_ERR_BAD_TRAILER_NAME: createError(
151
+ 'FST_ERR_BAD_TRAILER_NAME',
152
+ 'Called reply.trailer with an invalid header name: %s'
153
+ ),
154
+ FST_ERR_BAD_TRAILER_VALUE: createError(
155
+ 'FST_ERR_BAD_TRAILER_VALUE',
156
+ "Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function."
157
+ ),
150
158
 
151
159
  /**
152
160
  * schemas
package/lib/fourOhFour.js CHANGED
@@ -37,7 +37,8 @@ function fourOhFour (options) {
37
37
  const { logger, genReqId } = options
38
38
 
39
39
  // 404 router, used for handling encapsulated 404 handlers
40
- const router = FindMyWay({ defaultRoute: fourOhFourFallBack })
40
+ const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })
41
+ let _onBadUrlHandler = null
41
42
 
42
43
  return { router, setNotFoundHandler, setContext, arrange404 }
43
44
 
@@ -45,6 +46,8 @@ function fourOhFour (options) {
45
46
  // Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
46
47
  instance[kFourOhFourLevelInstance] = instance
47
48
  instance[kCanSetNotFoundHandler] = true
49
+ // we need to bind instance for the context
50
+ router.onBadUrl = router.onBadUrl.bind(instance)
48
51
  }
49
52
 
50
53
  function basic404 (request, reply) {
@@ -58,6 +61,18 @@ function fourOhFour (options) {
58
61
  })
59
62
  }
60
63
 
64
+ function createOnBadUrl () {
65
+ return function onBadUrl (path, req, res) {
66
+ const id = genReqId(req)
67
+ const childLogger = logger.child({ reqId: id })
68
+ const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
69
+ const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
70
+ const reply = new Reply(res, request, childLogger)
71
+
72
+ _onBadUrlHandler(request, reply)
73
+ }
74
+ }
75
+
61
76
  function setContext (instance, context) {
62
77
  const _404Context = Object.assign({}, instance[kFourOhFourContext])
63
78
  _404Context.onSend = context.onSend
@@ -107,8 +122,12 @@ function fourOhFour (options) {
107
122
  if (handler) {
108
123
  this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false
109
124
  handler = handler.bind(this)
125
+ // update onBadUrl handler
126
+ _onBadUrlHandler = handler
110
127
  } else {
111
128
  handler = basic404
129
+ // update onBadUrl handler
130
+ _onBadUrlHandler = basic404
112
131
  }
113
132
 
114
133
  this.after((notHandledErr, done) => {
package/lib/reply.js CHANGED
@@ -16,6 +16,7 @@ const {
16
16
  kReplySerializerDefault,
17
17
  kReplyIsError,
18
18
  kReplyHeaders,
19
+ kReplyTrailers,
19
20
  kReplyHasStatusCode,
20
21
  kReplyIsRunningOnErrorHook,
21
22
  kDisableRequestLogging
@@ -47,7 +48,9 @@ const {
47
48
  FST_ERR_REP_ALREADY_SENT,
48
49
  FST_ERR_REP_SENT_VALUE,
49
50
  FST_ERR_SEND_INSIDE_ONERR,
50
- FST_ERR_BAD_STATUS_CODE
51
+ FST_ERR_BAD_STATUS_CODE,
52
+ FST_ERR_BAD_TRAILER_NAME,
53
+ FST_ERR_BAD_TRAILER_VALUE
51
54
  } = require('./errors')
52
55
  const warning = require('./warnings')
53
56
 
@@ -60,6 +63,7 @@ function Reply (res, request, log) {
60
63
  this[kReplyIsRunningOnErrorHook] = false
61
64
  this.request = request
62
65
  this[kReplyHeaders] = {}
66
+ this[kReplyTrailers] = null
63
67
  this[kReplyHasStatusCode] = false
64
68
  this[kReplyStartTime] = undefined
65
69
  this.log = log
@@ -261,6 +265,47 @@ Reply.prototype.headers = function (headers) {
261
265
  return this
262
266
  }
263
267
 
268
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer#directives
269
+ // https://httpwg.org/specs/rfc7230.html#chunked.trailer.part
270
+ const INVALID_TRAILERS = new Set([
271
+ 'transfer-encoding',
272
+ 'content-length',
273
+ 'host',
274
+ 'cache-control',
275
+ 'max-forwards',
276
+ 'te',
277
+ 'authorization',
278
+ 'set-cookie',
279
+ 'content-encoding',
280
+ 'content-type',
281
+ 'content-range',
282
+ 'trailer'
283
+ ])
284
+
285
+ Reply.prototype.trailer = function (key, fn) {
286
+ key = key.toLowerCase()
287
+ if (INVALID_TRAILERS.has(key)) {
288
+ throw new FST_ERR_BAD_TRAILER_NAME(key)
289
+ }
290
+ if (typeof fn !== 'function') {
291
+ throw new FST_ERR_BAD_TRAILER_VALUE(key, typeof fn)
292
+ }
293
+ if (this[kReplyTrailers] === null) this[kReplyTrailers] = {}
294
+ this[kReplyTrailers][key] = fn
295
+ return this
296
+ }
297
+
298
+ Reply.prototype.hasTrailer = function (key) {
299
+ if (this[kReplyTrailers] === null) return false
300
+ return this[kReplyTrailers][key.toLowerCase()] !== undefined
301
+ }
302
+
303
+ Reply.prototype.removeTrailer = function (key) {
304
+ if (this[kReplyTrailers] === null) return this
305
+ this[kReplyTrailers][key.toLowerCase()] = undefined
306
+ return this
307
+ }
308
+
264
309
  Reply.prototype.code = function (code) {
265
310
  const intValue = parseInt(code)
266
311
  if (isNaN(intValue) || intValue < 100 || intValue > 600) {
@@ -416,18 +461,35 @@ function onSendEnd (reply, payload) {
416
461
  const req = reply.request
417
462
  const statusCode = res.statusCode
418
463
 
464
+ // we check if we need to update the trailers header and set it
465
+ if (reply[kReplyTrailers] !== null) {
466
+ const trailerHeaders = Object.keys(reply[kReplyTrailers])
467
+ let header = ''
468
+ for (const trailerName of trailerHeaders) {
469
+ if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
470
+ header += ' '
471
+ header += trailerName
472
+ }
473
+ // it must be chunked for trailer to work
474
+ reply.header('Transfer-Encoding', 'chunked')
475
+ reply.header('Trailer', header.trim())
476
+ }
477
+
419
478
  if (payload === undefined || payload === null) {
420
479
  reply[kReplySent] = true
421
480
 
422
481
  // according to https://tools.ietf.org/html/rfc7230#section-3.3.2
423
482
  // we cannot send a content-length for 304 and 204, and all status code
424
- // < 200.
483
+ // < 200
484
+ // A sender MUST NOT send a Content-Length header field in any message
485
+ // that contains a Transfer-Encoding header field.
425
486
  // For HEAD we don't overwrite the `content-length`
426
- if (statusCode >= 200 && statusCode !== 204 && statusCode !== 304 && req.method !== 'HEAD') {
487
+ if (statusCode >= 200 && statusCode !== 204 && statusCode !== 304 && req.method !== 'HEAD' && reply[kReplyTrailers] === null) {
427
488
  reply[kReplyHeaders]['content-length'] = '0'
428
489
  }
429
490
 
430
491
  res.writeHead(statusCode, reply[kReplyHeaders])
492
+ sendTrailer(payload, res, reply)
431
493
  // avoid ArgumentsAdaptorTrampoline from V8
432
494
  res.end(null, null, null)
433
495
  return
@@ -444,18 +506,23 @@ function onSendEnd (reply, payload) {
444
506
  throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
445
507
  }
446
508
 
447
- if (!reply[kReplyHeaders]['content-length']) {
448
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
449
- } else if (req.raw.method !== 'HEAD' && reply[kReplyHeaders]['content-length'] !== Buffer.byteLength(payload)) {
450
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
509
+ if (reply[kReplyTrailers] === null) {
510
+ if (!reply[kReplyHeaders]['content-length']) {
511
+ reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
512
+ } else if (req.raw.method !== 'HEAD' && reply[kReplyHeaders]['content-length'] !== Buffer.byteLength(payload)) {
513
+ reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
514
+ }
451
515
  }
452
516
 
453
517
  reply[kReplySent] = true
454
518
 
455
519
  res.writeHead(statusCode, reply[kReplyHeaders])
456
-
520
+ // write payload first
521
+ res.write(payload)
522
+ // then send trailers
523
+ sendTrailer(payload, res, reply)
457
524
  // avoid ArgumentsAdaptorTrampoline from V8
458
- res.end(payload, null, null)
525
+ res.end(null, null, null)
459
526
  }
460
527
 
461
528
  function logStreamError (logger, err, res) {
@@ -472,6 +539,9 @@ function sendStream (payload, res, reply) {
472
539
  let sourceOpen = true
473
540
  let errorLogged = false
474
541
 
542
+ // set trailer when stream ended
543
+ sendStreamTrailer(payload, res, reply)
544
+
475
545
  eos(payload, { readable: true, writable: false }, function (err) {
476
546
  sourceOpen = false
477
547
  if (err != null) {
@@ -520,6 +590,22 @@ function sendStream (payload, res, reply) {
520
590
  payload.pipe(res)
521
591
  }
522
592
 
593
+ function sendTrailer (payload, res, reply) {
594
+ if (reply[kReplyTrailers] === null) return
595
+ const trailerHeaders = Object.keys(reply[kReplyTrailers])
596
+ const trailers = {}
597
+ for (const trailerName of trailerHeaders) {
598
+ if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
599
+ trailers[trailerName] = reply[kReplyTrailers][trailerName](reply, payload)
600
+ }
601
+ res.addTrailers(trailers)
602
+ }
603
+
604
+ function sendStreamTrailer (payload, res, reply) {
605
+ if (reply[kReplyTrailers] === null) return
606
+ payload.on('end', () => sendTrailer(null, res, reply))
607
+ }
608
+
523
609
  function onErrorHook (reply, error, cb) {
524
610
  reply[kReplySent] = true
525
611
  if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
@@ -684,6 +770,7 @@ function buildReply (R) {
684
770
  this[kReplySerializer] = null
685
771
  this.request = request
686
772
  this[kReplyHeaders] = {}
773
+ this[kReplyTrailers] = null
687
774
  this[kReplyStartTime] = undefined
688
775
  this[kReplyEndTime] = undefined
689
776
  this.log = log
package/lib/symbols.js CHANGED
@@ -30,6 +30,7 @@ const keys = {
30
30
  kReplySerializer: Symbol('fastify.reply.serializer'),
31
31
  kReplyIsError: Symbol('fastify.reply.isError'),
32
32
  kReplyHeaders: Symbol('fastify.reply.headers'),
33
+ kReplyTrailers: Symbol('fastify.reply.trailers'),
33
34
  kReplyHasStatusCode: Symbol('fastify.reply.hasStatusCode'),
34
35
  kReplySent: Symbol('fastify.reply.sent'),
35
36
  kReplySentOverwritten: Symbol('fastify.reply.sentOverwritten'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.27.3",
3
+ "version": "3.29.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -127,9 +127,8 @@
127
127
  "@sinonjs/fake-timers": "^9.1.0",
128
128
  "@types/node": "^16.0.0",
129
129
  "@types/pino": "^6.0.1",
130
- "@typescript-eslint/eslint-plugin": "^5.0.0",
131
- "@typescript-eslint/parser": "^5.0.0",
132
- "JSONStream": "^1.3.5",
130
+ "@typescript-eslint/eslint-plugin": "^5.21.0",
131
+ "@typescript-eslint/parser": "^5.21.0",
133
132
  "ajv": "^6.0.0",
134
133
  "ajv-errors": "^1.0.1",
135
134
  "ajv-formats": "^2.1.1",
@@ -140,11 +139,11 @@
140
139
  "cors": "^2.8.5",
141
140
  "coveralls": "^3.1.0",
142
141
  "dns-prefetch-control": "^0.3.0",
143
- "eslint": "^8.0.1",
142
+ "eslint": "^8.14.0",
144
143
  "eslint-config-standard": "^17.0.0-1",
145
- "eslint-import-resolver-node": "^0.3.2",
146
- "eslint-plugin-import": "^2.25.4",
147
- "eslint-plugin-n": "^14.0.0",
144
+ "eslint-import-resolver-node": "^0.3.6",
145
+ "eslint-plugin-import": "^2.26.0",
146
+ "eslint-plugin-n": "^15.2.0",
148
147
  "eslint-plugin-promise": "^6.0.0",
149
148
  "fast-json-body": "^1.1.0",
150
149
  "fastify-plugin": "^3.0.0",
@@ -157,6 +156,7 @@
157
156
  "hsts": "^2.2.0",
158
157
  "http-errors": "^2.0.0",
159
158
  "ienoopen": "^1.1.0",
159
+ "JSONStream": "^1.3.5",
160
160
  "license-checker": "^25.0.1",
161
161
  "pem": "^1.14.4",
162
162
  "proxyquire": "^2.1.3",
@@ -166,7 +166,7 @@
166
166
  "simple-get": "^4.0.0",
167
167
  "snazzy": "^9.0.0",
168
168
  "split2": "^4.1.0",
169
- "standard": "^17.0.0-2",
169
+ "standard": "^17.0.0",
170
170
  "tap": "^15.1.1",
171
171
  "tap-mocha-reporter": "^5.0.1",
172
172
  "then-sleep": "^1.0.1",
@@ -178,15 +178,15 @@
178
178
  },
179
179
  "dependencies": {
180
180
  "@fastify/ajv-compiler": "^1.0.0",
181
+ "@fastify/error": "^2.0.0",
181
182
  "abstract-logging": "^2.0.0",
182
183
  "avvio": "^7.1.2",
183
184
  "fast-json-stringify": "^2.5.2",
184
- "fastify-error": "^0.3.0",
185
- "process-warning": "^1.0.0",
186
185
  "find-my-way": "^4.5.0",
187
186
  "flatstr": "^1.0.12",
188
187
  "light-my-request": "^4.2.0",
189
188
  "pino": "^6.13.0",
189
+ "process-warning": "^1.0.0",
190
190
  "proxy-addr": "^2.0.7",
191
191
  "rfdc": "^1.1.4",
192
192
  "secure-json-parse": "^2.0.0",
package/test/404s.test.js CHANGED
@@ -1738,6 +1738,58 @@ test('400 in case of bad url (pre find-my-way v2.2.0 was a 404)', t => {
1738
1738
  })
1739
1739
  })
1740
1740
 
1741
+ t.test('No route registered', t => {
1742
+ t.plan(3)
1743
+ const fastify = Fastify()
1744
+ fastify.inject({
1745
+ url: '/%c0',
1746
+ method: 'GET'
1747
+ }, (err, response) => {
1748
+ t.error(err)
1749
+ t.equal(response.statusCode, 404)
1750
+ t.same(JSON.parse(response.payload), {
1751
+ error: 'Not Found',
1752
+ message: 'Route GET:/%c0 not found',
1753
+ statusCode: 404
1754
+ })
1755
+ })
1756
+ })
1757
+
1758
+ t.test('Only / is registered', t => {
1759
+ t.plan(3)
1760
+ const fastify = Fastify({ logger: true })
1761
+ fastify.get('/', () => t.fail('we should not be here'))
1762
+ fastify.inject({
1763
+ url: '/%c0',
1764
+ method: 'GET'
1765
+ }, (err, response) => {
1766
+ t.error(err)
1767
+ t.equal(response.statusCode, 404)
1768
+ t.same(JSON.parse(response.payload), {
1769
+ error: 'Not Found',
1770
+ message: 'Route GET:/%c0 not found',
1771
+ statusCode: 404
1772
+ })
1773
+ })
1774
+ })
1775
+
1776
+ t.test('customized 404', t => {
1777
+ t.plan(3)
1778
+ const fastify = Fastify({ logger: true })
1779
+ fastify.get('/', () => t.fail('we should not be here'))
1780
+ fastify.setNotFoundHandler(function (req, reply) {
1781
+ reply.code(404).send('this was not found')
1782
+ })
1783
+ fastify.inject({
1784
+ url: '/%c0',
1785
+ method: 'GET'
1786
+ }, (err, response) => {
1787
+ t.error(err)
1788
+ t.equal(response.statusCode, 404)
1789
+ t.same(response.payload, 'this was not found')
1790
+ })
1791
+ })
1792
+
1741
1793
  t.end()
1742
1794
  })
1743
1795
 
@@ -5,9 +5,8 @@ const test = t.test
5
5
  const sget = require('simple-get').concat
6
6
  const http = require('http')
7
7
  const NotFound = require('http-errors').NotFound
8
- const EventEmitter = require('events').EventEmitter
9
8
  const Reply = require('../../lib/reply')
10
- const { Writable } = require('stream')
9
+ const { Readable, Writable } = require('stream')
11
10
  const {
12
11
  kReplyErrorHandlerCalled,
13
12
  kReplyHeaders,
@@ -40,30 +39,31 @@ test('Once called, Reply should return an object with methods', t => {
40
39
  test('reply.send will logStream error and destroy the stream', { only: true }, t => {
41
40
  t.plan(1)
42
41
  let destroyCalled
43
- const payload = new EventEmitter()
42
+ const payload = new Readable({
43
+ read () {},
44
+ destroy (err, cb) {
45
+ destroyCalled = true
46
+ cb(err)
47
+ }
48
+ })
44
49
 
45
- const response = {
50
+ const response = new Writable()
51
+ Object.assign(response, {
46
52
  setHeader: () => {},
47
53
  hasHeader: () => false,
48
54
  getHeader: () => undefined,
49
55
  writeHead: () => {},
50
- end: () => {},
51
- headersSent: true,
52
- destroy: () => (destroyCalled = true),
53
- on: () => {}
54
- }
56
+ write: () => {},
57
+ headersSent: true
58
+ })
55
59
 
56
60
  const log = {
57
61
  warn: () => {}
58
62
  }
59
63
 
60
- Object.assign(payload, {
61
- pipe: () => {},
62
- destroy: () => {}
63
- })
64
64
  const reply = new Reply(response, { context: { onSend: null } }, log)
65
65
  reply.send(payload)
66
- payload.emit('error', new Error('stream error'))
66
+ payload.destroy(new Error('stream error'))
67
67
 
68
68
  t.equal(destroyCalled, true, 'Error not logged and not streamed')
69
69
  })
@@ -75,6 +75,7 @@ test('reply.send throw with circular JSON', t => {
75
75
  hasHeader: () => false,
76
76
  getHeader: () => undefined,
77
77
  writeHead: () => {},
78
+ write: () => {},
78
79
  end: () => {}
79
80
  }
80
81
  const reply = new Reply(response, { context: { onSend: [] } })
@@ -92,6 +93,7 @@ test('reply.send returns itself', t => {
92
93
  hasHeader: () => false,
93
94
  getHeader: () => undefined,
94
95
  writeHead: () => {},
96
+ write: () => {},
95
97
  end: () => {}
96
98
  }
97
99
  const reply = new Reply(response, { context: { onSend: [] } })
@@ -1481,6 +1481,26 @@ test('should not log incoming request and outgoing response when disabled', t =>
1481
1481
  })
1482
1482
  })
1483
1483
 
1484
+ test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', t => {
1485
+ t.plan(1)
1486
+ const lines = []
1487
+ const dest = new stream.Writable({
1488
+ write: function (chunk, enc, cb) {
1489
+ lines.push(JSON.parse(chunk))
1490
+ cb()
1491
+ }
1492
+ })
1493
+ const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream: dest } })
1494
+
1495
+ fastify.inject({
1496
+ url: '/%c0',
1497
+ method: 'GET'
1498
+ }, (e, res) => {
1499
+ // it will log 1 line only because of basic404
1500
+ t.same(lines.length, 1)
1501
+ })
1502
+ })
1503
+
1484
1504
  test('should pass when using unWritable props in the logger option', t => {
1485
1505
  t.plan(1)
1486
1506
  Fastify({