@fastify/reply-from 8.3.0 → 8.3.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.
package/.taprc CHANGED
@@ -1,8 +1,11 @@
1
1
  ts: false
2
2
  jsx: false
3
3
  flow: false
4
- coverage: true
5
- check-coverage: false
4
+
5
+ functions: 97
6
+ lines: 96
7
+ statements: 96
8
+ branches: 96
6
9
 
7
10
  files:
8
11
  - test/**/*.js
package/README.md CHANGED
@@ -203,8 +203,7 @@ This only applies when a custom [`body`](#body) is not passed in. Defaults to:
203
203
 
204
204
  ```js
205
205
  [
206
- 'application/json',
207
- 'application/x-www-form-urlencoded'
206
+ 'application/json'
208
207
  ]
209
208
  ```
210
209
 
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const fp = require('fastify-plugin')
4
- const lru = require('tiny-lru')
4
+ const { lru } = require('tiny-lru')
5
5
  const querystring = require('fast-querystring')
6
6
  const Stream = require('stream')
7
7
  const buildRequest = require('./lib/request')
@@ -21,7 +21,7 @@ const {
21
21
  InternalServerError
22
22
  } = require('./lib/errors')
23
23
 
24
- module.exports = fp(function from (fastify, opts, next) {
24
+ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
25
25
  const contentTypesToEncode = new Set([
26
26
  'application/json',
27
27
  ...(opts.contentTypesToEncode || [])
@@ -33,12 +33,17 @@ module.exports = fp(function from (fastify, opts, next) {
33
33
 
34
34
  const cache = opts.disableCache ? undefined : lru(opts.cacheURLs || 100)
35
35
  const base = opts.base
36
- const { request, close, retryOnError } = buildRequest({
36
+ const requestBuilt = buildRequest({
37
37
  http: opts.http,
38
38
  http2: opts.http2,
39
39
  base,
40
40
  undici: opts.undici
41
41
  })
42
+ if (requestBuilt instanceof Error) {
43
+ next(requestBuilt)
44
+ return
45
+ }
46
+ const { request, close, retryOnError } = requestBuilt
42
47
  const disableRequestLogging = opts.disableRequestLogging || false
43
48
 
44
49
  fastify.decorateReply('from', function (source, opts) {
@@ -60,8 +65,9 @@ module.exports = fp(function from (fastify, opts, next) {
60
65
  const dest = getUpstream(req, base)
61
66
  let url
62
67
  if (cache) {
63
- url = cache.get(source) || buildURL(source, dest)
64
- cache.set(source, url)
68
+ const cacheKey = dest + source
69
+ url = cache.get(cacheKey) || buildURL(source, dest)
70
+ cache.set(cacheKey, url)
65
71
  } else {
66
72
  url = buildURL(source, dest)
67
73
  }
@@ -274,3 +280,7 @@ function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, max
274
280
 
275
281
  return requestRetry
276
282
  }
283
+
284
+ module.exports = fastifyReplyFrom
285
+ module.exports.default = fastifyReplyFrom
286
+ module.exports.fastifyReplyFrom = fastifyReplyFrom
package/lib/request.js CHANGED
@@ -47,9 +47,9 @@ function buildRequest (opts) {
47
47
  let agents
48
48
 
49
49
  if (isHttp2) {
50
- if (!opts.base) throw new Error('Option base is required when http2 is true')
50
+ if (!opts.base) return new Error('Option base is required when http2 is true')
51
51
  if (opts.base.startsWith('unix+')) {
52
- throw new Error('Unix socket destination is not supported when http2 is true')
52
+ return new Error('Unix socket destination is not supported when http2 is true')
53
53
  }
54
54
  } else {
55
55
  agents = httpOpts.agents || {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/reply-from",
3
- "version": "8.3.0",
3
+ "version": "8.3.1",
4
4
  "description": "forward your HTTP request to another server, for fastify",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "@fastify/formbody": "^7.0.1",
32
32
  "@fastify/multipart": "^7.1.0",
33
33
  "@fastify/pre-commit": "^2.0.2",
34
- "@sinonjs/fake-timers": "^9.1.2",
34
+ "@sinonjs/fake-timers": "^10.0.0",
35
35
  "@types/node": "^18.0.0",
36
36
  "@types/tap": "^15.0.7",
37
37
  "fastify": "^4.0.2",
@@ -46,7 +46,7 @@
46
46
  "split2": "^4.1.0",
47
47
  "standard": "^17.0.0",
48
48
  "tap": "^16.2.0",
49
- "tsd": "^0.23.0"
49
+ "tsd": "^0.24.1"
50
50
  },
51
51
  "dependencies": {
52
52
  "@fastify/error": "^3.0.0",
@@ -54,7 +54,7 @@
54
54
  "fast-querystring": "^1.0.0",
55
55
  "fastify-plugin": "^4.0.0",
56
56
  "pump": "^3.0.0",
57
- "tiny-lru": "^8.0.2",
57
+ "tiny-lru": "^10.0.0",
58
58
  "undici": "^5.5.1"
59
59
  },
60
60
  "pre-commit": [
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const Fastify = require('fastify')
5
+ const From = require('..')
6
+
7
+ async function createTarget (i) {
8
+ const target = Fastify({
9
+ keepAliveTimeout: 1
10
+ })
11
+
12
+ target.get('/test', async () => {
13
+ return `Hello from target ${i}`
14
+ })
15
+
16
+ t.teardown(() => target.close())
17
+ await target.listen({ port: 3000 + i })
18
+ }
19
+
20
+ t.plan(4)
21
+
22
+ async function run () {
23
+ await Promise.all([
24
+ createTarget(1),
25
+ createTarget(2)
26
+ ])
27
+
28
+ const instance = Fastify({
29
+ keepAliveTimeout: 1
30
+ })
31
+
32
+ instance.register(From, {
33
+ base: 'http://localhost',
34
+ http: true
35
+ })
36
+
37
+ instance.get('/', (req, reply) => {
38
+ const hostNumber = parseInt(req.headers['x-host-number'])
39
+ const port = 3000 + hostNumber
40
+
41
+ reply.from('/test', {
42
+ getUpstream () {
43
+ return `http://localhost:${port}`
44
+ }
45
+ })
46
+ })
47
+
48
+ t.teardown(() => instance.close())
49
+ await instance.listen({ port: 3000 })
50
+
51
+ const res1 = await instance.inject({
52
+ method: 'GET',
53
+ url: '/',
54
+ headers: {
55
+ 'x-host-number': 1
56
+ }
57
+ })
58
+ t.equal(res1.statusCode, 200)
59
+ t.equal(res1.body, 'Hello from target 1')
60
+
61
+ const res2 = await instance.inject({
62
+ method: 'GET',
63
+ url: '/',
64
+ headers: {
65
+ 'x-host-number': 2
66
+ }
67
+ })
68
+ t.equal(res2.statusCode, 200)
69
+ t.equal(res2.body, 'Hello from target 2')
70
+ }
71
+
72
+ run()
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const Fastify = require('fastify')
5
+ const From = require('../index')
6
+
7
+ test('http2 invalid base', async (t) => {
8
+ const instance = Fastify()
9
+
10
+ await t.rejects(instance.register(From, {
11
+ http2: { requestTimeout: 100 }
12
+ }), new Error('Option base is required when http2 is true'))
13
+ })
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const Fastify = require('fastify')
5
+ const From = require('../index')
6
+
7
+ test('throw an error if http2 is used with a Unix socket destination', async t => {
8
+ t.plan(1)
9
+
10
+ const instance = Fastify()
11
+
12
+ await t.rejects(instance.register(From, {
13
+ base: 'unix+http://localhost:1337',
14
+ http2: { requestTimeout: 100 }
15
+ }), new Error('Unix socket destination is not supported when http2 is true'))
16
+ })
@@ -0,0 +1,131 @@
1
+ 'use strict'
2
+
3
+ const test = require('tap').test
4
+ const Fastify = require('fastify')
5
+ const From = require('../index')
6
+ const http = require('http')
7
+ const get = require('simple-get').concat
8
+ const { parse } = require('querystring')
9
+
10
+ test('with explicitly set content-type application/octet-stream', t => {
11
+ const instance = Fastify()
12
+ instance.register(From, {
13
+ contentTypesToEncode: ['application/octet-stream']
14
+ })
15
+
16
+ instance.addContentTypeParser(
17
+ 'application/octet-stream',
18
+ { parseAs: 'buffer', bodyLimit: 1000 },
19
+ (req, body, done) => done(null, parse(body.toString()))
20
+ )
21
+
22
+ t.plan(9)
23
+ t.teardown(instance.close.bind(instance))
24
+
25
+ const target = http.createServer((req, res) => {
26
+ t.pass('request proxied')
27
+ t.equal(req.method, 'POST')
28
+ t.equal(req.headers['content-type'], 'application/octet-stream')
29
+ let data = ''
30
+ req.setEncoding('utf8')
31
+ req.on('data', (d) => {
32
+ data += d
33
+ })
34
+ req.on('end', () => {
35
+ const str = data.toString()
36
+ t.same(JSON.parse(data), { some: 'info', another: 'detail' })
37
+ res.statusCode = 200
38
+ res.setHeader('content-type', 'application/octet-stream')
39
+ res.end(str)
40
+ })
41
+ })
42
+
43
+ instance.post('/', (request, reply) => {
44
+ reply.from(`http://localhost:${target.address().port}`)
45
+ })
46
+
47
+ t.teardown(target.close.bind(target))
48
+
49
+ instance.listen({ port: 0 }, (err) => {
50
+ t.error(err)
51
+
52
+ target.listen({ port: 0 }, (err) => {
53
+ t.error(err)
54
+
55
+ get({
56
+ url: `http://localhost:${instance.server.address().port}`,
57
+ method: 'POST',
58
+ headers: { 'content-type': 'application/octet-stream' },
59
+ body: 'some=info&another=detail'
60
+ }, (err, res, data) => {
61
+ t.error(err)
62
+ t.equal(res.headers['content-type'], 'application/octet-stream')
63
+ t.same(JSON.parse(data), { some: 'info', another: 'detail' })
64
+ })
65
+ })
66
+ })
67
+ })
68
+
69
+ test('with implicit content-type application/octet-stream', t => {
70
+ const instance = Fastify()
71
+ instance.register(From, {
72
+ contentTypesToEncode: ['application/octet-stream']
73
+ })
74
+
75
+ instance.addContentTypeParser(
76
+ 'application/octet-stream',
77
+ { parseAs: 'buffer', bodyLimit: 1000 },
78
+ (req, body, done) => done(null, parse(body.toString()))
79
+ )
80
+
81
+ instance.addContentTypeParser(
82
+ '*',
83
+ { parseAs: 'buffer', bodyLimit: 1000 },
84
+ (req, body, done) => done(null, parse(body.toString()))
85
+ )
86
+
87
+ t.plan(9)
88
+ t.teardown(instance.close.bind(instance))
89
+
90
+ const target = http.createServer((req, res) => {
91
+ t.pass('request proxied')
92
+ t.equal(req.method, 'POST')
93
+ t.equal(req.headers['content-type'], 'application/octet-stream')
94
+ let data = ''
95
+ req.setEncoding('utf8')
96
+ req.on('data', (d) => {
97
+ data += d
98
+ })
99
+ req.on('end', () => {
100
+ const str = data.toString()
101
+ t.same(JSON.parse(data), { some: 'info', another: 'detail' })
102
+ res.statusCode = 200
103
+ res.setHeader('content-type', 'application/octet-stream')
104
+ res.end(str)
105
+ })
106
+ })
107
+
108
+ instance.post('/', (request, reply) => {
109
+ reply.from(`http://localhost:${target.address().port}`)
110
+ })
111
+
112
+ t.teardown(target.close.bind(target))
113
+
114
+ instance.listen({ port: 0 }, (err) => {
115
+ t.error(err)
116
+
117
+ target.listen({ port: 0 }, (err) => {
118
+ t.error(err)
119
+
120
+ get({
121
+ url: `http://localhost:${instance.server.address().port}`,
122
+ method: 'POST',
123
+ body: 'some=info&another=detail'
124
+ }, (err, res, data) => {
125
+ t.error(err)
126
+ t.equal(res.headers['content-type'], 'application/octet-stream')
127
+ t.same(JSON.parse(data), { some: 'info', another: 'detail' })
128
+ })
129
+ })
130
+ })
131
+ })
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const test = require('tap').test
4
+ const filterPseudoHeaders = require('../lib/utils').filterPseudoHeaders
5
+
6
+ test('filterPseudoHeaders', t => {
7
+ t.plan(1)
8
+ const headers = {
9
+ accept: '*/*',
10
+ 'Content-Type': 'text/html; charset=UTF-8',
11
+ ':method': 'GET'
12
+ }
13
+
14
+ t.strictSame(filterPseudoHeaders(headers), {
15
+ accept: '*/*',
16
+ 'content-type': 'text/html; charset=UTF-8'
17
+ })
18
+ })
package/types/index.d.ts CHANGED
@@ -30,68 +30,76 @@ import {
30
30
  SecureClientSessionOptions,
31
31
  } from "http2";
32
32
  import { Pool } from 'undici'
33
- type QueryStringFunction = (search: string | undefined, reqUrl: string) => string;
34
- export interface FastifyReplyFromHooks {
35
- queryString?: { [key: string]: unknown } | QueryStringFunction;
36
- contentType?: string;
37
- onResponse?: (
38
- request: FastifyRequest<RequestGenericInterface, RawServerBase>,
39
- reply: FastifyReply<RawServerBase>,
40
- res: RawReplyDefaultExpression<RawServerBase>
41
- ) => void;
42
- onError?: (
43
- reply: FastifyReply<RawServerBase>,
44
- error: { error: Error }
45
- ) => void;
46
- body?: unknown;
47
- rewriteHeaders?: (
48
- headers: Http2IncomingHttpHeaders | IncomingHttpHeaders,
49
- req?: Http2ServerRequest | IncomingMessage
50
- ) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
51
- rewriteRequestHeaders?: (
52
- req: Http2ServerRequest | IncomingMessage,
53
- headers: Http2IncomingHttpHeaders | IncomingHttpHeaders
54
- ) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
55
- getUpstream?: (
56
- req: Http2ServerRequest | IncomingMessage,
57
- base: string
58
- ) => string;
59
- }
60
33
 
61
34
  declare module "fastify" {
62
35
  interface FastifyReply {
63
36
  from(
64
37
  source?: string,
65
- opts?: FastifyReplyFromHooks
66
- ): void;
38
+ opts?: fastifyReplyFrom.FastifyReplyFromHooks
39
+ ): this;
67
40
  }
68
41
  }
69
42
 
70
- interface Http2Options {
71
- sessionTimeout?: number;
72
- requestTimeout?: number;
73
- sessionOptions?: ClientSessionOptions | SecureClientSessionOptions;
74
- requestOptions?: ClientSessionRequestOptions;
75
- }
43
+ type FastifyReplyFrom = FastifyPluginCallback<fastifyReplyFrom.FastifyReplyFromOptions>
76
44
 
77
- interface HttpOptions {
78
- agentOptions?: AgentOptions | SecureAgentOptions;
79
- requestOptions?: RequestOptions | SecureRequestOptions;
80
- agents?: { 'http:': Agent, 'https:': SecureAgent }
81
- }
45
+ declare namespace fastifyReplyFrom {
46
+ type QueryStringFunction = (search: string | undefined, reqUrl: string) => string;
47
+ export interface FastifyReplyFromHooks {
48
+ queryString?: { [key: string]: unknown } | QueryStringFunction;
49
+ contentType?: string;
50
+ onResponse?: (
51
+ request: FastifyRequest<RequestGenericInterface, RawServerBase>,
52
+ reply: FastifyReply<RawServerBase>,
53
+ res: RawReplyDefaultExpression<RawServerBase>
54
+ ) => void;
55
+ onError?: (
56
+ reply: FastifyReply<RawServerBase>,
57
+ error: { error: Error }
58
+ ) => void;
59
+ body?: unknown;
60
+ rewriteHeaders?: (
61
+ headers: Http2IncomingHttpHeaders | IncomingHttpHeaders,
62
+ req?: Http2ServerRequest | IncomingMessage
63
+ ) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
64
+ rewriteRequestHeaders?: (
65
+ req: Http2ServerRequest | IncomingMessage,
66
+ headers: Http2IncomingHttpHeaders | IncomingHttpHeaders
67
+ ) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
68
+ getUpstream?: (
69
+ req: Http2ServerRequest | IncomingMessage,
70
+ base: string
71
+ ) => string;
72
+ }
73
+
74
+ interface Http2Options {
75
+ sessionTimeout?: number;
76
+ requestTimeout?: number;
77
+ sessionOptions?: ClientSessionOptions | SecureClientSessionOptions;
78
+ requestOptions?: ClientSessionRequestOptions;
79
+ }
80
+
81
+ interface HttpOptions {
82
+ agentOptions?: AgentOptions | SecureAgentOptions;
83
+ requestOptions?: RequestOptions | SecureRequestOptions;
84
+ agents?: { 'http:': Agent, 'https:': SecureAgent }
85
+ }
86
+
87
+ export interface FastifyReplyFromOptions {
88
+ base?: string;
89
+ cacheURLs?: number;
90
+ disableCache?: boolean;
91
+ http?: HttpOptions;
92
+ http2?: Http2Options | boolean;
93
+ undici?: Pool.Options;
94
+ contentTypesToEncode?: string[];
95
+ retryMethods?: (HTTPMethods | 'TRACE')[];
96
+ maxRetriesOn503?: number;
97
+ disableRequestLogging?: boolean;
98
+ }
82
99
 
83
- export interface FastifyReplyFromOptions {
84
- base?: string;
85
- cacheURLs?: number;
86
- disableCache?: boolean;
87
- http?: HttpOptions;
88
- http2?: Http2Options | boolean;
89
- undici?: Pool.Options;
90
- contentTypesToEncode?: string[];
91
- retryMethods?: (HTTPMethods | 'TRACE')[];
92
- maxRetriesOn503?: number;
93
- disableRequestLogging?: boolean;
100
+ export const fastifyReplyFrom: FastifyReplyFrom
101
+ export { fastifyReplyFrom as default }
94
102
  }
95
103
 
96
- declare const fastifyReplyFrom: FastifyPluginCallback<FastifyReplyFromOptions>;
97
- export default fastifyReplyFrom;
104
+ declare function fastifyReplyFrom(...params: Parameters<FastifyReplyFrom>): ReturnType<FastifyReplyFrom>
105
+ export = fastifyReplyFrom
@@ -60,7 +60,7 @@ async function main() {
60
60
  server.register(replyFrom, fullOptions);
61
61
 
62
62
  server.get("/v1", (request, reply) => {
63
- reply.from();
63
+ expectType<FastifyReply>(reply.from());
64
64
  });
65
65
  server.get("/v3", (request, reply) => {
66
66
  reply.from("/v3", {