Skip to content

Commit

Permalink
feat: add stream pipe response (#68)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
  • Loading branch information
stafyniaksacha and pi0 committed Mar 16, 2022
1 parent 11ef8e3 commit c3eb8ea
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IncomingMessage, ServerResponse } from './types/node'
import { lazyHandle, promisifyHandle } from './handle'
import type { Handle, LazyHandle, Middleware, PHandle } from './handle'
import { createError, sendError } from './error'
import { send, MIMES } from './utils'
import { send, sendStream, isStream, MIMES } from './utils'

export interface Layer {
route: string
Expand Down Expand Up @@ -114,6 +114,8 @@ export function createHandle (stack: Stack, options: AppOptions): PHandle {
const type = typeof val
if (type === 'string') {
return send(res, val, MIMES.html)
} else if (isStream(val)) {
return sendStream(res, val)
} else if (type === 'object' || type === 'boolean' || type === 'number' /* IS_JSON */) {
if (val && val.buffer) {
return send(res, val)
Expand Down
13 changes: 13 additions & 0 deletions src/utils/response.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ServerResponse } from 'http'
import { createError } from '../error'
import { MIMES } from './consts'

const defer = typeof setImmediate !== 'undefined' ? setImmediate : (fn: Function) => fn()
Expand Down Expand Up @@ -41,3 +42,15 @@ export function appendHeader (res: ServerResponse, name: string, value: string):

res.setHeader(name, current.concat(value))
}

export function isStream (data: any) {
return typeof data === 'object' && typeof data.pipe === 'function' && typeof data.on === 'function'
}

export function sendStream (res: ServerResponse, data: any) {
return new Promise((resolve, reject) => {
data.pipe(res)
data.on('end', () => resolve(undefined))
data.on('error', (error: Error) => reject(createError(error)))
})
}
43 changes: 43 additions & 0 deletions test/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Readable, Transform } from 'stream'
import supertest, { SuperTest, Test } from 'supertest'
import { describe, it, expect, beforeEach } from 'vitest'
import { createApp, App } from '../src'
Expand All @@ -18,6 +19,48 @@ describe('app', () => {
expect(res.body).toEqual({ url: '/' })
})

it('can return Buffer directly', async () => {
app.use(() => Buffer.from('<h1>Hello world!</h1>', 'utf8'))
const res = await request.get('/')

expect(res.text).toBe('<h1>Hello world!</h1>')
})

it('can return Readable stream directly', async () => {
app.use(() => {
const readable = new Readable()
readable.push(Buffer.from('<h1>Hello world!</h1>', 'utf8'))
readable.push(null)
return readable
})
const res = await request.get('/')

expect(res.text).toBe('<h1>Hello world!</h1>')
expect(res.header['transfer-encoding']).toBe('chunked')
})

it('can return Readable stream that may throw', async () => {
app.use(() => {
const readable = new Readable()
const willThrow = new Transform({
transform (
_chunk,
_encoding,
callback
) {
setTimeout(() => callback(new Error('test')), 0)
}
})
readable.push(Buffer.from('<h1>Hello world!</h1>', 'utf8'))
readable.push(null)

return readable.pipe(willThrow)
})
const res = await request.get('/')

expect(res.status).toBe(500)
})

it('can return HTML directly', async () => {
app.use(() => '<h1>Hello world!</h1>')
const res = await request.get('/')
Expand Down

0 comments on commit c3eb8ea

Please sign in to comment.