Skip to content

Commit

Permalink
feat: add support web WHATWG (#1830)
Browse files Browse the repository at this point in the history
closes #1777
  • Loading branch information
kravorkid authored Jun 28, 2024
1 parent 757e4eb commit 8d5beb3
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
87 changes: 87 additions & 0 deletions __tests__/application/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,93 @@ describe('app.respond', () => {
})
})

describe('when .body is a Blob', () => {
it('should respond', async () => {
const app = new Koa()

app.use(ctx => {
ctx.body = new Blob(['Hello'])
})

const expectedBlob = new Blob(['Hello'])

const server = app.listen()

const res = await request(server)
.get('/')
.expect(200)

assert.deepStrictEqual(res.body, Buffer.from(await expectedBlob.arrayBuffer()))
})

it('should keep Blob headers', async () => {
const app = new Koa()

app.use(ctx => {
ctx.body = new Blob(['hello world'])
})

const server = app.listen()

return request(server)
.head('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect('content-length', '11')
})
})

describe('when .body is a ReadableStream', () => {
it('should respond', async () => {
const app = new Koa()

app.use(async ctx => {
ctx.body = new ReadableStream()
})

const server = app.listen()

return request(server)
.head('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
})
})

describe('when .body is a Response', () => {
it('should keep Response headers', () => {
const app = new Koa()

app.use(ctx => {
ctx.body = new Response(null, { status: 201, statusText: 'OK', headers: { 'Content-Type': 'text/plain' } })
})

const server = app.listen()

return request(server)
.head('/')
.expect(201)
.expect('content-type', 'text/plain')
.expect('content-length', '2')
})

it('should default to octet-stream', () => {
const app = new Koa()

app.use(ctx => {
ctx.body = new Response(null, { status: 200, statusText: 'OK' })
})

const server = app.listen()

return request(server)
.head('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect('content-length', '2')
})
})

describe('when .body is a Stream', () => {
it('should respond', async () => {
const app = new Koa()
Expand Down
44 changes: 44 additions & 0 deletions __tests__/response/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,48 @@ describe('res.body=', () => {
assert.strictEqual('application/json; charset=utf-8', res.header['content-type'])
})
})

describe('when a ReadableStream is given', () => {
it('should default to an octet stream', () => {
const res = response()
res.body = new ReadableStream()
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
})

describe('when a Blob is given', () => {
it('should default to an octet stream', () => {
const res = response()
res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })
assert.strictEqual('application/octet-stream', res.header['content-type'])
})

it('should set length', () => {
const res = response()
res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })
assert.strictEqual('3', res.header['content-length'])
})
})

describe('when a response is given', () => {
it('should set the status', () => {
const res = response()
res.body = new Response(null, { status: 201 })
assert.strictEqual(201, res.status)
})

it('should set headers', () => {
const res = response()
res.body = new Response(null, { status: 200, headers: { 'x-fizz': 'buzz', 'x-foo': 'bar' } })
assert.strictEqual('buzz', res.header['x-fizz'])
assert.strictEqual('bar', res.header['x-foo'])
})

it('should redirect', () => {
const res = response()
res.body = Response.redirect('https://www.example.com/', 301)
assert.strictEqual(301, res.status)
assert.strictEqual('https://www.example.com/', res.header.location)
})
})
})
4 changes: 4 additions & 0 deletions lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,13 @@ function respond (ctx) {
}

// responses

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)

// body: json
body = JSON.stringify(body)
Expand Down
32 changes: 30 additions & 2 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ module.exports = {
/**
* Set response body.
*
* @param {String|Buffer|Object|Stream} val
* @param {String|Buffer|Object|Stream|ReadableStream|Blob|Response} val
* @api public
*/

set body (val) {
const original = this._body
this._body = val

// no content

if (val == null) {
if (!statuses.empty[this.status]) {
if (this.type === 'application/json') {
Expand Down Expand Up @@ -183,6 +183,34 @@ module.exports = {
return
}

// ReadableStream
if (val instanceof ReadableStream) {
if (setType) this.type = 'bin'
return
}

// blob
if (val instanceof Blob) {
if (setType) this.type = 'bin'
this.length = val.size
return
}

// Response
if (val instanceof Response) {
this.status = val.status
if (setType) this.type = 'bin'
const headers = val.headers
for (const key of headers.keys()) {
this.set(key, headers.get(key))
}

if (val.redirected) {
this.redirect(val.url)
}
return
}

// json
this.remove('Content-Length')
this.type = 'json'
Expand Down

0 comments on commit 8d5beb3

Please sign in to comment.