Skip to content

Commit

Permalink
Merge branch 'master' into fix-readme-typo-0
Browse files Browse the repository at this point in the history
  • Loading branch information
etroynov authored Jun 28, 2024
2 parents a2f8b40 + cfdb77f commit dd0d9ed
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 12 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 docs/api/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,7 @@ ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');
### response.flushHeaders()

Flush any set headers, and begin the body.

### response.writable

A boolean that indicates whether the response is still writable
6 changes: 5 additions & 1 deletion lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Emitter = require('events')
const util = require('util')
const Stream = require('stream')
const http = require('http')
const only = require('only')
const only = require('./only.js')
const { HttpError } = require('http-errors')

/** @typedef {typeof import ('./context') & {
Expand Down 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
11 changes: 11 additions & 0 deletions lib/only.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = (obj, keys) => {
obj = obj || {}
if (typeof keys === 'string') keys = keys.split(/ +/)
const ret = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (obj[key] == null) continue
ret[key] = obj[key]
}
return ret
}
2 changes: 1 addition & 1 deletion lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const parse = require('parseurl')
const qs = require('querystring')
const typeis = require('type-is')
const fresh = require('fresh')
const only = require('only')
const only = require('./only.js')
const util = require('util')

const IP = Symbol('context#ip')
Expand Down
34 changes: 31 additions & 3 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const destroy = require('destroy')
const assert = require('assert')
const extname = require('path').extname
const vary = require('vary')
const only = require('only')
const only = require('./only.js')
const util = require('util')
const encodeUrl = require('encodeurl')
const Stream = require('stream')
Expand Down 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
6 changes: 0 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"http-errors": "^1.6.3",
"koa-compose": "^4.1.0",
"on-finished": "^2.3.0",
"only": "~0.0.2",
"parseurl": "^1.3.2",
"statuses": "^1.5.0",
"type-is": "^1.6.16",
Expand Down

0 comments on commit dd0d9ed

Please sign in to comment.