Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
mikicho committed Feb 24, 2024
1 parent f8cc655 commit a95f032
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 30 deletions.
59 changes: 43 additions & 16 deletions lib/create_response.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const zlib = require('node:zlib')
const { headersArrayToObject } = require('./common')
const { STATUS_CODES } = require('http')
const { pipeline, Readable, Writable } = require('node:stream')

/**
* Creates a Fetch API `Response` instance from the given
Expand All @@ -20,29 +21,55 @@ const responseStatusCodesWithoutBody = [204, 205, 304]
* @param {import('node:http').IncomingMessage} message
*/
function createResponse(message) {
const isGzipped = ['x-gzip', 'gzip'].includes(
message.headers['content-encoding'],
)
// https://github.com/Uzlopak/undici/blob/main/lib/fetch/index.js#L2031
const decoders = []
const codings =
message.headers['content-encoding']
?.toLowerCase()
.split(',')
.map(x => x.trim())
.reverse() || []
for (const coding of codings) {
if (coding === 'gzip' || coding === 'x-gzip') {
decoders.push(
zlib.createGunzip({
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH,
}),
)
} else if (coding === 'deflate') {
decoders.push(zlib.createInflate())
} else if (coding === 'br') {
decoders.push(zlib.createBrotliDecompress())
} else {
decoders.length = 0
break
}
}

const chunks = []
const responseBodyOrNull = responseStatusCodesWithoutBody.includes(
message.statusCode,
)
? null
: new ReadableStream({
start(controller) {
message.on('data', chunk => {
if (isGzipped) {
// https://github.com/nodejs/node/blob/0161ad0baf87a0009101ce00b22b874ad6fc5d88/deps/undici/src/lib/fetch/index.js#L2178
controller.enqueue(
zlib.gunzipSync(chunk, {
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH,
}),
)
} else {
controller.enqueue(chunk)
}
message.on('data', chunk => chunks.push(chunk))
message.on('end', () => {
pipeline(
Readable.from(chunks),
...decoders,
async function* (source) {
for await (const chunk of source) {
yield controller.enqueue(chunk)
}
},
e => {
if (e) throw e
controller.close()
},
)
})
message.on('end', () => controller.close())

/**
* @todo Should also listen to the "error" on the message
Expand Down
84 changes: 70 additions & 14 deletions tests/test_fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,77 @@ describe('Native Fetch', () => {
scope.done()
})

it('should accept gzipped content', async () => {
const message = 'Lorem ipsum dolor sit amet'
const compressed = zlib.gzipSync(message)
describe('content-encoding', () => {
it('should accept gzipped content', async () => {
const message = 'Lorem ipsum dolor sit amet'
const compressed = zlib.gzipSync(message)

const scope = nock('http://example.test')
.get('/foo')
.reply(200, compressed, {
'X-Transfer-Length': String(compressed.length),
'Content-Length': undefined,
'Content-Encoding': 'gzip',
})
const response = await fetch('http://example.test/foo')

expect(response.status).to.equal(200)
expect(await response.text()).to.equal(message)
scope.done()
})

const scope = nock('http://example.test')
.get('/foo')
.reply(200, compressed, {
'X-Transfer-Length': String(compressed.length),
'Content-Length': undefined,
'Content-Encoding': 'gzip',
})
const response = await fetch('http://example.test/foo')
it('should accept deflated content', async () => {
const message = 'Lorem ipsum dolor sit amet'
const compressed = zlib.deflateSync(message)

const scope = nock('http://example.test')
.get('/foo')
.reply(200, compressed, {
'X-Transfer-Length': String(compressed.length),
'Content-Length': undefined,
'Content-Encoding': 'deflate',
})
const response = await fetch('http://example.test/foo')

expect(response.status).to.equal(200)
expect(await response.text()).to.equal(message)
scope.done()
})

expect(response.status).to.equal(200)
expect(await response.text()).to.equal(message)
scope.done()
it('should accept brotli content', async () => {
const message = 'Lorem ipsum dolor sit amet'
const compressed = zlib.brotliCompressSync(message)

const scope = nock('http://example.test')
.get('/foo')
.reply(200, compressed, {
'X-Transfer-Length': String(compressed.length),
'Content-Length': undefined,
'Content-Encoding': 'br',
})
const response = await fetch('http://example.test/foo')

expect(response.status).to.equal(200)
expect(await response.text()).to.equal(message)
scope.done()
})

it('should accept gzip and broti content', async () => {
const message = 'Lorem ipsum dolor sit amet'
const compressed = zlib.brotliCompressSync(zlib.gzipSync(message))

const scope = nock('http://example.test')
.get('/foo')
.reply(200, compressed, {
'X-Transfer-Length': String(compressed.length),
'Content-Length': undefined,
'Content-Encoding': 'gzip, br',
})
const response = await fetch('http://example.test/foo')

expect(response.status).to.equal(200)
expect(await response.text()).to.equal(message)
scope.done()
})
})
})

0 comments on commit a95f032

Please sign in to comment.