This repository was archived by the owner on Mar 16, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathresponse-body.js
217 lines (173 loc) · 4.99 KB
/
response-body.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
const zlib = require('zlib')
const partial = require('lodash').partial
const helpers = require('../helpers')
module.exports = function responseBody (middleware, filter) {
return function (req, res, next) {
// Apply request filter, if present
if (filter && !helpers.filterRequest(filter, res)) {
return next()
}
// If body is already present, just continue with it
if (res.body) {
return middleware(req, res, next)
}
// If connection was closed, stop with explicit error
if (req.socket.destroyed) {
return next('connection closed')
}
// Cache http.ServerResponse prototype chain for future use
const resProto = Object.getPrototypeOf(res)
var buf = []
var length = 0
var closed = false
var headArgs = null
// Listen for client connection close
req.socket.once('close', onClose)
// Wrap native http.ServerResponse methods
// to intercept data and handle the state
res.writeHead = function (code, headers) {
// Explicitly set status code property
if (typeof code === 'number') {
res.statusCode = code
}
headArgs = helpers.toArray(arguments)
}
res.write = function (data) {
const cb = getCallback(arguments)
if (isEnded()) {
cb && cb()
return true
}
if (data) {
length += Buffer.byteLength(data)
buf.push(data)
}
if (cb) cb()
return true
}
res.end = function (data, encoding) {
const cb = getCallback(arguments)
if (isEnded()) return cb && cb()
if (data && typeof data !== 'function') {
buf.push(data)
}
var body = ''
try { body = Buffer.concat(buf, length) } catch (e) {}
// Expose the current body buffer
res.body = res._originalBody = body
if (isGzip(res, headArgs)) {
res.body = inflate(body)
}
// Expose the original body encoding
if (typeof encoding === 'string') {
res._originalEncoding = encoding
}
// Parse body for convenience
if (isJSON(res)) parseJSON(res)
// Restore native methods
restore()
// Trigger response middleware stack
middleware(req, res, partial(finisher, cb))
}
function isEnded () {
return closed || buf === null
}
function onClose () {
closed = true
cleanupAndRestore()
}
function finisher (cb, err, body, encoding) {
if (isEnded() || res.headersSent) return cb && cb()
if (err) {
res.statusCode = +err.status || 500
return res.end(err.message || err)
}
// Write the final body in the stream
const finalEncoding = encoding || res._originalEncoding
res.body = body == null ? res.body : body
if (isGzip(res, headArgs)) {
res.body = deflate(res.body)
}
// Set content length response header
setContentLength(res)
// Remove content length header if transfer encoding is chuncked.
// See: https://github.com/request/request/issues/2091
if (res.getHeader('transfer-encoding') === 'chunked') {
res.removeHeader('content-length')
}
// Write the response head
if (headArgs && !res.headersSent) {
res.writeHead.apply(res, headArgs)
}
// Write body
resProto.write.call(res, res.body, finalEncoding)
// Clean references to prevent leaks
cleanup()
// Ensure we "uncork" the stream before ending it
res.socket.uncork()
// Send EOF
resProto.end.call(res, cb)
}
function cleanupAndRestore () {
restore()
cleanup()
}
function restore () {
const proto = Object.getPrototypeOf(res)
res.end = proto.end
res.write = proto.write
res.writeHead = proto.writeHead
}
function cleanup () {
buf = length = headArgs = null
req.socket.removeListener('close', onClose)
}
next()
}
}
function setContentLength (res) {
if (typeof res.body === 'string') {
return res.setHeader('content-length', Buffer.byteLength(res.body))
}
if (Buffer.isBuffer(res.body)) {
return res.setHeader('content-length', res.body.length)
}
res.setHeader('content-length', 0)
}
function parseJSON (res) {
if (res.json) return
const body = res.body || ''
try {
res.json = JSON.parse(body.toString())
} catch (e) {
res.parseError = e
}
}
function isGzip (res, head) {
return (res.body && res.getHeader('content-encoding') === 'gzip') ||
(head && head[1] && head[1]['content-encoding'] === 'gzip') ||
false
}
function isJSON (res) {
return /json/i.test(res.getHeader('content-type'))
}
function zlibProxy (fn, body) {
try {
return fn(body)
} catch (e) {
return body
}
}
function inflate (body) {
return zlibProxy(zlib.gunzipSync, body)
}
function deflate (body) {
return zlibProxy(zlib.gzipSync, body)
}
function getCallback (args) {
for (var i = 0, l = args.length; i < l; i += 1) {
if (typeof args[i] === 'function') {
return args[i]
}
}
}