@fastify/reply-from 9.8.0 → 10.0.0-pre.fv5.2

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.
@@ -17,7 +17,7 @@ on:
17
17
 
18
18
  jobs:
19
19
  test:
20
- uses: fastify/workflows/.github/workflows/plugins-ci.yml@v3
20
+ uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5.0.0
21
21
  with:
22
22
  license-check: true
23
23
  lint: true
package/.taprc CHANGED
@@ -1,11 +1,3 @@
1
- ts: false
2
- jsx: false
3
- flow: false
4
-
5
- functions: 97
6
- lines: 96
7
- statements: 96
8
- branches: 96
9
-
1
+ disable-coverage: true
10
2
  files:
11
3
  - test/**/*.test.js
package/README.md CHANGED
@@ -344,10 +344,11 @@ the request or response being sent or received to/from the source.
344
344
 
345
345
  **Note: If `base` is specified in plugin options, the `source` here should not override the host/origin.**
346
346
 
347
- #### `onResponse(request, reply, res)`
347
+ #### `onResponse(request, reply, response)`
348
348
 
349
- Called when a HTTP response is received from the source.
350
- The default behavior is `reply.send(res)`, which will be disabled if the
349
+ Called when a HTTP response is received from the source. Passed the original source `request`, the in-progress reply to the source as `reply`, and the ongoing `response` from the upstream server.
350
+
351
+ The default behavior is `reply.send(response.stream)`, which will be disabled if the
351
352
  option is specified.
352
353
 
353
354
  When replying with a body of a different length it is necessary to remove
@@ -362,6 +363,8 @@ the `content-length` header.
362
363
  }
