Skip to content

Commit 8d5beb3

Browse files
authored
feat: add support web WHATWG (#1830)
closes #1777
1 parent 757e4eb commit 8d5beb3

File tree

4 files changed

+165
-2
lines changed

4 files changed

+165
-2
lines changed

__tests__/application/respond.js

+87
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,93 @@ describe('app.respond', () => {
570570
})
571571
})
572572

573+
describe('when .body is a Blob', () => {
574+
it('should respond', async () => {
575+
const app = new Koa()
576+
577+
app.use(ctx => {
578+
ctx.body = new Blob(['Hello'])
579+
})
580+
581+
const expectedBlob = new Blob(['Hello'])
582+
583+
const server = app.listen()
584+
585+
const res = await request(server)
586+
.get('/')
587+
.expect(200)
588+
589+
assert.deepStrictEqual(res.body, Buffer.from(await expectedBlob.arrayBuffer()))
590+
})
591+
592+
it('should keep Blob headers', async () => {
593+
const app = new Koa()
594+
595+
app.use(ctx => {
596+
ctx.body = new Blob(['hello world'])
597+
})
598+
599+
const server = app.listen()
600+
601+
return request(server)
602+
.head('/')
603+
.expect(200)
604+
.expect('content-type', 'application/octet-stream')
605+
.expect('content-length', '11')
606+
})
607+
})
608+
609+
describe('when .body is a ReadableStream', () => {
610+
it('should respond', async () => {
611+
const app = new Koa()
612+
613+
app.use(async ctx => {
614+
ctx.body = new ReadableStream()
615+
})
616+
617+
const server = app.listen()
618+
619+
return request(server)
620+
.head('/')
621+
.expect(200)
622+
.expect('content-type', 'application/octet-stream')
623+
})
624+
})
625+
626+
describe('when .body is a Response', () => {
627+
it('should keep Response headers', () => {
628+
const app = new Koa()
629+
630+
app.use(ctx => {
631+
ctx.body = new Response(null, { status: 201, statusText: 'OK', headers: { 'Content-Type': 'text/plain' } })
632+
})
633+
634+
const server = app.listen()
635+
636+
return request(server)
637+
.head('/')
638+
.expect(201)
639+
.expect('content-type', 'text/plain')
640+
.expect('content-length', '2')
641+
})
642+
643+
it('should default to octet-stream', () => {
644+
const app = new Koa()
645+
646+
app.use(ctx => {
647+
ctx.body = new Response(null, { status: 200, statusText: 'OK' })
648+
})
649+
650+
const server = app.listen()
651+
652+
return request(server)
653+
.head('/')
654+
.expect(200)
655+
.expect('content-type', 'application/octet-stream')
656+
.expect('content-length', '2')
657+
})
658+
})
659+
573660
describe('when .body is a Stream', () => {
574661
it('should respond', async () => {
575662
const app = new Koa()

__tests__/response/body.js

+44
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,48 @@ describe('res.body=', () => {
141141
assert.strictEqual('application/json; charset=utf-8', res.header['content-type'])
142142
})
143143
})
144+
145+
describe('when a ReadableStream is given', () => {
146+
it('should default to an octet stream', () => {
147+
const res = response()
148+
res.body = new ReadableStream()
149+
assert.strictEqual('application/octet-stream', res.header['content-type'])
150+
})
151+
})
152+
153+
describe('when a Blob is given', () => {
154+
it('should default to an octet stream', () => {
155+
const res = response()
156+
res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })
157+
assert.strictEqual('application/octet-stream', res.header['content-type'])
158+
})
159+
160+
it('should set length', () => {
161+
const res = response()
162+
res.body = new Blob([new Uint8Array([1, 2, 3])], { type: 'application/octet-stream' })
163+
assert.strictEqual('3', res.header['content-length'])
164+
})
165+
})
166+
167+
describe('when a response is given', () => {
168+
it('should set the status', () => {
169+
const res = response()
170+
res.body = new Response(null, { status: 201 })
171+
assert.strictEqual(201, res.status)
172+
})
173+
174+
it('should set headers', () => {
175+
const res = response()
176+
res.body = new Response(null, { status: 200, headers: { 'x-fizz': 'buzz', 'x-foo': 'bar' } })
177+
assert.strictEqual('buzz', res.header['x-fizz'])
178+
assert.strictEqual('bar', res.header['x-foo'])
179+
})
180+
181+
it('should redirect', () => {
182+
const res = response()
183+
res.body = Response.redirect('https://www.example.com/', 301)
184+
assert.strictEqual(301, res.status)
185+
assert.strictEqual('https://www.example.com/', res.header.location)
186+
})
187+
})
144188
})

lib/application.js

+4
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,13 @@ function respond (ctx) {
300300
}
301301

302302
// responses
303+
303304
if (Buffer.isBuffer(body)) return res.end(body)
304305
if (typeof body === 'string') return res.end(body)
305306
if (body instanceof Stream) return body.pipe(res)
307+
if (body instanceof Blob) return Stream.Readable.from(body.stream()).pipe(res)
308+
if (body instanceof ReadableStream) return Stream.Readable.from(body).pipe(res)
309+
if (body instanceof Response) return Stream.Readable.from(body?.body).pipe(res)
306310

307311
// body: json
308312
body = JSON.stringify(body)

lib/response.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ module.exports = {
126126
/**
127127
* Set response body.
128128
*
129-
* @param {String|Buffer|Object|Stream} val
129+
* @param {String|Buffer|Object|Stream|ReadableStream|Blob|Response} val
130130
* @api public
131131
*/
132132

133133
set body (val) {
134134
const original = this._body
135135
this._body = val
136-
137136
// no content
137+
138138
if (val == null) {
139139
if (!statuses.empty[this.status]) {
140140
if (this.type === 'application/json') {
@@ -183,6 +183,34 @@ module.exports = {
183183
return
184184
}
185185

186+
// ReadableStream
187+
if (val instanceof ReadableStream) {
188+
if (setType) this.type = 'bin'
189+
return
190+
}
191+
192+
// blob
193+
if (val instanceof Blob) {
194+
if (setType) this.type = 'bin'
195+
this.length = val.size
196+
return
197+
}
198+
199+
// Response
200+
if (val instanceof Response) {
201+
this.status = val.status
202+
if (setType) this.type = 'bin'
203+
const headers = val.headers
204+
for (const key of headers.keys()) {
205+
this.set(key, headers.get(key))
206+
}
207+
208+
if (val.redirected) {
209+
this.redirect(val.url)
210+
}
211+
return
212+
}
213+
186214
// json
187215
this.remove('Content-Length')
188216
this.type = 'json'

0 commit comments

Comments
 (0)