Skip to content

Commit 106bf1c

Browse files
authored
feat: dump interceptor (#3118)
1 parent 0980f9d commit 106bf1c

File tree

8 files changed

+701
-6
lines changed

8 files changed

+701
-6
lines changed

docs/docs/api/Dispatcher.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,38 @@ const client = new Client("http://example.com").compose(
952952
);
953953
```
954954

955+
##### `dump`
956+
957+
The `dump` interceptor enables you to dump the response body from a request upon a given limit.
958+
959+
**Options**
960+
- `maxSize` - The maximum size (in bytes) of the response body to dump. If the size of the request's body exceeds this value then the connection will be closed. Default: `1048576`.
961+
962+
> The `Dispatcher#options` also gets extended with the options `dumpMaxSize`, `abortOnDumped`, and `waitForTrailers` which can be used to configure the interceptor at a request-per-request basis.
963+
964+
**Example - Basic Dump Interceptor**
965+
966+
```js
967+
const { Client, interceptors } = require("undici");
968+
const { dump } = interceptors;
969+
970+
const client = new Client("http://example.com").compose(
971+
dump({
972+
maxSize: 1024,
973+
})
974+
);
975+
976+
// or
977+
client.dispatch(
978+
{
979+
path: "/",
980+
method: "GET",
981+
dumpMaxSize: 1024,
982+
},
983+
handler
984+
);
985+
```
986+
955987
## Instance Events
956988

957989
### Event: `'connect'`

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ module.exports.RedirectHandler = RedirectHandler
4040
module.exports.createRedirectInterceptor = createRedirectInterceptor
4141
module.exports.interceptors = {
4242
redirect: require('./lib/interceptor/redirect'),
43-
retry: require('./lib/interceptor/retry')
43+
retry: require('./lib/interceptor/retry'),
44+
dump: require('./lib/interceptor/dump')
4445
}
4546

4647
module.exports.buildConnector = buildConnector

lib/interceptor/dump.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
'use strict'
2+
3+
const util = require('../core/util')
4+
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
5+
const DecoratorHandler = require('../handler/decorator-handler')
6+
7+
class DumpHandler extends DecoratorHandler {
8+
#maxSize = 1024 * 1024
9+
#abort = null
10+
#dumped = false
11+
#aborted = false
12+
#size = 0
13+
#reason = null
14+
#handler = null
15+
16+
constructor ({ maxSize }, handler) {
17+
super(handler)
18+
19+
if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
20+
throw new InvalidArgumentError('maxSize must be a number greater than 0')
21+
}
22+
23+
this.#maxSize = maxSize ?? this.#maxSize
24+
this.#handler = handler
25+
}
26+
27+
onConnect (abort) {
28+
this.#abort = abort
29+
30+
this.#handler.onConnect(this.#customAbort.bind(this))
31+
}
32+
33+
#customAbort (reason) {
34+
this.#aborted = true
35+
this.#reason = reason
36+
}
37+
38+
// TODO: will require adjustment after new hooks are out
39+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
40+
const headers = util.parseHeaders(rawHeaders)
41+
const contentLength = headers['content-length']
42+
43+
if (contentLength != null && contentLength > this.#maxSize) {
44+
throw new RequestAbortedError(
45+
`Response size (${contentLength}) larger than maxSize (${
46+
this.#maxSize
47+
})`
48+
)
49+
}
50+
51+
if (this.#aborted) {
52+
return true
53+
}
54+
55+
return this.#handler.onHeaders(
56+
statusCode,
57+
rawHeaders,
58+
resume,
59+
statusMessage
60+
)
61+
}
62+
63+
onError (err) {
64+
if (this.#dumped) {
65+
return
66+
}
67+
68+
err = this.#reason ?? err
69+
70+
this.#handler.onError(err)
71+
}
72+
73+
onData (chunk) {
74+
this.#size = this.#size + chunk.length
75+
76+
if (this.#size >= this.#maxSize) {
77+
this.#dumped = true
78+
79+
if (this.#aborted) {
80+
this.#handler.onError(this.#reason)
81+
} else {
82+
this.#handler.onComplete([])
83+
}
84+
}
85+
86+
return true
87+
}
88+
89+
onComplete (trailers) {
90+
if (this.#dumped) {
91+
return
92+
}
93+
94+
if (this.#aborted) {
95+
this.#handler.onError(this.reason)
96+
return
97+
}
98+
99+
this.#handler.onComplete(trailers)
100+
}
101+
}
102+
103+
function createDumpInterceptor (
104+
{ maxSize: defaultMaxSize } = {
105+
maxSize: 1024 * 1024
106+
}
107+
) {
108+
return dispatch => {
109+
return function Intercept (opts, handler) {
110+
const { dumpMaxSize = defaultMaxSize } =
111+
opts
112+
113+
const dumpHandler = new DumpHandler(
114+
{ maxSize: dumpMaxSize },
115+
handler
116+
)
117+
118+
return dispatch(opts, dumpHandler)
119+
}
120+
}
121+
}
122+
123+
module.exports = createDumpInterceptor

0 commit comments

Comments
 (0)