Skip to content

Commit

Permalink
Updated dependencies + added a skipErrors option
Browse files Browse the repository at this point in the history
  • Loading branch information
brianreavis committed Oct 3, 2020
1 parent a3362ec commit 0c3d077
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 96 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ app.use(tilestrata.middleware({

## Usage Notes

### Fault-Tolerant Initialization

By default, TileStrata will error when initializing if any of the layer handlers fail to initialize. If you would like to ignore errors so that _other_ layers are booted up and available, use the `skipErrors` option:

```js
var strata = tilestrata({ skipErrors: true });
```

### Metatile-Aware Load Balancing & Layer Sharding

TileStrata >= [2.0.0](https://github.com/naturalatlas/tilestrata/releases/tag/v2.0.0) supports integration with [TileStrata Balancer](https://github.com/naturalatlas/tilestrata-balancer), an elastic load balancer designed specifically for the nuances of tile serving – particularly [metatiles](http://wiki.openstreetmap.org/wiki/Meta_tiles). Generic load balancers have no knowledge of metatiles and thus will naively split tile requests out to multiple servers which leads to redundant rendering (slow and a waste of computing power).
Expand Down Expand Up @@ -166,16 +174,16 @@ TileStrata includes a `/health` endpoint that will return a `200 OK` if it can a
```js
// not healthy
var strata = tilestrata({
healthy: function(callback) {
callback(new Error('CPU is too high'), {loadavg: 3});
}
healthy: function(callback) {
callback(new Error('CPU is too high'), {loadavg: 3});
}
});

// healthy
var strata = tilestrata({
healthy: function(callback) {
callback(null, {loadavg: 1});
}
healthy: function(callback) {
callback(null, {loadavg: 1});
}
});
```

Expand Down Expand Up @@ -375,7 +383,7 @@ $ npm test

## License

Copyright © 2014–2016 [Natural Atlas, Inc.](https://github.com/naturalatlas) & [Contributors](https://github.com/naturalatlas/tilestrata/graphs/contributors)
Copyright © 2014–2020 [Natural Atlas, Inc.](https://github.com/naturalatlas) & [Contributors](https://github.com/naturalatlas/tilestrata/graphs/contributors)

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0

Expand Down
15 changes: 11 additions & 4 deletions lib/TileRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var BUFFER_NOPROVIDER = Buffer.from('No provider configured for layer', 'utf8');

var TileRequestHandler = module.exports = function(options) {
options = options || {};
this.initialized = false;
this.provider = null;
this.caches = [];
this.transforms = [];
Expand Down Expand Up @@ -81,8 +82,8 @@ TileRequestHandler.prototype._invokeLifecycleMethod = function(server, plugin_me
return function(err) {
if (err && skip_errors) {
last_error = err;
log.warn('plugin', 'Failed to close: "' + err.message + '"');
return callback(err);
log.warn('plugin', 'Failed to "' + plugin_method + '": "' + err.message + '"');
return callback();
}
return callback(err);
};
Expand Down Expand Up @@ -123,17 +124,23 @@ TileRequestHandler.prototype._invokeLifecycleMethod = function(server, plugin_me
}
], function(err) {
setImmediate(function() {
callback(err || last_error || null);
var finalError = skip_errors ? null : err || last_error;
callback(finalError || null);
});
});
};

TileRequestHandler.prototype._destroy = function(server, callback) {
this.initialized = false;
this._invokeLifecycleMethod(server, 'destroy', true, callback);
};

TileRequestHandler.prototype._initialize = function(server, callback) {
this._invokeLifecycleMethod(server, 'init', false, callback);
var self = this;
this._invokeLifecycleMethod(server, 'init', false, function(err) {
if (!err) self.initialized = true;
callback(err);
});
};

/**
Expand Down
174 changes: 96 additions & 78 deletions lib/TileServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var PROFILING_ENABLED = !process.env.TILESTRATA_NOPROFILE;
var HEADER_XPOWEREDBY = 'TileStrata/' + version;
var BUFFER_NOTFOUND = Buffer.from('Not found', 'utf8');
var BUFFER_NOTIMPLEMENTED = Buffer.from('Not implemented', 'utf8');
var BUFFER_NOTINITIALIZED = Buffer.from('Not initialized', 'utf8');

log.heading = 'tilestrata';
log.prefixStyle = {fg: 'grey'}
Expand Down Expand Up @@ -99,10 +100,20 @@ TileServer.prototype.initialize = function(callback) {
self.initialize = async.memoize(TileServer.prototype.initialize.bind(self));
if (callback) callback(err);
};

async.each(_.values(this.layers), function(layer, callback) {
async.each(_.values(layer.routes), function(route, callback) {
route.handler._initialize(self, callback);
var verbose = self.options.verbose;
var skipFailures = self.options.skipFailures;
async.eachSeries(_.values(this.layers), function(layer, callback) {
async.eachSeries(_.values(layer.routes), function(route, callback) {
route.handler._initialize(self, function(err) {
var contextString = layer.name + ' ' + route.filename;
if (err) {
var level = skipFailures ? 'warn' : 'error';
log[level](contextString + ' init failed', err.stack);
} else if (verbose) {
log.info(contextString + ' initialized');
}
callback(skipFailures ? null : err);
});
}, function(err) {
if (err) err = new Error('Unable to initialize "' + layer.name + '" layer: "' + (err.message || err) + '"');
callback(err);
Expand Down Expand Up @@ -183,91 +194,98 @@ TileServer.prototype.serve = function(req, http, callback) {
var self = this;
if (!req) return callback(404, BUFFER_NOTFOUND, {});
var _method = req.method;

if (req.method === 'HEAD') {
_method = 'GET';
}

var handler = this._getTileHandler(req);
if (!handler) {
return callback(404, BUFFER_NOTFOUND, {
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': BUFFER_NOTFOUND.length
});
} else if (_method !== _method.toUpperCase() || !handler[_method]) {
return callback(501, BUFFER_NOTIMPLEMENTED, {
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': BUFFER_NOTIMPLEMENTED.length
});
}
this.initialize(function(err) {
if (err) return callback(err);
var handler = self._getTileHandler(req);

var result = {};
if (!handler) {
return callback(404, BUFFER_NOTFOUND, {
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': BUFFER_NOTFOUND.length
});
} else if (_method !== _method.toUpperCase() || !handler[_method]) {
return callback(501, BUFFER_NOTIMPLEMENTED, {
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': BUFFER_NOTIMPLEMENTED.length
});
} else if (!handler.initialized) {
return callback(500, BUFFER_NOTINITIALIZED, {
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': BUFFER_NOTINITIALIZED.length
});
}

async.series([
function invokeRequestHooks(callback) {
if (!http || !handler.requestHooks.length) return callback();
async.eachSeries(handler.requestHooks, function(hook, callback) {
var __profile = self.profile(hook.id, req);
hook.plugin.reqhook(self, req, http.req, http.res, function(err) {
__profile(err);
callback(err);
var result = {};
async.series([
function invokeRequestHooks(callback) {
if (!http || !handler.requestHooks.length) return callback();
async.eachSeries(handler.requestHooks, function(hook, callback) {
var __profile = self.profile(hook.id, req);
hook.plugin.reqhook(self, req, http.req, http.res, function(err) {
__profile(err);
callback(err);
});
}, callback);
},
function handleRequest(callback) {
handler[_method](self, req, function(status, buffer, headers) {
headers = headers || {};
var headerKeysBefore = Object.keys(headers);
headers['X-Powered-By'] = HEADER_XPOWEREDBY;
if (status === 200) {
headers['Cache-Control'] = 'max-age=60';
}
normalizeHeaders(headers, headerKeysBefore);

result = {status: status, buffer: buffer, headers: headers};
callback();
});
}, callback);
},
function handleRequest(callback) {
handler[_method](self, req, function(status, buffer, headers) {
headers = headers || {};
var headerKeysBefore = Object.keys(headers);
headers['X-Powered-By'] = HEADER_XPOWEREDBY;
if (status === 200) {
headers['Cache-Control'] = 'max-age=60';
},
function invokeResponseHooks(callback) {
if (!http || !handler.responseHooks.length) return callback();
var headerKeysBefore = Object.keys(result.headers);
async.eachSeries(handler.responseHooks, function(hook, callback) {
var __profile = self.profile(hook.id, req);
hook.plugin.reshook(self, req, http.req, http.res, result, function(err) {
normalizeHeaders(result.headers, headerKeysBefore);
__profile(err);
callback(err);
});
}, callback);
},
function finalizeResult(callback) {
if (!result.headers) result.headers = {};
var headerKeysBefore = Object.keys(result.headers);
if (result.status !== 204) {
result.headers['Content-Length'] = result.buffer ? result.buffer.length : 0;
}
normalizeHeaders(headers, headerKeysBefore);
normalizeHeaders(result.headers, headerKeysBefore);

// head request support
if (req.method === 'HEAD') result.buffer = Buffer.from('', 'utf8');

result = {status: status, buffer: buffer, headers: headers};
callback();
});
},
function invokeResponseHooks(callback) {
if (!http || !handler.responseHooks.length) return callback();
var headerKeysBefore = Object.keys(result.headers);
async.eachSeries(handler.responseHooks, function(hook, callback) {
var __profile = self.profile(hook.id, req);
hook.plugin.reshook(self, req, http.req, http.res, result, function(err) {
normalizeHeaders(result.headers, headerKeysBefore);
__profile(err);
callback(err);
}
], function(err) {
if (err) {
var buffer = Buffer.from(String(err.message || err), 'utf8');
log.error(null, 'Failed to serve ' + req.filename + ' for "' + req.layer + '" layer {x:' + req.x + ',y:' + req.y + ',z:' + req.z + '}');
log.error(null, err.stack || err);
callback(500, buffer, {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': buffer.length
});
}, callback);
},
function finalizeResult(callback) {
if (!result.headers) result.headers = {};
var headerKeysBefore = Object.keys(result.headers);
if (result.status !== 204) {
result.headers['Content-Length'] = result.buffer ? result.buffer.length : 0;
} else {
callback(result.status, result.buffer, result.headers);
}
normalizeHeaders(result.headers, headerKeysBefore);

// head request support
if (req.method === 'HEAD') result.buffer = Buffer.from('', 'utf8');

callback();
}
], function(err) {
if (err) {
var buffer = Buffer.from(String(err.message || err), 'utf8');
log.error(null, 'Failed to serve ' + req.filename + ' for "' + req.layer + '" layer {x:' + req.x + ',y:' + req.y + ',z:' + req.z + '}');
log.error(null, err);
callback(500, buffer, {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'X-Powered-By': HEADER_XPOWEREDBY,
'Content-Length': buffer.length
});
} else {
callback(result.status, result.buffer, result.headers);
}
});
});
};

Expand Down Expand Up @@ -483,7 +501,7 @@ TileServer.prototype.bindBalancer = function() {
self.balancer_token = body.token;
self.balancer_check_interval = body.check_interval;
self.handleBalancerPing();
recovery.reconnected();
recovery.reconnected();
});
});

Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@
},
"homepage": "https://github.com/naturalatlas/tilestrata",
"dependencies": {
"async": "~1.5.2",
"async": "~3.2.0",
"basic-auth": "~2.0.1",
"bbox-intersect": "~0.1.2",
"filesize": "~4.1.2",
"http-status": "~1.3.2",
"humanize-duration": "~3.18.0",
"lodash": "~4.17.11",
"chalk": "^2.4.2",
"filesize": "~6.1.0",
"http-status": "~1.4.2",
"humanize-duration": "~3.24.0",
"lodash": "~4.17.20",
"npmlog": "~4.1.2",
"recovery": "~0.2.6",
"request": "~2.88.0",
"request": "~2.88.2",
"tilebelt": "~1.0.1",
"uuid": "~3.3.2"
},
Expand Down
Loading

0 comments on commit 0c3d077

Please sign in to comment.