forked from sequoiar/bouncy
/
index.js
143 lines (124 loc) · 4.06 KB
/
index.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
var http = require('http');
var ServerResponse = http.ServerResponse;
var parsley = require('parsley');
var BufferedStream = require('morestreams').BufferedStream;
var insertHeaders = require('./lib/insert_headers');
var updatePath = require('./lib/update_path');
var parseArgs = require('./lib/parse_args');
var net = require('net');
var tls = require('tls');
var bouncy = module.exports = function (opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (opts && opts.key && opts.cert) {
return tls.createServer(opts, handler.bind(null, cb));
} else {
return net.createServer(handler.bind(null, cb));
}
};
var handler = bouncy.handler = function (cb, c) {
parsley(c, function (req) {
c.setMaxListeners(0);
var stream = new BufferedStream;
stream.pause();
function onData(buf) {
stream.write(buf);
}
req.socket.on('close', function () {
stream.end();
});
req.on('rawHead', onData);
req.on('rawBody', onData);
req.on('rawEnd', function () {
req.removeListener('rawHead', onData);
req.removeListener('rawBody', onData);
});
function onHeaders() {
req.removeListener('error', onError);
// don't kill the server on subsequent request errors
req.on('error', function () {});
var bounce = makeBounce(stream, c, req);
cb(req, bounce);
}
req.on('headers', onHeaders);
function onError(err) {
req.removeListener('headers', onHeaders);
var bounce = makeBounce(stream, c, req);
cb(req, bounce);
req.emit('error', err);
}
req.once('error', onError);
});
};
function makeBounce(bs, client, req) {
var bounce = function (stream, opts) {
if (!stream || !stream.write) {
opts = parseArgs(arguments);
stream = opts.stream;
}
var clstream = new BufferedStream;
clstream.pause();
if (!opts) opts = {};
if (opts.forward) {
if (!opts.requestHeaders) opts.requestHeaders = {}
if (!('x-forwarded-for' in opts.requestHeaders)) {
opts.requestHeaders['x-forwarded-for'] = client.remoteAddress;
}
if (!('x-forwarded-port' in opts.requestHeaders)) {
var m = (req.headers.host || '').match(/:(\d+)/);
opts.requestHeaders['x-forwarded-port'] = m && m[1] || 80;
}
if (!('x-forwarded-proto' in opts.requestHeaders)) {
opts.requestHeaders['x-forwarded-proto'] = client.encrypted ? 'https' : 'http';
}
}
if (opts.requestHeaders) {
insertHeaders(bs.chunks, opts.requestHeaders);
}
if (opts.path) {
updatePath(bs.chunks, opts.path);
}
if (stream.writable && client.writable) {
//bs = req, stream = outbound net.createConnection, client = res
//Pipe req to stream
//Pipe stream to res
//Usually only the initial request is prebuffered to inject headers before proxying it
//This section has been modified to pre-buffer the response from the target to inject headers before proxying it back
stream.on('end', function () {
if (opts.responseHeaders) {
insertHeaders(clstream.chunks, opts.responseHeaders);
}
clstream.pipe(client);
});
bs.pipe(stream);
stream.pipe(clstream);
} else if (opts.emitter) {
opts.emitter.emit('drop', client);
}
stream.on('error', function (err) {
if (stream.listeners('error').length === 1) {
// destroy the request and stream if nobody is listening
req.destroy();
stream.destroy();
}
});
return stream;
};
bounce.error = function (msg) {
var res = this.respond()
res.writeHead(400);
res.end(msg);
};
bounce.respond = function () {
var res = new ServerResponse(req);
res.assignSocket(client);
res.on('finish', function () {
res.detachSocket(client);
client.destroySoon();
});
return res;
};
return bounce;
}