Skip to content

Commit 6e10e2b

Browse files
committed
remove anti-pattern dispatcher hooks
onBodySent on onRequestSent are footguns that easily cause bugs when implementing logic will send multiple requests, e.g. redirect and retry. To achieve similar functionality wrap body into a stream and listen to 'data' and 'end' events. Refs: #2722
1 parent e2652b7 commit 6e10e2b

17 files changed

+10
-353
lines changed

docs/api/Dispatcher.md

-2
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,9 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
209209
* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
210210
* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
211211
* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
212-
* **onResponseStarted** `() => void` (optional) - Invoked when response is received, before headers have been read.
213212
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
214213
* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
215214
* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
216-
* **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
217215

218216
#### Example 1 - Dispatch GET request
219217

docs/api/RedirectHandler.md

-8
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,3 @@ Called when the request is complete.
8686
Parameters:
8787

8888
- **trailers** `object` - The trailers received.
89-
90-
#### `onBodySent(chunk)`
91-
92-
Called when the request body is sent.
93-
94-
Parameters:
95-
96-
- **chunk** `Buffer` - The chunk of the request body sent.

docs/api/RetryHandler.md

-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ const handler = new RetryHandler(
7373
},
7474
handler: {
7575
onConnect() {},
76-
onBodySent() {},
7776
onHeaders(status, _rawHeaders, resume, _statusMessage) {
7877
// do something with headers
7978
},
@@ -98,7 +97,6 @@ const handler = new RetryHandler(dispatchOptions, {
9897
dispatch: client.dispatch.bind(client),
9998
handler: {
10099
onConnect() {},
101-
onBodySent() {},
102100
onHeaders(status, _rawHeaders, resume, _statusMessage) {},
103101
onData(chunk) {},
104102
onComplete() {},

lib/client.js

+3-26
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,6 @@ class Parser {
726726
if (!request) {
727727
return -1
728728
}
729-
request.onResponseStarted()
730729
}
731730

732731
onHeaderField (buf) {
@@ -1605,16 +1604,13 @@ function write (client, request) {
16051604
assert(contentLength === null, 'no body must not have content length')
16061605
socket.write(`${header}\r\n`, 'latin1')
16071606
}
1608-
request.onRequestSent()
16091607
} else if (util.isBuffer(body)) {
16101608
assert(contentLength === body.byteLength, 'buffer body must have content length')
16111609

16121610
socket.cork()
16131611
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1')
16141612
socket.write(body)
16151613
socket.uncork()
1616-
request.onBodySent(body)
1617-
request.onRequestSent()
16181614
if (!expectsPayload) {
16191615
socket[kReset] = true
16201616
}
@@ -1788,7 +1784,6 @@ function writeH2 (client, session, request) {
17881784

17891785
stream.once('response', headers => {
17901786
const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers
1791-
request.onResponseStarted()
17921787

17931788
if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) {
17941789
stream.pause()
@@ -1851,15 +1846,13 @@ function writeH2 (client, session, request) {
18511846
function writeBodyH2 () {
18521847
/* istanbul ignore else: assertion */
18531848
if (!body) {
1854-
request.onRequestSent()
1849+
// Do nothing...
18551850
} else if (util.isBuffer(body)) {
18561851
assert(contentLength === body.byteLength, 'buffer body must have content length')
18571852
stream.cork()
18581853
stream.write(body)
18591854
stream.uncork()
18601855
stream.end()
1861-
request.onBodySent(body)
1862-
request.onRequestSent()
18631856
} else if (util.isBlobLike(body)) {
18641857
if (typeof body.stream === 'function') {
18651858
writeIterable({
@@ -1924,22 +1917,14 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength,
19241917
if (err) {
19251918
util.destroy(body, err)
19261919
util.destroy(h2stream, err)
1927-
} else {
1928-
request.onRequestSent()
19291920
}
19301921
}
19311922
)
19321923

1933-
pipe.on('data', onPipeData)
19341924
pipe.once('end', () => {
1935-
pipe.removeListener('data', onPipeData)
19361925
util.destroy(pipe)
19371926
})
19381927

1939-
function onPipeData (chunk) {
1940-
request.onBodySent(chunk)
1941-
}
1942-
19431928
return
19441929
}
19451930

@@ -2055,9 +2040,6 @@ async function writeBlob ({ h2stream, body, client, request, socket, contentLeng
20552040
socket.uncork()
20562041
}
20572042

2058-
request.onBodySent(buffer)
2059-
request.onRequestSent()
2060-
20612043
if (!expectsPayload) {
20622044
socket[kReset] = true
20632045
}
@@ -2103,15 +2085,13 @@ async function writeIterable ({ h2stream, body, client, request, socket, content
21032085
}
21042086

21052087
const res = h2stream.write(chunk)
2106-
request.onBodySent(chunk)
21072088
if (!res) {
21082089
await waitForDrain()
21092090
}
21102091
}
21112092
} catch (err) {
21122093
h2stream.destroy(err)
21132094
} finally {
2114-
request.onRequestSent()
21152095
h2stream.end()
21162096
h2stream
21172097
.off('close', onDrain)
@@ -2162,7 +2142,7 @@ class AsyncWriter {
21622142
}
21632143

21642144
write (chunk) {
2165-
const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this
2145+
const { socket, contentLength, client, bytesWritten, expectsPayload, header } = this
21662146

21672147
if (socket[kError]) {
21682148
throw socket[kError]
@@ -2210,8 +2190,6 @@ class AsyncWriter {
22102190

22112191
socket.uncork()
22122192

2213-
request.onBodySent(chunk)
2214-
22152193
if (!ret) {
22162194
if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) {
22172195
// istanbul ignore else: only for jest
@@ -2225,8 +2203,7 @@ class AsyncWriter {
22252203
}
22262204

22272205
end () {
2228-
const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this
2229-
request.onRequestSent()
2206+
const { socket, contentLength, client, bytesWritten, expectsPayload, header } = this
22302207

22312208
socket[kWriting] = false
22322209

lib/core/request.js

-28
Original file line numberDiff line numberDiff line change
@@ -201,30 +201,6 @@ class Request {
201201
}
202202
}
203203

204-
onBodySent (chunk) {
205-
if (this[kHandler].onBodySent) {
206-
try {
207-
return this[kHandler].onBodySent(chunk)
208-
} catch (err) {
209-
this.abort(err)
210-
}
211-
}
212-
}
213-
214-
onRequestSent () {
215-
if (channels.bodySent.hasSubscribers) {
216-
channels.bodySent.publish({ request: this })
217-
}
218-
219-
if (this[kHandler].onRequestSent) {
220-
try {
221-
return this[kHandler].onRequestSent()
222-
} catch (err) {
223-
this.abort(err)
224-
}
225-
}
226-
}
227-
228204
onConnect (abort) {
229205
assert(!this.aborted)
230206
assert(!this.completed)
@@ -237,10 +213,6 @@ class Request {
237213
}
238214
}
239215

240-
onResponseStarted () {
241-
return this[kHandler].onResponseStarted?.()
242-
}
243-
244216
onHeaders (statusCode, headers, resume, statusText) {
245217
assert(!this.aborted)
246218
assert(!this.completed)

lib/core/util.js

-4
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,6 @@ function validateHandler (handler, method, upgrade) {
323323
throw new InvalidArgumentError('invalid onError method')
324324
}
325325

326-
if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) {
327-
throw new InvalidArgumentError('invalid onBodySent method')
328-
}
329-
330326
if (upgrade || method === 'CONNECT') {
331327
if (typeof handler.onUpgrade !== 'function') {
332328
throw new InvalidArgumentError('invalid onUpgrade method')

lib/fetch/index.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -2115,15 +2115,13 @@ async function httpNetworkFetch (
21152115
timingInfo.finalNetworkRequestStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
21162116
},
21172117

2118-
onResponseStarted () {
2118+
onHeaders (status, rawHeaders, resume, statusText) {
21192119
// Set timingInfo’s final network-response start time to the coarsened shared current
21202120
// time given fetchParams’s cross-origin isolated capability, immediately after the
21212121
// user agent’s HTTP parser receives the first byte of the response (e.g., frame header
21222122
// bytes for HTTP/2 or response status line for HTTP/1.x).
21232123
timingInfo.finalNetworkResponseStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability)
2124-
},
21252124

2126-
onHeaders (status, rawHeaders, resume, statusText) {
21272125
if (status < 200) {
21282126
return
21292127
}

lib/handler/DecoratorHandler.js

-4
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,4 @@ module.exports = class DecoratorHandler {
2828
onComplete (...args) {
2929
return this.handler.onComplete(...args)
3030
}
31-
32-
onBodySent (...args) {
33-
return this.handler.onBodySent(...args)
34-
}
3531
}

lib/handler/RedirectHandler.js

-6
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,6 @@ class RedirectHandler {
173173
this.handler.onComplete(trailers)
174174
}
175175
}
176-
177-
onBodySent (chunk) {
178-
if (this.handler.onBodySent) {
179-
this.handler.onBodySent(chunk)
180-
}
181-
}
182176
}
183177

184178
function parseLocation (statusCode, headers) {

lib/handler/RetryHandler.js

-10
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ class RetryHandler {
7474
})
7575
}
7676

77-
onRequestSent () {
78-
if (this.handler.onRequestSent) {
79-
this.handler.onRequestSent()
80-
}
81-
}
82-
8377
onUpgrade (statusCode, headers, socket) {
8478
if (this.handler.onUpgrade) {
8579
this.handler.onUpgrade(statusCode, headers, socket)
@@ -94,10 +88,6 @@ class RetryHandler {
9488
}
9589
}
9690

97-
onBodySent (chunk) {
98-
if (this.handler.onBodySent) return this.handler.onBodySent(chunk)
99-
}
100-
10191
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
10292
const { statusCode, code, headers } = err
10393
const { method, retryOptions } = opts

test/http2.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ test('Should handle h2 request with body (string or buffer) - dispatch', t => {
808808
stream.end('hello h2!')
809809
})
810810

811-
t.plan(7)
811+
t.plan(6)
812812

813813
server.listen(0, () => {
814814
const client = new Client(`https://localhost:${server.address().port}`, {
@@ -846,9 +846,6 @@ test('Should handle h2 request with body (string or buffer) - dispatch', t => {
846846
onData (chunk) {
847847
response.push(chunk)
848848
},
849-
onBodySent (body) {
850-
t.equal(body.toString('utf-8'), expectedBody)
851-
},
852849
onComplete () {
853850
t.equal(Buffer.concat(response).toString('utf-8'), 'hello h2!')
854851
t.equal(

test/jest/interceptor.test.js

-8
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,6 @@ describe('interceptors with NtlmRequestHandler', () => {
143143
return this.handler.onComplete(...args)
144144
}
145145
}
146-
147-
onBodySent (...args) {
148-
if (this.requestCount < 2) {
149-
// Do nothing
150-
} else {
151-
return this.handler.onBodySent(...args)
152-
}
153-
}
154146
}
155147
let server
156148

test/mock-agent.js

+2-16
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ test('MockAgent - dispatch', t => {
149149
})
150150

151151
t.test('should throw if handler is not valid on redirect', (t) => {
152-
t.plan(7)
152+
t.plan(6)
153153

154154
const baseUrl = 'http://localhost:9999'
155155

@@ -173,24 +173,13 @@ test('MockAgent - dispatch', t => {
173173
onConnect: 'INVALID'
174174
}), new InvalidArgumentError('invalid onConnect method'))
175175

176-
t.throws(() => mockAgent.dispatch({
177-
origin: baseUrl,
178-
path: '/foo',
179-
method: 'GET'
180-
}, {
181-
onError: (err) => { throw err },
182-
onConnect: () => {},
183-
onBodySent: 'INVALID'
184-
}), new InvalidArgumentError('invalid onBodySent method'))
185-
186176
t.throws(() => mockAgent.dispatch({
187177
origin: baseUrl,
188178
path: '/foo',
189179
method: 'CONNECT'
190180
}, {
191181
onError: (err) => { throw err },
192182
onConnect: () => {},
193-
onBodySent: () => {},
194183
onUpgrade: 'INVALID'
195184
}), new InvalidArgumentError('invalid onUpgrade method'))
196185

@@ -201,7 +190,6 @@ test('MockAgent - dispatch', t => {
201190
}, {
202191
onError: (err) => { throw err },
203192
onConnect: () => {},
204-
onBodySent: () => {},
205193
onHeaders: 'INVALID'
206194
}), new InvalidArgumentError('invalid onHeaders method'))
207195

@@ -212,7 +200,6 @@ test('MockAgent - dispatch', t => {
212200
}, {
213201
onError: (err) => { throw err },
214202
onConnect: () => {},
215-
onBodySent: () => {},
216203
onHeaders: () => {},
217204
onData: 'INVALID'
218205
}), new InvalidArgumentError('invalid onData method'))
@@ -224,7 +211,6 @@ test('MockAgent - dispatch', t => {
224211
}, {
225212
onError: (err) => { throw err },
226213
onConnect: () => {},
227-
onBodySent: () => {},
228214
onHeaders: () => {},
229215
onData: () => {},
230216
onComplete: 'INVALID'
@@ -797,7 +783,7 @@ test('MockAgent - handle delays to simulate work', async (t) => {
797783
const response = await getResponse(body)
798784
t.equal(response, 'hello')
799785
const elapsedInMs = process.hrtime(start)[1] / 1e6
800-
t.ok(elapsedInMs >= 50, `Elapsed time is not greater than 50ms: ${elapsedInMs}`)
786+
t.ok(elapsedInMs >= 49, `Elapsed time is not greater than 50ms: ${elapsedInMs}`)
801787
})
802788

803789
test('MockAgent - should persist requests', async (t) => {

0 commit comments

Comments
 (0)