feat: support custom streams

closes #1825
This commit is contained in:
KristapsUI
2024-08-13 23:18:54 +03:00
committed by jongleberry
parent 579fd15bf8
commit 0486b65ef9
5 changed files with 54 additions and 4 deletions

View File

@@ -2,6 +2,7 @@
const { describe, it } = require('node:test')
const response = require('../../test-helpers/context').response
const CustomStream = require('../../test-helpers/stream')
const assert = require('assert')
const fs = require('fs')
const Stream = require('stream')
@@ -110,6 +111,12 @@ describe('res.body=', () => {
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
it('should support custom stream', () => {
const res = response()
res.body = new CustomStream.Readable()
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
it('should add error handler to the stream, but only once', () => {
const res = response()
const body = new Stream.PassThrough()

View File

@@ -16,6 +16,7 @@ const Emitter = require('events')
const util = require('util')
const Stream = require('stream')
const http = require('http')
const isStream = require('./is-stream.js')
const only = require('./only.js')
const { HttpError } = require('http-errors')
@@ -304,10 +305,10 @@ function respond (ctx) {
if (Buffer.isBuffer(body)) return res.end(body)
if (typeof body === 'string') return res.end(body)
if (body instanceof Stream) return body.pipe(res)
if (body instanceof Blob) return Stream.Readable.from(body.stream()).pipe(res)
if (body instanceof ReadableStream) return Stream.Readable.from(body).pipe(res)
if (body instanceof Response) return Stream.Readable.from(body?.body || '').pipe(res)
if (isStream(body)) return body.pipe(res)
// body: json
body = JSON.stringify(body)

18
lib/is-stream.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict'
const Stream = require('stream')
module.exports = (stream) => {
return (
stream instanceof Stream ||
(stream !== null &&
typeof stream === 'object' &&
!!stream.readable &&
typeof stream.pipe === 'function' &&
typeof stream.read === 'function' &&
typeof stream.readable === 'boolean' &&
typeof stream.readableObjectMode === 'boolean' &&
typeof stream.destroy === 'function' &&
typeof stream.destroyed === 'boolean')
)
}

View File

@@ -14,10 +14,10 @@ const destroy = require('destroy')
const assert = require('assert')
const extname = require('path').extname
const vary = require('vary')
const isStream = require('./is-stream.js')
const only = require('./only.js')
const util = require('util')
const encodeUrl = require('encodeurl')
const Stream = require('stream')
const deprecate = require('depd')('koa')
/**
@@ -172,7 +172,7 @@ module.exports = {
}
// stream
if (val instanceof Stream) {
if (isStream(val)) {
onFinish(this.res, destroy.bind(null, val))
if (original !== val) {
val.once('error', err => this.ctx.onerror(err))
@@ -240,7 +240,7 @@ module.exports = {
}
const { body } = this
if (!body || body instanceof Stream) return undefined
if (!body || isStream(body)) return undefined
if (typeof body === 'string') return Buffer.byteLength(body)
if (Buffer.isBuffer(body)) return body.length
return Buffer.byteLength(JSON.stringify(body))

24
test-helpers/stream.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict'
const { EventEmitter } = require('events')
class Readable extends EventEmitter {
pipe () {}
read () {}
destroy () {}
get readable () {
return true
}
get readableObjectMode () {
return false
}
get destroyed () {
return false
}
}
module.exports = {
Readable
}