-
-
Notifications
You must be signed in to change notification settings - Fork 733
/
create_response.js
88 lines (80 loc) · 2.52 KB
/
create_response.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
'use strict'
const zlib = require('node:zlib')
const { headersArrayToObject } = require('./common')
const { STATUS_CODES } = require('http')
const { pipeline, Readable } = require('node:stream')
/**
* Creates a Fetch API `Response` instance from the given
* `http.IncomingMessage` instance.
* Inspired by: https://github.com/mswjs/interceptors/blob/04152ed914f8041272b6e92ed374216b8177e1b2/src/interceptors/ClientRequest/utils/createResponse.ts#L8
*/
/**
* Response status codes for responses that cannot have body.
* @see https://fetch.spec.whatwg.org/#statuses
*/
const responseStatusCodesWithoutBody = [204, 205, 304]
/**
* @param {import('node:http').IncomingMessage} message
*/
function createResponse(message) {
// 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 => chunks.push(chunk))
message.on('end', () => {
pipeline(
Readable.from(chunks),
...decoders,
async function* (source) {
for await (const chunk of source) {
yield controller.enqueue(chunk)
}
},
error => {
error ? controller.error(error) : controller.close()
},
)
})
/**
* @todo Should also listen to the "error" on the message
* and forward it to the controller. Otherwise the stream
* will pend indefinitely.
*/
},
})
return new Response(responseBodyOrNull, {
status: message.statusCode,
statusText: STATUS_CODES[message.statusCode],
headers: headersArrayToObject(message.rawHeaders),
})
}
module.exports = { createResponse }