Skip to content

Commit a0afde8

Browse files
authored
fix: undici stream throwOnError (#1995)
* fix: undici stream throwOnError * fix: revert move getResolveErrorBodyCallback to api-request and pr comments * fix: handle error event
1 parent 3606c35 commit a0afde8

File tree

3 files changed

+93
-6
lines changed

3 files changed

+93
-6
lines changed

lib/api/api-stream.js

+33-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
'use strict'
22

3-
const { finished } = require('stream')
3+
const { finished, PassThrough } = require('stream')
44
const {
55
InvalidArgumentError,
66
InvalidReturnValueError,
7-
RequestAbortedError
7+
RequestAbortedError,
8+
ResponseStatusCodeError
89
} = require('../core/errors')
910
const util = require('../core/util')
1011
const { AsyncResource } = require('async_hooks')
@@ -16,7 +17,7 @@ class StreamHandler extends AsyncResource {
1617
throw new InvalidArgumentError('invalid opts')
1718
}
1819

19-
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
20+
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
2021

2122
try {
2223
if (typeof callback !== 'function') {
@@ -57,6 +58,7 @@ class StreamHandler extends AsyncResource {
5758
this.trailers = null
5859
this.body = body
5960
this.onInfo = onInfo || null
61+
this.throwOnError = throwOnError || false
6062

6163
if (util.isStream(body)) {
6264
body.on('error', (err) => {
@@ -76,8 +78,8 @@ class StreamHandler extends AsyncResource {
7678
this.context = context
7779
}
7880

79-
onHeaders (statusCode, rawHeaders, resume) {
80-
const { factory, opaque, context } = this
81+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
82+
const { factory, opaque, context, callback } = this
8183

8284
if (statusCode < 200) {
8385
if (this.onInfo) {
@@ -96,6 +98,32 @@ class StreamHandler extends AsyncResource {
9698
context
9799
})
98100

101+
if (this.throwOnError && statusCode >= 400) {
102+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
103+
const chunks = []
104+
const pt = new PassThrough()
105+
pt
106+
.on('data', (chunk) => chunks.push(chunk))
107+
.on('end', () => {
108+
const payload = Buffer.concat(chunks).toString('utf8')
109+
this.runInAsyncScope(
110+
callback,
111+
null,
112+
new ResponseStatusCodeError(
113+
`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`,
114+
statusCode,
115+
headers,
116+
payload
117+
)
118+
)
119+
})
120+
.on('error', (err) => {
121+
this.onError(err)
122+
})
123+
this.res = pt
124+
return
125+
}
126+
99127
if (
100128
!res ||
101129
typeof res.write !== 'function' ||

test/async_hooks.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ test('async hooks client is destroyed', (t) => {
157157
const client = new Client(`http://localhost:${server.address().port}`)
158158
t.teardown(client.destroy.bind(client))
159159

160-
client.request({ path: '/', method: 'GET' }, (err, { body }) => {
160+
client.request({ path: '/', method: 'GET', throwOnError: true }, (err, { body }) => {
161161
t.error(err)
162162
body.resume()
163163
body.on('error', (err) => {

test/client-stream.js

+59
Original file line numberDiff line numberDiff line change
@@ -785,4 +785,63 @@ test('stream legacy needDrain', (t) => {
785785
t.pass()
786786
})
787787
})
788+
789+
test('steam throwOnError', (t) => {
790+
t.plan(2)
791+
792+
const errStatusCode = 500
793+
const errMessage = 'Internal Server Error'
794+
795+
const server = createServer((req, res) => {
796+
res.writeHead(errStatusCode, { 'Content-Type': 'text/plain' })
797+
res.end(errMessage)
798+
})
799+
t.teardown(server.close.bind(server))
800+
801+
server.listen(0, async () => {
802+
const client = new Client(`http://localhost:${server.address().port}`)
803+
t.teardown(client.close.bind(client))
804+
805+
client.stream({
806+
path: '/',
807+
method: 'GET',
808+
throwOnError: true,
809+
opaque: new PassThrough()
810+
}, ({ opaque: pt }) => {
811+
pt.on('data', () => {
812+
t.fail()
813+
})
814+
return pt
815+
}, (e) => {
816+
t.equal(e.status, errStatusCode)
817+
t.equal(e.body, errMessage)
818+
t.end()
819+
})
820+
})
821+
})
822+
823+
test('steam throwOnError=true, error on stream', (t) => {
824+
t.plan(1)
825+
826+
const server = createServer((req, res) => {
827+
res.end('asd')
828+
})
829+
t.teardown(server.close.bind(server))
830+
831+
server.listen(0, async () => {
832+
const client = new Client(`http://localhost:${server.address().port}`)
833+
t.teardown(client.close.bind(client))
834+
835+
client.stream({
836+
path: '/',
837+
method: 'GET',
838+
throwOnError: true,
839+
opaque: new PassThrough()
840+
}, () => {
841+
throw new Error('asd')
842+
}, (e) => {
843+
t.equal(e.message, 'asd')
844+
})
845+
})
846+
})
788847
})

0 commit comments

Comments
 (0)