Skip to content

Commit aa1e221

Browse files
authored
@tus/server: fix promise rejection for stream conversion (#753)
1 parent e3a3491 commit aa1e221

File tree

4 files changed

+26
-20
lines changed

4 files changed

+26
-20
lines changed

.changeset/dull-fans-drop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tus/server": patch
3+
---
4+
5+
Fix unhandled promise rejection when converting a web stream to a Node.js stream when a client disconnects

packages/server/src/handlers/BaseHandler.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {DataStore, CancellationContext} from '@tus/utils'
55
import {ERRORS, type Upload, StreamLimiter, EVENTS} from '@tus/utils'
66
import throttle from 'lodash.throttle'
77
import stream from 'node:stream/promises'
8-
import {PassThrough, type Readable} from 'node:stream'
8+
import {PassThrough, Readable} from 'node:stream'
99

1010
const reExtractFileID = /([^/]+)\/?$/
1111
const reForwardedHost = /host="?([^";]+)/
@@ -127,7 +127,7 @@ export class BaseHandler extends EventEmitter {
127127
}
128128

129129
protected writeToStore(
130-
data: Readable,
130+
webStream: ReadableStream | null,
131131
upload: Upload,
132132
maxFileSize: number,
133133
context: CancellationContext
@@ -143,10 +143,17 @@ export class BaseHandler extends EventEmitter {
143143
// Create a PassThrough stream as a proxy to manage the request stream.
144144
// This allows for aborting the write process without affecting the incoming request stream.
145145
const proxy = new PassThrough()
146+
const nodeStream = webStream ? Readable.fromWeb(webStream) : Readable.from([])
147+
148+
// Ignore errors on the data stream to prevent crashes from client disconnections
149+
// We handle errors on the proxy stream instead.
150+
nodeStream.on('error', (err) => {
151+
/* do nothing */
152+
})
146153

147154
// gracefully terminate the proxy stream when the request is aborted
148155
const onAbort = () => {
149-
data.unpipe(proxy)
156+
nodeStream.unpipe(proxy)
150157

151158
if (!proxy.closed) {
152159
proxy.end()
@@ -155,13 +162,13 @@ export class BaseHandler extends EventEmitter {
155162
context.signal.addEventListener('abort', onAbort, {once: true})
156163

157164
proxy.on('error', (err) => {
158-
data.unpipe(proxy)
165+
nodeStream.unpipe(proxy)
159166
reject(err.name === 'AbortError' ? ERRORS.ABORTED : err)
160167
})
161168

162169
const postReceive = throttle(
163170
(offset: number) => {
164-
this.emit(EVENTS.POST_RECEIVE, data, {...upload, offset})
171+
this.emit(EVENTS.POST_RECEIVE, nodeStream, {...upload, offset})
165172
},
166173
this.options.postReceiveInterval,
167174
{leading: false}
@@ -177,9 +184,13 @@ export class BaseHandler extends EventEmitter {
177184
// to ensure that errors in the pipeline do not cause the request stream to be destroyed,
178185
// which would result in a socket hangup error for the client.
179186
stream
180-
.pipeline(data.pipe(proxy), new StreamLimiter(maxFileSize), async (stream) => {
181-
return this.store.write(stream as StreamLimiter, upload.id, upload.offset)
182-
})
187+
.pipeline(
188+
nodeStream.pipe(proxy),
189+
new StreamLimiter(maxFileSize),
190+
async (stream) => {
191+
return this.store.write(stream as StreamLimiter, upload.id, upload.offset)
192+
}
193+
)
183194
.then(resolve)
184195
.catch(reject)
185196
.finally(() => {

packages/server/src/handlers/PatchHandler.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,7 @@ export class PatchHandler extends BaseHandler {
9696
}
9797

9898
const maxBodySize = await this.calculateMaxBodySize(req, upload, maxFileSize)
99-
newOffset = await this.writeToStore(
100-
req.body ? Readable.fromWeb(req.body) : Readable.from([]),
101-
upload,
102-
maxBodySize,
103-
context
104-
)
99+
newOffset = await this.writeToStore(req.body, upload, maxBodySize, context)
105100
} finally {
106101
await lock.unlock()
107102
}

packages/server/src/handlers/PostHandler.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,7 @@ export class PostHandler extends BaseHandler {
129129
// The request MIGHT include a Content-Type header when using creation-with-upload extension
130130
if (validateHeader('content-type', req.headers.get('content-type'))) {
131131
const bodyMaxSize = await this.calculateMaxBodySize(req, upload, maxFileSize)
132-
const newOffset = await this.writeToStore(
133-
req.body ? Readable.fromWeb(req.body) : Readable.from([]),
134-
upload,
135-
bodyMaxSize,
136-
context
137-
)
132+
const newOffset = await this.writeToStore(req.body, upload, bodyMaxSize, context)
138133

139134
responseData.headers['Upload-Offset'] = newOffset.toString()
140135
isFinal = newOffset === Number.parseInt(upload_length as string, 10)

0 commit comments

Comments
 (0)