363
364
  ```
364
365
 
366
+ **Note**: `onResponse` is called after headers have already been sent. If you want to modify response headers, use the `rewriteHeaders` hook.
367
+
365
368
  #### `onError(reply, error)`
366
369
 
367
370
  Called when a HTTP response is received with error from the source.
@@ -485,8 +488,8 @@ This library has:
485
488
 
486
489
  - `timeout` for `http` set by default. The default value is 10 seconds (`10000`).
487
490
  - `requestTimeout` & `sessionTimeout` for `http2` set by default.
488
- - The default value for `requestTimeout` is 10 seconds (`10000`).
489
- - The default value for `sessionTimeout` is 60 seconds (`60000`).
491
+ - The default value for `requestTimeout` is 10 seconds (`10000`), a value of 0 disables the timeout.
492
+ - The default value for `sessionTimeout` is 60 seconds (`60000`), a value of 0 disables the timeout.
490
493
 
491
494
  When a timeout happens, [`504 Gateway Timeout`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504)
492
495
  will be returned to the client.
package/index.js CHANGED
@@ -38,6 +38,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
38
38
  http: opts.http,
39
39
  http2: opts.http2,
40
40
  base,
41
+ sessionTimeout: opts.sessionTimeout,
41
42
  undici: opts.undici,
42
43
  globalAgent: opts.globalAgent,
43
44
  destroyAgent: opts.destroyAgent
@@ -200,7 +201,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
200
201
  }
201
202
  this.code(res.statusCode)
202
203
  if (onResponse) {
203
- onResponse(this.request, this, res.stream)
204
+ onResponse(this.request, this, res)
204
205
  } else {
205
206
  this.send(res.stream)
206
207
  }
@@ -223,7 +224,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
223
224
  })
224
225
  next()
225
226
  }, {
226
- fastify: '4.x',
227
+ fastify: '5.x',
227
228
  name: '@fastify/reply-from'
228
229
  })
229
230
 
@@ -266,9 +267,7 @@ function onErrorDefault (reply, { error }) {
266
267
  }
267
268
 
268
269
  function isFastifyMultipartRegistered (fastify) {
269
- // TODO: remove fastify.hasContentTypeParser('multipart') in next major
270
- // It is used to be compatible with @fastify/multipart@<=7.3.0
271
- return (fastify.hasContentTypeParser('multipart') || fastify.hasContentTypeParser('multipart/form-data')) && fastify.hasRequestDecorator('multipart')
270
+ return fastify.hasContentTypeParser('multipart/form-data')
272
271
  }
273
272
 
274
273
  function createRequestRetry (requestImpl, reply, retryHandler) {
package/lib/request.js CHANGED
@@ -272,10 +272,10 @@ function getHttp2Opts (opts) {
272
272
  }
273
273
  http2Opts.sessionOptions = http2Opts.sessionOptions || {}
274
274
 
275
- if (!http2Opts.sessionTimeout) {
275
+ if (http2Opts.sessionTimeout === undefined) {
276
276
  http2Opts.sessionTimeout = opts.sessionTimeout || 60000
277
277
  }
278
- if (!http2Opts.requestTimeout) {
278
+ if (http2Opts.requestTimeout === undefined) {
279
279
  http2Opts.requestTimeout = 10000
280
280
  }
281
281
  http2Opts.sessionOptions.rejectUnauthorized = http2Opts.sessionOptions.rejectUnauthorized || false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/reply-from",
3
- "version": "9.8.0",
3
+ "version": "10.0.0-pre.fv5.2",
4
4
  "description": "forward your HTTP request to another server, for fastify",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -29,36 +29,36 @@
29
29
  },
30
30
  "homepage": "https://github.com/fastify/fastify-reply-from#readme",
31
31
  "devDependencies": {
32
- "@fastify/formbody": "^7.4.0",
33
- "@fastify/multipart": "^7.4.0",
34
- "@fastify/pre-commit": "^2.0.2",
35
- "@sinonjs/fake-timers": "^11.0.0",
36
- "@types/node": "^20.1.4",
37
- "@types/tap": "^15.0.7",
38
- "fastify": "^4.0.2",
32
+ "@fastify/formbody": "^8.0.0-pre.fv5.1",
33
+ "@fastify/multipart": "^9.0.0-pre.fv5.1",
34
+ "@fastify/pre-commit": "^2.1.0",
35
+ "@sinonjs/fake-timers": "^11.2.2",
36
+ "@types/node": "^22.0.0",
37
+ "@types/tap": "^15.0.11",
38
+ "fastify": "^5.0.0-alpha.3",
39
39
  "form-data": "^4.0.0",
40
- "got": "^11.8.2",
40
+ "got": "^11.8.6",
41
41
  "h2url": "^0.2.0",
42
- "msgpack5": "^6.0.1",
43
- "nock": "^13.2.6",
42
+ "msgpack5": "^6.0.2",
43
+ "nock": "^13.5.4",
44
44
  "proxy": "^2.1.1",
45
45
  "proxyquire": "^2.1.3",
46
- "semver": "^7.5.1",
46
+ "semver": "^7.6.0",
47
47
  "simple-get": "^4.0.1",
48
48
  "snazzy": "^9.0.0",
49
- "split2": "^4.1.0",
50
- "standard": "^17.0.0",
51
- "tap": "^16.2.0",
49
+ "split2": "^4.2.0",
50
+ "standard": "^17.1.0",
51
+ "tap": "^18.7.2",
52
52
  "tsd": "^0.31.0"
53
53
  },
54
54
  "dependencies": {
55
- "@fastify/error": "^3.0.0",
55
+ "@fastify/error": "^4.0.0",
56
56
  "end-of-stream": "^1.4.4",
57
- "fast-content-type-parse": "^1.1.0",
58
- "fast-querystring": "^1.0.0",
59
- "fastify-plugin": "^4.0.0",
57
+ "fast-content-type-parse": "^2.0.0",
58
+ "fast-querystring": "^1.1.2",
59
+ "fastify-plugin": "^4.5.1",
60
60
  "toad-cache": "^3.7.0",
61
- "undici": "^5.19.1"
61
+ "undici": "^6.11.1"
62
62
  },
63
63
  "pre-commit": [
64
64
  "lint",
@@ -47,4 +47,6 @@ test('http -> http2', async function (t) {
47
47
  t.equal(headers['x-my-header'], 'hello!')
48
48
  t.match(headers['content-type'], /application\/json/)
49
49
  t.same(body, { hello: 'world' })
50
+ instance.close()
51
+ target.close()
50
52
  })
@@ -46,4 +46,6 @@ test('http -> http2', async function (t) {
46
46
  t.equal(headers['x-my-header'], 'hello!')
47
47
  t.match(headers['content-type'], /application\/json/)
48
48
  t.same(body, { hello: 'world' })
49
+ instance.close()
50
+ target.close()
49
51
  })
@@ -51,6 +51,8 @@ target.listen({ port: 0 }, (err) => {
51
51
  t.equal(res.headers['x-my-header'], 'hello!')
52
52
  t.equal(res.statusCode, 205)
53
53
  t.equal(data.toString(), 'hello world')
54
+ instance.close()
55
+ target.close()
54
56
  })
55
57
  })
56
58
  })
@@ -43,6 +43,8 @@ test('http -> http2', async (t) => {
43
43
  t.equal(err.response.headers['x-my-header'], 'hello!')
44
44
  t.match(err.response.headers['content-type'], /application\/json/)
45
45
  t.same(JSON.parse(err.response.body), { hello: 'world' })
46
+ instance.close()
47
+ target.close()
46
48
  return
47
49
  }
48
50
 
@@ -9,8 +9,6 @@ const FakeTimers = require('@sinonjs/fake-timers')
9
9
  const clock = FakeTimers.createClock()
10
10
 
11
11
  test('http request timeout', async (t) => {
12
- t.autoend(false)
13
-
14
12
  const target = Fastify()
15
13
  t.teardown(target.close.bind(target))
16
14
 
@@ -54,4 +54,6 @@ t.test('http2 -> http2', async (t) => {
54
54
  t.equal(headers['x-my-header'], 'hello!')
55
55
  t.match(headers['content-type'], /application\/json/)
56
56
  t.same(JSON.parse(body), { hello: 'world' })
57
+ instance.close()
58
+ target.close()
57
59
  })
@@ -7,7 +7,7 @@ const From = require('../index')
7
7
  test('http2 invalid base', async (t) => {
8
8
  const instance = Fastify()
9
9
 
10
- await t.rejects(instance.register(From, {
10
+ await t.rejects(async () => instance.register(From, {
11
11
  http2: { requestTimeout: 100 }
12
12
  }), new Error('Option base is required when http2 is true'))
13
13
  })
@@ -0,0 +1,85 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const Fastify = require('fastify')
5
+ const From = require('..')
6
+ const got = require('got')
7
+
8
+ test('http2 request timeout disabled', async (t) => {
9
+ const target = Fastify({ http2: true })
10
+ t.teardown(target.close.bind(target))
11
+
12
+ target.get('/', () => {
13
+ t.pass('request arrives')
14
+ })
15
+
16
+ await target.listen({ port: 0 })
17
+
18
+ const instance = Fastify()
19
+ t.teardown(instance.close.bind(instance))
20
+
21
+ instance.register(From, {
22
+ base: `http://localhost:${target.server.address().port}`,
23
+ http2: { requestTimeout: 0, sessionTimeout: 16000 }
24
+ })
25
+
26
+ instance.get('/', (request, reply) => {
27
+ reply.from(`http://localhost:${target.server.address().port}/`)
28
+ })
29
+
30
+ await instance.listen({ port: 0 })
31
+
32
+ const result = await Promise.race([
33
+ got.get(`http://localhost:${instance.server.address().port}/`, {
34
+ retry: 0
35
+ }),
36
+ new Promise(resolve => setTimeout(resolve, 11000, 'passed'))
37
+ ])
38
+
39
+ // if we wait 11000 ms without a timeout error, we assume disabling the timeout worked
40
+ // 10000 ms is the default timeout
41
+ t.equal(result, 'passed')
42
+ })
43
+
44
+ test('http2 session timeout disabled', async (t) => {
45
+ const target = Fastify({ http2: true })
46
+
47
+ target.get('/', () => {
48
+ t.pass('request arrives')
49
+ })
50
+
51
+ await target.listen({ port: 0 })
52
+
53
+ const instance = Fastify()
54
+
55
+ instance.register(From, {
56
+ sessionTimeout: 3000,
57
+ destroyAgent: true,
58
+ base: `http://localhost:${target.server.address().port}`,
59
+ http2: { requestTimeout: 0, sessionTimeout: 0 }
60
+ })
61
+
62
+ instance.get('/', (request, reply) => {
63
+ reply.from(`http://localhost:${target.server.address().port}/`)
64
+ })
65
+
66
+ await instance.listen({ port: 0 })
67
+
68
+ const request = got.get(`http://localhost:${instance.server.address().port}/`, {
69
+ retry: 0
70
+ })
71
+
72
+ const result = await Promise.race([
73
+ request,
74
+ new Promise(resolve => setTimeout(resolve, 4000, 'passed'))
75
+ ])
76
+
77
+ // clean up right after the timeout, otherwise test will hang
78
+ request.cancel()
79
+ target.close()
80
+ instance.close()
81
+
82
+ // if we wait 4000 ms without a timeout error, we assume disabling the session timeout for reply-from worked
83
+ // because we pass 3000 ms as session timeout to the Fastify options itself
84
+ t.equal(result, 'passed')
85
+ })
@@ -122,4 +122,6 @@ test('http2 sse removes request and session timeout test', async (t) => {
122
122
 
123
123
  const { statusCode } = await got.get(`http://localhost:${instance.server.address().port}/`, { retry: 0 })
124
124
  t.equal(statusCode, 200)
125
+ instance.close()
126
+ target.close()
125
127
  })
