Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support web WHATWG #1830

Merged
merged 3 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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