UNPKG

3.53 kBJavaScriptView Raw
1'use strict'
2
3const zlib = require('zlib')
4const { pipeline } = require('stream')
5
6const decoderFactories = {
7 gzip: zlib.createGunzip,
8 deflate: zlib.createInflate,
9 default: encoding => { throw new Error(`Unsupported encoding: ${encoding}`) }
10}
11
12/* istanbul ignore else */
13if (zlib.createBrotliDecompress) {
14 decoderFactories.br = zlib.createBrotliDecompress
15}
16
17function defer () {
18 let done
19 let fail
20 const promise = new Promise((resolve, reject) => {
21 done = resolve
22 fail = reject
23 })
24 return { done, fail, promise }
25}
26
27function selectDecoder (headers) {
28 const encoding = headers['content-encoding']
29 if (encoding && encoding !== 'identity') {
30 const factory = decoderFactories[encoding] || decoderFactories.default
31 return factory(encoding)
32 }
33}
34
35function _getParameters (forEnd, args) {
36 let [data, encoding, callback = () => {}] = args
37 if (forEnd && typeof data === 'function') {
38 callback = data
39 data = undefined
40 encoding = undefined
41 } else if (typeof encoding === 'function') {
42 callback = encoding
43 encoding = undefined
44 }
45 return { data, encoding, callback }
46}
47
48const writeParameters = _getParameters.bind(null, false)
49const endParameters = _getParameters.bind(null, true)
50
51function capture (response, headers, writableStream) {
52 const { done, fail, promise } = defer()
53 const { emit, end, write } = response
54
55 function release () {
56 response.write = write
57 response.end = end
58 }
59
60 function onError (error) {
61 fail(error)
62 release()
63 }
64
65 try {
66 const out = selectDecoder(headers) || writableStream
67
68 out.on('error', onError)
69 if (writableStream !== out) {
70 writableStream.on('error', onError)
71 }
72 response.on('error', onError)
73
74 writableStream.on('finish', () => done())
75
76 response.write = function () {
77 const { data, encoding, callback } = writeParameters(arguments)
78 let flushCount = 0
79 function flush () {
80 if (--flushCount === 0) {
81 response.emit = emit
82 response.emit('drain')
83 }
84 }
85 function needDrain (writeResult) {
86 if (!writeResult) {
87 if (++flushCount === 1) {
88 response.emit = function (eventName) {
89 if (eventName !== 'drain') {
90 return emit.apply(response, arguments)
91 }
92 }
93 }
94 }
95 }
96 needDrain(out.write(data, encoding, flush))
97 needDrain(write.call(response, data, encoding, function () {
98 callback.apply(this, arguments)
99 flush()
100 }))
101 return flushCount === 0
102 }
103
104 response.end = function () {
105 const { data, encoding, callback } = endParameters(arguments)
106 function endWritableStream () {
107 if (out !== writableStream) {
108 pipeline(out, writableStream, () => {
109 writableStream.end()
110 })
111 out.end()
112 } else {
113 writableStream.end()
114 }
115 }
116 end.call(response, data, encoding, function () {
117 endWritableStream()
118 callback.apply(this, arguments)
119 })
120 return this
121 }
122 } catch (e) {
123 fail(e)
124 }
125 return promise
126}
127
128module.exports = (response, writableStream) => {
129 const { done, fail, promise } = defer()
130 const { writeHead } = response
131 response.writeHead = function (status, headers = {}) {
132 if (status === 200) {
133 done(capture(response, headers, writableStream))
134 } else {
135 fail(new Error('Invalid status'))
136 }
137 writeHead.apply(response, arguments)
138 }
139 return promise
140}