@fastify/reply-from 12.0.2 → 12.1.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.
- package/.github/workflows/ci.yml +3 -0
- package/README.md +4 -0
- package/index.js +2 -1
- package/lib/request.js +5 -4
- package/package.json +1 -1
- package/test/http-timeout.test.js +57 -4
- package/test/http2-timeout.test.js +58 -0
- package/test/undici-timeout.test.js +65 -6
- package/types/index.d.ts +4 -3
- package/types/index.test-d.ts +4 -3
package/.github/workflows/ci.yml
CHANGED
package/README.md
CHANGED
|
@@ -477,6 +477,10 @@ By default: 0 (disabled)
|
|
|
477
477
|
Override the `'Content-Type'` header of the forwarded request, if we are
|
|
478
478
|
already overriding the [`body`](#body).
|
|
479
479
|
|
|
480
|
+
#### `timeout`
|
|
481
|
+
|
|
482
|
+
Set a specific timeout for the request. Override options `http.requestOptions.timeout`, `http2.requestOptions.timeout`, `undici.headersTimeout` and `undici.bodyTimeout` from the plugin config.
|
|
483
|
+
|
|
480
484
|
### Combining with [@fastify/formbody](https://github.com/fastify/fastify-formbody)
|
|
481
485
|
|
|
482
486
|
`formbody` expects the body to be returned as a string and not an object.
|
package/index.js
CHANGED
|
@@ -55,6 +55,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
55
55
|
opts = opts || {}
|
|
56
56
|
const req = this.request.raw
|
|
57
57
|
const method = opts.method || req.method
|
|
58
|
+
const timeout = opts.timeout
|
|
58
59
|
const onResponse = opts.onResponse
|
|
59
60
|
const rewriteHeaders = opts.rewriteHeaders || headersNoOp
|
|
60
61
|
const rewriteRequestHeaders = opts.rewriteRequestHeaders || requestHeadersNoOp
|
|
@@ -169,7 +170,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
169
170
|
requestImpl = createRequestRetry(request, this, getDefaultDelay)
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
requestImpl({ method, url, qs, headers: requestHeaders, body }, (err, res) => {
|
|
173
|
+
requestImpl({ method, url, qs, headers: requestHeaders, body, timeout }, (err, res) => {
|
|
173
174
|
if (err) {
|
|
174
175
|
this.request.log.warn(err, 'response errored')
|
|
175
176
|
if (!this.sent) {
|
package/lib/request.js
CHANGED
|
@@ -120,7 +120,8 @@ function buildRequest (opts) {
|
|
|
120
120
|
hostname: opts.url.hostname,
|
|
121
121
|
headers: opts.headers,
|
|
122
122
|
agent: agents[opts.url.protocol.replace(/^unix:/, '')],
|
|
123
|
-
...httpOpts.requestOptions
|
|
123
|
+
...httpOpts.requestOptions,
|
|
124
|
+
timeout: opts.timeout ?? httpOpts.requestOptions.timeout
|
|
124
125
|
})
|
|
125
126
|
req.on('error', done)
|
|
126
127
|
req.on('response', res => {
|
|
@@ -146,8 +147,8 @@ function buildRequest (opts) {
|
|
|
146
147
|
method: opts.method,
|
|
147
148
|
headers: Object.assign({}, opts.headers),
|
|
148
149
|
body: opts.body,
|
|
149
|
-
headersTimeout: undiciOpts.headersTimeout,
|
|
150
|
-
bodyTimeout: undiciOpts.bodyTimeout
|
|
150
|
+
headersTimeout: opts.timeout ?? undiciOpts.headersTimeout,
|
|
151
|
+
bodyTimeout: opts.timeout ?? undiciOpts.bodyTimeout
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
let pool
|
|
@@ -212,7 +213,7 @@ function buildRequest (opts) {
|
|
|
212
213
|
if (!isGet && !isDelete) {
|
|
213
214
|
end(req, opts.body, done)
|
|
214
215
|
}
|
|
215
|
-
req.setTimeout(http2Opts.requestTimeout, () => {
|
|
216
|
+
req.setTimeout(opts.timeout ?? http2Opts.requestTimeout, () => {
|
|
216
217
|
const err = new Http2RequestTimeoutError()
|
|
217
218
|
req.close(http2.constants.NGHTTP2_CANCEL)
|
|
218
219
|
done(err)
|
package/package.json
CHANGED
|
@@ -6,19 +6,19 @@ const From = require('..')
|
|
|
6
6
|
const got = require('got')
|
|
7
7
|
const FakeTimers = require('@sinonjs/fake-timers')
|
|
8
8
|
|
|
9
|
-
const clock = FakeTimers.createClock()
|
|
10
|
-
|
|
11
9
|
test('http request timeout', async (t) => {
|
|
10
|
+
const clock = FakeTimers.createClock()
|
|
12
11
|
const target = Fastify()
|
|
13
12
|
t.teardown(target.close.bind(target))
|
|
14
13
|
|
|
15
14
|
target.get('/', (_request, reply) => {
|
|
16
15
|
t.pass('request arrives')
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
setTimeout(() => {
|
|
19
18
|
reply.status(200).send('hello world')
|
|
20
|
-
t.end()
|
|
21
19
|
}, 200)
|
|
20
|
+
|
|
21
|
+
clock.tick(200)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
await target.listen({ port: 0 })
|
|
@@ -45,7 +45,60 @@ test('http request timeout', async (t) => {
|
|
|
45
45
|
error: 'Gateway Timeout',
|
|
46
46
|
message: 'Gateway Timeout'
|
|
47
47
|
})
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
t.fail()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('http request with specific timeout', async (t) => {
|
|
55
|
+
const clock = FakeTimers.createClock()
|
|
56
|
+
const target = Fastify()
|
|
57
|
+
t.teardown(target.close.bind(target))
|
|
58
|
+
|
|
59
|
+
target.get('/', (_request, reply) => {
|
|
60
|
+
t.pass('request arrives')
|
|
61
|
+
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
reply.status(200).send('hello world')
|
|
64
|
+
}, 200)
|
|
65
|
+
|
|
48
66
|
clock.tick(200)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
await target.listen({ port: 0 })
|
|
70
|
+
|
|
71
|
+
const instance = Fastify()
|
|
72
|
+
t.teardown(instance.close.bind(instance))
|
|
73
|
+
|
|
74
|
+
instance.register(From, { http: { requestOptions: { timeout: 100 } } })
|
|
75
|
+
|
|
76
|
+
instance.get('/success', (_request, reply) => {
|
|
77
|
+
reply.from(`http://localhost:${target.server.address().port}/`, {
|
|
78
|
+
timeout: 300
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
instance.get('/fail', (_request, reply) => {
|
|
82
|
+
reply.from(`http://localhost:${target.server.address().port}/`, {
|
|
83
|
+
timeout: 50
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
await instance.listen({ port: 0 })
|
|
88
|
+
const { statusCode } = await got.get(`http://localhost:${instance.server.address().port}/success`, { retry: 0 })
|
|
89
|
+
t.equal(statusCode, 200)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await got.get(`http://localhost:${instance.server.address().port}/fail`, { retry: 0 })
|
|
93
|
+
} catch (err) {
|
|
94
|
+
t.equal(err.response.statusCode, 504)
|
|
95
|
+
t.match(err.response.headers['content-type'], /application\/json/)
|
|
96
|
+
t.same(JSON.parse(err.response.body), {
|
|
97
|
+
statusCode: 504,
|
|
98
|
+
code: 'FST_REPLY_FROM_GATEWAY_TIMEOUT',
|
|
99
|
+
error: 'Gateway Timeout',
|
|
100
|
+
message: 'Gateway Timeout'
|
|
101
|
+
})
|
|
49
102
|
return
|
|
50
103
|
}
|
|
51
104
|
|
|
@@ -4,6 +4,7 @@ const { test } = require('tap')
|
|
|
4
4
|
const Fastify = require('fastify')
|
|
5
5
|
const From = require('..')
|
|
6
6
|
const got = require('got')
|
|
7
|
+
const FakeTimers = require('@sinonjs/fake-timers')
|
|
7
8
|
|
|
8
9
|
test('http2 request timeout', async (t) => {
|
|
9
10
|
const target = Fastify({ http2: true, sessionTimeout: 0 })
|
|
@@ -49,6 +50,63 @@ test('http2 request timeout', async (t) => {
|
|
|
49
50
|
t.fail()
|
|
50
51
|
})
|
|
51
52
|
|
|
53
|
+
test('http2 request with specific timeout', async (t) => {
|
|
54
|
+
const clock = FakeTimers.createClock()
|
|
55
|
+
const target = Fastify({ http2: true })
|
|
56
|
+
t.teardown(target.close.bind(target))
|
|
57
|
+
|
|
58
|
+
target.get('/', (_request, reply) => {
|
|
59
|
+
t.pass('request arrives')
|
|
60
|
+
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
reply.status(200).send('hello world')
|
|
63
|
+
}, 200)
|
|
64
|
+
|
|
65
|
+
clock.tick(200)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await target.listen({ port: 0 })
|
|
69
|
+
|
|
70
|
+
const instance = Fastify()
|
|
71
|
+
t.teardown(instance.close.bind(instance))
|
|
72
|
+
|
|
73
|
+
instance.register(From, {
|
|
74
|
+
base: `http://localhost:${target.server.address().port}`,
|
|
75
|
+
http2: { requestTimeout: 100, sessionTimeout: 6000 }
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
instance.get('/success', (_request, reply) => {
|
|
79
|
+
reply.from(`http://localhost:${target.server.address().port}/`, {
|
|
80
|
+
timeout: 300
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
instance.get('/fail', (_request, reply) => {
|
|
84
|
+
reply.from(`http://localhost:${target.server.address().port}/`, {
|
|
85
|
+
timeout: 50
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await instance.listen({ port: 0 })
|
|
90
|
+
const { statusCode } = await got.get(`http://localhost:${instance.server.address().port}/success`, { retry: 0 })
|
|
91
|
+
t.equal(statusCode, 200)
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await got.get(`http://localhost:${instance.server.address().port}/fail`, { retry: 0 })
|
|
95
|
+
} catch (err) {
|
|
96
|
+
t.equal(err.response.statusCode, 504)
|
|
97
|
+
t.match(err.response.headers['content-type'], /application\/json/)
|
|
98
|
+
t.same(JSON.parse(err.response.body), {
|
|
99
|
+
statusCode: 504,
|
|
100
|
+
code: 'FST_REPLY_FROM_GATEWAY_TIMEOUT',
|
|
101
|
+
error: 'Gateway Timeout',
|
|
102
|
+
message: 'Gateway Timeout'
|
|
103
|
+
})
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
t.fail()
|
|
108
|
+
})
|
|
109
|
+
|
|
52
110
|
test('http2 session timeout', async (t) => {
|
|
53
111
|
const target = Fastify({ http2: true, sessionTimeout: 0 })
|
|
54
112
|
t.teardown(target.close.bind(target))
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { test } = require('tap')
|
|
4
4
|
const Fastify = require('fastify')
|
|
5
5
|
const From = require('..')
|
|
6
6
|
const got = require('got')
|
|
7
7
|
const FakeTimers = require('@sinonjs/fake-timers')
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
t.test('undici request timeout', async (t) => {
|
|
9
|
+
test('undici request timeout', async (t) => {
|
|
10
|
+
const clock = FakeTimers.createClock()
|
|
12
11
|
const target = Fastify()
|
|
13
12
|
t.teardown(target.close.bind(target))
|
|
14
13
|
|
|
15
14
|
target.get('/', (_request, reply) => {
|
|
16
15
|
t.pass('request arrives')
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
setTimeout(() => {
|
|
19
18
|
reply.status(200).send('hello world')
|
|
20
|
-
t.end()
|
|
21
19
|
}, 1000)
|
|
20
|
+
|
|
21
|
+
clock.tick(1000)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
await target.listen({ port: 0 })
|
|
@@ -50,7 +50,66 @@ t.test('undici request timeout', async (t) => {
|
|
|
50
50
|
error: 'Gateway Timeout',
|
|
51
51
|
message: 'Gateway Timeout'
|
|
52
52
|
})
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
t.fail()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('undici request with specific timeout', async (t) => {
|
|
60
|
+
const clock = FakeTimers.createClock()
|
|
61
|
+
const target = Fastify()
|
|
62
|
+
t.teardown(target.close.bind(target))
|
|
63
|
+
|
|
64
|
+
target.get('/', (_request, reply) => {
|
|
65
|
+
t.pass('request arrives')
|
|
66
|
+
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
reply.status(200).send('hello world')
|
|
69
|
+
}, 1000)
|
|
70
|
+
|
|
53
71
|
clock.tick(1000)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
await target.listen({ port: 0 })
|
|
75
|
+
|
|
76
|
+
const instance = Fastify()
|
|
77
|
+
t.teardown(instance.close.bind(instance))
|
|
78
|
+
|
|
79
|
+
instance.register(From, {
|
|
80
|
+
base: `http://localhost:${target.server.address().port}`,
|
|
81
|
+
undici: {
|
|
82
|
+
headersTimeout: 100,
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
instance.get('/success', (_request, reply) => {
|
|
87
|
+
reply.from('/', {
|
|
88
|
+
timeout: 1000
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
instance.get('/fail', (_request, reply) => {
|
|
92
|
+
reply.from('/', {
|
|
93
|
+
timeout: 50
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
await instance.listen({ port: 0 })
|
|
98
|
+
|
|
99
|
+
const { statusCode } = await got.get(`http://localhost:${instance.server.address().port}/success`, { retry: 0 })
|
|
100
|
+
t.equal(statusCode, 200)
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await got.get(`http://localhost:${instance.server.address().port}/fail`, { retry: 0 })
|
|
104
|
+
} catch (err) {
|
|
105
|
+
t.equal(err.response.statusCode, 504)
|
|
106
|
+
t.match(err.response.headers['content-type'], /application\/json/)
|
|
107
|
+
t.same(JSON.parse(err.response.body), {
|
|
108
|
+
statusCode: 504,
|
|
109
|
+
code: 'FST_REPLY_FROM_GATEWAY_TIMEOUT',
|
|
110
|
+
error: 'Gateway Timeout',
|
|
111
|
+
message: 'Gateway Timeout'
|
|
112
|
+
})
|
|
54
113
|
return
|
|
55
114
|
}
|
|
56
115
|
|
package/types/index.d.ts
CHANGED
|
@@ -16,18 +16,18 @@ import {
|
|
|
16
16
|
AgentOptions,
|
|
17
17
|
IncomingHttpHeaders,
|
|
18
18
|
RequestOptions,
|
|
19
|
-
} from 'http'
|
|
19
|
+
} from 'node:http'
|
|
20
20
|
import {
|
|
21
21
|
ClientSessionOptions,
|
|
22
22
|
ClientSessionRequestOptions,
|
|
23
23
|
IncomingHttpHeaders as Http2IncomingHttpHeaders,
|
|
24
24
|
SecureClientSessionOptions,
|
|
25
|
-
} from 'http2'
|
|
25
|
+
} from 'node:http2'
|
|
26
26
|
import {
|
|
27
27
|
Agent as SecureAgent,
|
|
28
28
|
AgentOptions as SecureAgentOptions,
|
|
29
29
|
RequestOptions as SecureRequestOptions
|
|
30
|
-
} from 'https'
|
|
30
|
+
} from 'node:https'
|
|
31
31
|
import { Pool, ProxyAgent, Dispatcher } from 'undici'
|
|
32
32
|
|
|
33
33
|
declare module 'fastify' {
|
|
@@ -83,6 +83,7 @@ declare namespace fastifyReplyFrom {
|
|
|
83
83
|
base: string
|
|
84
84
|
) => string;
|
|
85
85
|
method?: HTTPMethods;
|
|
86
|
+
timeout?: number;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
interface Http2Options {
|
package/types/index.test-d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fastify, { FastifyReply, FastifyRequest, RawReplyDefaultExpression, RawServerBase, RequestGenericInterface, RouteGenericInterface } from 'fastify'
|
|
2
2
|
import * as http from 'node:http'
|
|
3
|
-
import { IncomingHttpHeaders } from 'http2'
|
|
3
|
+
import { IncomingHttpHeaders } from 'node:http2'
|
|
4
4
|
import * as https from 'node:https'
|
|
5
|
-
import { AddressInfo } from 'net'
|
|
5
|
+
import { AddressInfo } from 'node:net'
|
|
6
6
|
import { expectType } from 'tsd'
|
|
7
7
|
import { Agent, Client, Dispatcher, Pool } from 'undici'
|
|
8
8
|
import replyFrom, { FastifyReplyFromOptions } from '..'
|
|
@@ -70,6 +70,7 @@ async function main () {
|
|
|
70
70
|
})
|
|
71
71
|
server.get('/v3', (_request, reply) => {
|
|
72
72
|
reply.from('/v3', {
|
|
73
|
+
timeout: 1000,
|
|
73
74
|
body: { hello: 'world' },
|
|
74
75
|
rewriteRequestHeaders (req, headers) {
|
|
75
76
|
expectType<FastifyRequest<RequestGenericInterface, RawServerBase>>(req)
|
|
@@ -102,7 +103,7 @@ async function main () {
|
|
|
102
103
|
instance.get('/http2', (_request, reply) => {
|
|
103
104
|
reply.from('/', {
|
|
104
105
|
method: 'POST',
|
|
105
|
-
|
|
106
|
+
|
|
106
107
|
retryDelay: ({ req, res, getDefaultDelay }) => {
|
|
107
108
|
const defaultDelay = getDefaultDelay()
|
|
108
109
|
if (defaultDelay) return defaultDelay
|