-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
131 lines (117 loc) · 4.38 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
var request = require('request');
var zlib = require('zlib');
function template(str, data) {
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
}
module.exports = function(options) {
if (typeof options === 'string') options = {uri: options};
if (!options.uri) throw new Error('Missing proxy "uri" parameter');
var uri = options.uri;
var decompress = options.decompress || 'always';
var subdomains = options.subdomains || 'abc';
var retries = options.retries || 0;
var retryDelayBase = typeof options.retryDelayBase === 'number' ? options.retryDelayBase : 500;
var retryDelayMax = typeof options.retryDelayMax === 'number' ? options.retryDelayMax : 1000 * 60;
var retryDelayMultiple = typeof options.retryDelayMultiple === 'number' ? options.retryDelayMultiple : 2;
var usesSubdomainReplacement = typeof uri === 'string' && uri.indexOf('{s}') > -1;
var headers = decompress === 'always' ? {'Accept-Encoding': 'gzip, deflate'} : {};
for (var k in options.headers) {
if (options.headers.hasOwnProperty(k)) {
headers[k] = options.headers[k];
}
}
return {
name: 'proxy',
serve: function(server, req, callback) {
// use the client's accept-encoding if we want decompression
// behavior to be determined by client capabilities
var finalRequestHeaders = headers;
if (decompress === 'client') {
var finalRequestHeaders = {};
for (var k in headers) {
if (headers.hasOwnProperty(k)) {
finalRequestHeaders[k] = headers[k];
}
}
finalRequestHeaders['Accept-Encoding'] = req.headers['accept-encoding'];
}
var requestURI;
if (typeof uri === 'function') {
requestURI = uri(req);
} else {
requestURI = uri;
if (usesSubdomainReplacement) {
var sudomainIndex = Math.abs(req.x + req.y) % subdomains.length;
var subdomain = subdomains[sudomainIndex];
requestURI = requestURI.replace('{s}', subdomain);
}
requestURI = template(requestURI, req);
}
if (!requestURI) {
var error = new Error('No proxy url defined for this tile');
error.statusCode = 404;
return callback(error);
}
var options = {
headers: finalRequestHeaders,
uri: requestURI,
encoding: null // we want a buffer, not a string
};
var handleError = function(err, requestNumber) {
var attemptsRemaining = retries - (requestNumber - 1);
var delayMs = Math.min(retryDelayMax, retryDelayBase * Math.pow(retryDelayMultiple, requestNumber - 1));
if (attemptsRemaining > 0) {
setTimeout(attemptRequest, delayMs, requestNumber + 1);
} else {
callback(err);
}
};
var attemptRequest = function(requestNumber) {
request(options, function onResponse(err, resp, body) {
if (err) return handleError(err, requestNumber);
// don't accept anything but a 200 OK
if (resp.statusCode !== 200) {
var httpError = new Error('Received non-200 status from upstream (' + resp.statusCode + ')');
httpError.statusCode = resp.statusCode;
return handleError(httpError, requestNumber);
}
// detect content encoding
var contentEncoding = (resp.headers['content-encoding'] || '').toLowerCase();
if (!contentEncoding) {
if (body && body[0] === 0x1F && body[1] === 0x8B) contentEncoding = 'gzip';
if (body && body[0] === 0x78 && body[1] === 0x9C) contentEncoding = 'deflate';
}
// should we attempt decompression?
var shouldDecompress = false;
if (contentEncoding === 'gzip' || contentEncoding === 'deflate') {
shouldDecompress = decompress === 'always';
if (decompress === 'client') {
var clientSupportedEncodings = (req.headers['accept-encoding'] || '').trim().toLowerCase().split(/\s*,\s*/);
shouldDecompress = clientSupportedEncodings.indexOf(contentEncoding) === -1;
}
}
// decompress body
if (shouldDecompress) {
var fn = contentEncoding === 'gzip' ? 'gunzip' : 'inflate';
return zlib[fn](body, function(err, data) {
if (err) return callback(err);
delete resp.headers['content-encoding'];
delete resp.headers['content-length'];
callback(null, data, resp.headers);
});
}
callback(null, body, resp.headers);
});
};
attemptRequest(1);
}
};
};