@@ -9,7 +9,7 @@ test('throw an error if http2 is used with a Unix socket destination', async t =
9
9
 
10
10
  const instance = Fastify()
11
11
 
12
- await t.rejects(instance.register(From, {
12
+ await t.rejects(async () => instance.register(From, {
13
13
  base: 'unix+http://localhost:1337',
14
14
  http2: { requestTimeout: 100 }
15
15
  }), new Error('Unix socket destination is not supported when http2 is true'))
@@ -8,21 +8,19 @@ const FakeTimers = require('@sinonjs/fake-timers')
8
8
 
9
9
  const clock = FakeTimers.createClock()
10
10
 
11
- t.autoend(false)
11
+ t.test('on-error', async (t) => {
12
+ const target = Fastify()
13
+ t.teardown(target.close.bind(target))
12
14
 
13
- const target = Fastify()
14
- t.teardown(target.close.bind(target))
15
+ target.get('/', (request, reply) => {
16
+ t.pass('request arrives')
15
17
 
16
- target.get('/', (request, reply) => {
17
- t.pass('request arrives')
18
-
19
- clock.setTimeout(() => {
20
- reply.status(200).send('hello world')
21
- t.end()
22
- }, 1000)
23
- })
18
+ clock.setTimeout(() => {
19
+ reply.status(200).send('hello world')
20
+ t.end()
21
+ }, 1000)
22
+ })
24
23
 
25
- async function main () {
26
24
  await target.listen({ port: 0 })
27
25
 
28
26
  const instance = Fastify()
@@ -63,6 +61,4 @@ async function main () {
63
61
  }
64
62
 
65
63
  t.fail()
66
- }
67
-
68
- main()
64
+ })
@@ -9,7 +9,7 @@ const get = require('simple-get').concat
9
9
  const instance = Fastify()
10
10
  instance.register(From)
11
11
 
12
- t.plan(8)
12
+ t.plan(9)
13
13
  t.teardown(instance.close.bind(instance))
14
14
 
15
15
  const target = http.createServer((req, res) => {
@@ -22,8 +22,9 @@ const target = http.createServer((req, res) => {
22
22
  instance.get('/', (request1, reply) => {
23
23
  reply.from(`http://localhost:${target.address().port}`, {
24
24
  onResponse: (request2, reply, res) => {
25
+ t.equal(res.statusCode, 200)
25
26
  t.equal(request1.raw, request2.raw)
26
- reply.send(res)
27
+ reply.send(res.stream)
27
28
  }
28
29
  })
29
30
  })
@@ -26,7 +26,7 @@ instance.get('/', (request, reply) => {
26
26
  reply.from(`http://localhost:${target.address().port}`, {
27
27
  onResponse: (request, reply, res) => {
28
28
  reply.send(
29
- res.pipe(
29
+ res.stream.pipe(
30
30
  new Transform({
31
31
  transform: function (chunk, enc, cb) {
32
32
  this.push(chunk.toString().toUpperCase())
@@ -31,7 +31,7 @@ target.listen({ port: 0 }, err => {
31
31
  const From = proxyquire('..', {
32
32
  './lib/request.js': proxyquire('../lib/request.js', {
33
33
  undici: proxyquire('undici', {
34
- './lib/agent': proxyquire('undici/lib/agent.js', {
34
+ './lib/dispatcher/agent': proxyquire('undici/lib/dispatcher/agent.js', {
35
35
  './pool': class Pool extends undici.Pool {
36
36
  constructor (url, options) {
37
37
  super(url, options)
@@ -7,18 +7,16 @@ const Fastify = require('fastify')
7
7
  const From = require('..')
8
8
  const got = require('got')
9
9
 
10
- t.autoend(false)
11
-
10
+ t.test('undici connect timeout', async (t) => {
12
11
  // never connect
13
- net.connect = function (options) {
14
- return new net.Socket(options)
15
- }
12
+ net.connect = function (options) {
13
+ return new net.Socket(options)
14
+ }
16
15
 
17
- const target = http.createServer((req, res) => {
18
- t.fail('target never called')
19
- })
16
+ const target = http.createServer((req, res) => {
17
+ t.fail('target never called')
18
+ })
20
19
 
21
- async function main () {
22
20
  t.plan(2)
23
21
  await target.listen({ port: 0 })
24
22
 
@@ -53,6 +51,4 @@ async function main () {
53
51
  }
54
52
 
55
53
  t.fail()
56
- }
57
-
58
- main()
54
+ })
@@ -56,6 +56,8 @@ for (const [description, format] of Object.entries(configFormat)) {
56
56
  t.same(res.statusCode, 200)
57
57
  t.match(JSON.parse(data.toString()), { hello: 'world' })
58
58
  resolve()
59
+ instance.close()
60
+ target.close()
59
61
  })
60
62
  })
61
63
  })
@@ -9,23 +9,21 @@ const FakeTimers = require('@sinonjs/fake-timers')
9
9
 
10
10
  const clock = FakeTimers.createClock()
11
11
 
12
- t.autoend(false)
13
-
14
- const target = http.createServer((req, res) => {
15
- t.pass('request proxied')
16
- req.on('data', () => undefined)
17
- req.on('end', () => {
18
- res.writeHead(200)
19
- res.flushHeaders()
20
- res.write('test')
21
- clock.setTimeout(() => {
22
- res.end()
23
- t.end()
24
- }, 1000)
12
+ t.test('undici body timeout', async (t) => {
13
+ const target = http.createServer((req, res) => {
14
+ t.pass('request proxied')
15
+ req.on('data', () => undefined)
16
+ req.on('end', () => {
17
+ res.writeHead(200)
18
+ res.flushHeaders()
19
+ res.write('test')
20
+ clock.setTimeout(() => {
21
+ res.end()
22
+ t.end()
23
+ }, 1000)
24
+ })
25
25
  })
26
- })
27
26
 
28
- async function main () {
29
27
  await target.listen({ port: 0 })
30
28
 
31
29
  const instance = Fastify()
@@ -55,6 +53,4 @@ async function main () {
55
53
  }
56
54
 
57
55
  t.fail()
58
- }
59
-
60
- main()
56
+ })
@@ -9,21 +9,19 @@ const FakeTimers = require('@sinonjs/fake-timers')
9
9
 
10
10
  const clock = FakeTimers.createClock()
11
11
 
12
- t.autoend(false)
13
-
14
- const target = http.createServer((req, res) => {
15
- t.pass('request proxied')
16
- req.on('data', () => undefined)
17
- req.on('end', () => {
18
- res.flushHeaders()
19
- clock.setTimeout(() => {
20
- res.end()
21
- t.end()
22
- }, 1000)
12
+ t.test('undici body timeout', async (t) => {
13
+ const target = http.createServer((req, res) => {
14
+ t.pass('request proxied')
15
+ req.on('data', () => undefined)
16
+ req.on('end', () => {
17
+ res.flushHeaders()
18
+ clock.setTimeout(() => {
19
+ res.end()
20
+ t.end()
21
+ }, 1000)
22
+ })
23
23
  })
24
- })
25
24
 
26
- async function main () {
27
25
  await target.listen({ port: 0 })
28
26
 
29
27
  const instance = Fastify()
@@ -58,6 +56,4 @@ async function main () {
58
56
  }
59
57
 
60
58
  t.fail()
61
- }
62
-
63
- main()
59
+ })
@@ -8,21 +8,19 @@ const FakeTimers = require('@sinonjs/fake-timers')
8
8
 
9
9
  const clock = FakeTimers.createClock()
10
10
 
11
- t.autoend(false)
11
+ t.test('undici request timeout', async (t) => {
12
+ const target = Fastify()
13
+ t.teardown(target.close.bind(target))
12
14
 
13
- const target = Fastify()
14
- t.teardown(target.close.bind(target))
15
+ target.get('/', (request, reply) => {
16
+ t.pass('request arrives')
15
17
 
16
- target.get('/', (request, reply) => {
17
- t.pass('request arrives')
18
-
19
- clock.setTimeout(() => {
20
- reply.status(200).send('hello world')
21
- t.end()
22
- }, 1000)
23
- })
18
+ clock.setTimeout(() => {
19
+ reply.status(200).send('hello world')
20
+ t.end()
21
+ }, 1000)
22
+ })
24
23
 
25
- async function main () {
26
24
  await target.listen({ port: 0 })
27
25
 
28
26
  const instance = Fastify()
@@ -57,6 +55,4 @@ async function main () {
57
55
  }
58
56
 
59
57
  t.fail()
60
- }
61
-
62
- main()
58
+ })
@@ -1,4 +1,4 @@
1
- import fastify, { FastifyReply, FastifyRequest, RawServerBase, RequestGenericInterface } from "fastify";
1
+ import fastify, { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawServerBase, RequestGenericInterface } from "fastify";
2
2
  import * as http from 'http';
3
3
  import { IncomingHttpHeaders } from "http2";
4
4
  import * as https from 'https';
@@ -48,7 +48,6 @@ const fullOptions: FastifyReplyFromOptions = {
48
48
  globalAgent: false,
49
49
  destroyAgent: true
50
50
  };
51
- tap.autoend(false);
52
51
 
53
52
  async function main() {
54
53
  const server = fastify();
@@ -78,6 +77,12 @@ async function main() {
78
77
  getUpstream(req, base) {
79
78
  expectType<FastifyRequest<RequestGenericInterface, RawServerBase>>(req);
80
79
  return base;
80
+ },
81
+ onResponse(request, reply, res) {
82
+ expectType<FastifyRequest<RequestGenericInterface, RawServerBase>>(request);
83
+ expectType<FastifyReply<RawServerBase>>(reply);
84
+ expectType<RawReplyDefaultExpression<RawServerBase>>(res);
85
+ expectType<number>(res.statusCode);
81
86
  }
82
87
  });
83
88
  });