tileblaster 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtins/check.js +19 -16
- package/builtins/compress.js +4 -4
- package/builtins/cors.js +4 -1
- package/builtins/deliver.js +1 -0
- package/builtins/dump.js +0 -1
- package/builtins/edit.js +1 -1
- package/builtins/versatiles.js +16 -4
- package/lib/purge.js +1 -1
- package/lib/router.js +1 -1
- package/lib/store.js +1 -1
- package/package.json +1 -1
- package/tileblaster.js +17 -0
package/builtins/check.js
CHANGED
|
@@ -3,21 +3,13 @@
|
|
|
3
3
|
const cache = {};
|
|
4
4
|
|
|
5
5
|
module.exports = function({ req, res, opts, data }, next, skip){
|
|
6
|
+
const debug = this.lib.debug;
|
|
6
7
|
|
|
7
8
|
// fill cache for map
|
|
8
9
|
if (!cache.hasOwnProperty(data.map)) {
|
|
9
10
|
|
|
10
11
|
cache[data.map] = {};
|
|
11
12
|
|
|
12
|
-
// skip function
|
|
13
|
-
cache[data.map].abort = function(err){
|
|
14
|
-
res.statusCode = cache[data.map].status;
|
|
15
|
-
if (cache[data.map].hints && err) res.setHeader("x-tileblaster-hint", err.message || err.toString());
|
|
16
|
-
res.end();
|
|
17
|
-
res.used = true; // mark connection as used
|
|
18
|
-
return skip(); // skip rest of jobs
|
|
19
|
-
};
|
|
20
|
-
|
|
21
13
|
cache[data.map].status = (opts.status && Number.isInteger(opts.status)) ? opts.status : 204;
|
|
22
14
|
|
|
23
15
|
cache[data.map].hints = !!opts.hints;
|
|
@@ -98,29 +90,40 @@ module.exports = function({ req, res, opts, data }, next, skip){
|
|
|
98
90
|
};
|
|
99
91
|
opts = cache[data.map];
|
|
100
92
|
|
|
93
|
+
// skip function
|
|
94
|
+
const abort = function abort (err){
|
|
95
|
+
debug.info("Check: Abort %s/%s/%s/%s: %s", data.map, data.req.params.z, data.req.params.x, data.req.params.y, err.message || err.toString());
|
|
96
|
+
if (res.used) return;
|
|
97
|
+
res.statusCode = cache[data.map].status;
|
|
98
|
+
if (cache[data.map].hints && err) res.setHeader("x-tileblaster-hint", err.message || err.toString());
|
|
99
|
+
res.end();
|
|
100
|
+
res.used = true; // mark connection as used
|
|
101
|
+
return skip(); // skip rest of jobs
|
|
102
|
+
};
|
|
103
|
+
|
|
101
104
|
// check for NaNs
|
|
102
|
-
if (isNaN(data.req.params.z) || isNaN(data.req.params.x) || isNaN(data.req.params.y)) return
|
|
105
|
+
if (isNaN(data.req.params.z) || isNaN(data.req.params.x) || isNaN(data.req.params.y)) return abort(new Error("illegal zxy."));
|
|
103
106
|
|
|
104
107
|
// check zoom
|
|
105
|
-
if (data.req.params.z < opts.minZoom || data.req.params.z > opts.maxZoom) return
|
|
108
|
+
if (data.req.params.z < opts.minZoom || data.req.params.z > opts.maxZoom) return abort(new Error("illegal zoom."));
|
|
106
109
|
|
|
107
110
|
// check bounds
|
|
108
111
|
if (opts.bounds) {
|
|
109
112
|
if (opts.bounds[data.req.params.z][0] < opts.bounds[data.req.params.z][2]) { // check for bounds spanning antimeridian
|
|
110
113
|
// bounds don't span antimeridian
|
|
111
|
-
if (data.req.params.x < opts.bounds[data.req.params.z][0] || data.req.params.x > opts.bounds[data.req.params.z][2]) return
|
|
114
|
+
if (data.req.params.x < opts.bounds[data.req.params.z][0] || data.req.params.x > opts.bounds[data.req.params.z][2]) return abort(new Error("x is out of bounds."));
|
|
112
115
|
} else {
|
|
113
116
|
// bounds span antimeridian
|
|
114
|
-
if (data.req.params.x > opts.bounds[data.req.params.z][0] && data.req.params.x < opts.bounds[data.req.params.z][2]) return
|
|
117
|
+
if (data.req.params.x > opts.bounds[data.req.params.z][0] && data.req.params.x < opts.bounds[data.req.params.z][2]) return abort(new Error("x is out of bounds, bounds span antimeridian"));
|
|
115
118
|
}
|
|
116
|
-
if (data.req.params.y < opts.bounds[data.req.params.z][1] || data.req.params.y > opts.bounds[data.req.params.z][3]) return
|
|
119
|
+
if (data.req.params.y < opts.bounds[data.req.params.z][1] || data.req.params.y > opts.bounds[data.req.params.z][3]) return abort(new Error("y is out of bounds."));
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// check extension
|
|
120
|
-
if (opts.extensions.length > 0 && !opts.extensions.includes(data.req.params.e) && !opts.extensions.includes(data.req.params.f)) return
|
|
123
|
+
if (opts.extensions.length > 0 && !opts.extensions.includes(data.req.params.e) && !opts.extensions.includes(data.req.params.f)) return abort(new Error("illegal extension."));
|
|
121
124
|
|
|
122
125
|
// check density
|
|
123
|
-
if (opts.density && !opts.density.includes(data.req.params.d) && !opts.density.includes(data.req.params.f)) return
|
|
126
|
+
if (opts.density && !opts.density.includes(data.req.params.d) && !opts.density.includes(data.req.params.f)) return abort(new Error("illegal density marker."));
|
|
124
127
|
|
|
125
128
|
// all passed
|
|
126
129
|
next();
|
package/builtins/compress.js
CHANGED
|
@@ -39,7 +39,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
39
39
|
if (opts.brotli) promises.push(new Promise(function(resolve, reject) {
|
|
40
40
|
brotli(tile.buffer, opts.brotli).then(function(compressed){
|
|
41
41
|
if (compressed.length > tile.buffer.length) {
|
|
42
|
-
debug.warn("Discarding useless Brotli compression for %s: +%db",
|
|
42
|
+
debug.warn("Discarding useless Brotli compression for %s: +%db", tile.path.magenta, compressed.length-tile.buffer.length);
|
|
43
43
|
return resolve();
|
|
44
44
|
};
|
|
45
45
|
data.tiles.push({
|
|
@@ -52,7 +52,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
52
52
|
});
|
|
53
53
|
resolve();
|
|
54
54
|
}).catch(function(err){
|
|
55
|
-
debug.error("Brotli failed for %s: %s",
|
|
55
|
+
debug.error("Brotli failed for %s: %s", tile.path.magenta, err);
|
|
56
56
|
reject(err);
|
|
57
57
|
});
|
|
58
58
|
}));
|
|
@@ -60,7 +60,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
60
60
|
if (opts.gzip) promises.push(new Promise(function(resolve, reject) {
|
|
61
61
|
gzip(tile.buffer, opts.gzip).then(function(compressed){
|
|
62
62
|
if (compressed.length > tile.buffer.length) {
|
|
63
|
-
debug.warn("Discarding useless Gzip compression for %s: +%db",
|
|
63
|
+
debug.warn("Discarding useless Gzip compression for %s: +%db", tile.path.magenta, compressed.length-tile.buffer.length);
|
|
64
64
|
return resolve();
|
|
65
65
|
}
|
|
66
66
|
data.tiles.push({
|
|
@@ -73,7 +73,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
73
73
|
});
|
|
74
74
|
resolve();
|
|
75
75
|
}).catch(function(err){
|
|
76
|
-
debug.error("Gzip failed for %s: %s",
|
|
76
|
+
debug.error("Gzip failed for %s: %s", tile.path.magenta, err);
|
|
77
77
|
reject(err);
|
|
78
78
|
});
|
|
79
79
|
}));
|
package/builtins/cors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// send cors headers.
|
|
2
2
|
|
|
3
|
-
module.exports = function({ req, res, opts, data }, next){
|
|
3
|
+
module.exports = function({ req, res, opts, data }, next, skip){
|
|
4
4
|
|
|
5
5
|
// only if origin header is set
|
|
6
6
|
if (!req.headers.hasOwnProperty("origin") || !req.headers.origin) return next();
|
|
@@ -15,6 +15,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
15
15
|
if (!opts.origins.includes("*") && opts.origins.includes(req.headers.origin)) return next();
|
|
16
16
|
|
|
17
17
|
// send common headers
|
|
18
|
+
res.setHeader("Vary", "Origin"); // important for caching
|
|
18
19
|
res.setHeader("Access-Control-Allow-Origin", req.headers.origin||"*");
|
|
19
20
|
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
20
21
|
res.setHeader("Access-Control-Allow-Headers", "DNT,If-None-Match,If-Modified-Since,Cache-Control,Content-Type,Range,Accept-Encoding");
|
|
@@ -27,6 +28,8 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
27
28
|
res.setHeader("Content-Length", 0);
|
|
28
29
|
res.statusCode = 204;
|
|
29
30
|
res.end();
|
|
31
|
+
res.used = true;
|
|
32
|
+
return skip();
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
next();
|
package/builtins/deliver.js
CHANGED
package/builtins/dump.js
CHANGED
package/builtins/edit.js
CHANGED
|
@@ -19,7 +19,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
19
19
|
return tile.type === "pbf";
|
|
20
20
|
}).forEach(function(tile){
|
|
21
21
|
try {
|
|
22
|
-
tile.buffer = vtt.pack(opts.edit(vtt.unpack(tile.buffer)));
|
|
22
|
+
tile.buffer = Buffer.from(vtt.pack(opts.edit(vtt.unpack(tile.buffer))));
|
|
23
23
|
} catch (err) {
|
|
24
24
|
debug.error("Editing vector tile failed:", err);
|
|
25
25
|
}
|
package/builtins/versatiles.js
CHANGED
|
@@ -4,7 +4,7 @@ const versatiles = load("versatiles");
|
|
|
4
4
|
const cache = {};
|
|
5
5
|
|
|
6
6
|
// versatiles backend
|
|
7
|
-
module.exports = function({ req, res, opts, data }, next){
|
|
7
|
+
module.exports = function({ req, res, opts, data }, next, skip){
|
|
8
8
|
const mime = this.lib.mime;
|
|
9
9
|
const debug = this.lib.debug;
|
|
10
10
|
|
|
@@ -16,12 +16,24 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
16
16
|
tms: (!!opts.tms),
|
|
17
17
|
headers: ((opts.hasOwnProperty("headers")) ? opts.headers : {}),
|
|
18
18
|
});
|
|
19
|
+
|
|
19
20
|
};
|
|
20
21
|
const vt = cache[data.map];
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// abort function
|
|
24
|
+
const abort = function abort(err){
|
|
25
|
+
debug.info("Versatiles: Abort %s/%s/%s/%s: %s", data.map, data.req.params.z, data.req.params.x, data.req.params.y, err.message || err.toString());
|
|
26
|
+
if (res.used) return;
|
|
27
|
+
res.statusCode = 204; // no content
|
|
28
|
+
res.end();
|
|
29
|
+
res.used = true; // mark connection as used
|
|
30
|
+
return skip(); // skip rest of jobs
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
debug.info("Versatiles: Fetching %s/%s/%s/%s", data.map, data.req.params.z, data.req.params.x, data.req.params.y);
|
|
23
34
|
vt.getTile(data.req.params.z, data.req.params.x, data.req.params.y, function(err, buf){
|
|
24
|
-
if (err) return
|
|
35
|
+
if (err) return abort(err); // fail gracefully
|
|
36
|
+
if (buf.length === 0) return abort(new Error("Tile does not exist")); // fail gracefully
|
|
25
37
|
|
|
26
38
|
// if precompressed, keep in tile stack
|
|
27
39
|
/* TODO evaluate side effects
|
|
@@ -40,7 +52,7 @@ module.exports = function({ req, res, opts, data }, next){
|
|
|
40
52
|
|
|
41
53
|
// decompress tile
|
|
42
54
|
vt.decompress(vt.header.tile_precompression, buf, function(err, buf){
|
|
43
|
-
if (err) return
|
|
55
|
+
if (err) return abort(err); // fail gracefully
|
|
44
56
|
|
|
45
57
|
const tile = {
|
|
46
58
|
buffer: buf,
|
package/lib/purge.js
CHANGED
|
@@ -87,7 +87,7 @@ if (worker.isMainThread) {
|
|
|
87
87
|
let deletable = [];
|
|
88
88
|
|
|
89
89
|
klaw(cache.dir).on("data", function(file){
|
|
90
|
-
if (file.stats.mtimeMs
|
|
90
|
+
if (file.stats.mtimeMs > cache.expires) deletable.push(file.path);
|
|
91
91
|
}).on("end", function(){
|
|
92
92
|
Promise.allSettled(deletable.map(function(file){
|
|
93
93
|
return new Promise(function(resolve,reject){
|
package/lib/router.js
CHANGED
|
@@ -14,7 +14,7 @@ const router = module.exports = function router({ mountpoint }) {
|
|
|
14
14
|
|
|
15
15
|
// default default route, very bare bones
|
|
16
16
|
self.routes[""] = function(req, res){
|
|
17
|
-
res.statusCode = 404, res.end();
|
|
17
|
+
res.statusCode = 404, res.end(), res.used = true;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
return self;
|
package/lib/store.js
CHANGED
|
@@ -80,7 +80,7 @@ store.prototype.put = function(tile, fn){
|
|
|
80
80
|
const self = this;
|
|
81
81
|
|
|
82
82
|
const destfile = path.join(self.root, tile.path);
|
|
83
|
-
const tmpfile = destfile+self.ext;
|
|
83
|
+
const tmpfile = destfile+"."+Date.now()+self.ext;
|
|
84
84
|
|
|
85
85
|
// check if exists and still valid?
|
|
86
86
|
self.check(destfile, function(err, isValid){
|
package/package.json
CHANGED
package/tileblaster.js
CHANGED
|
@@ -113,9 +113,26 @@ const tileblaster = module.exports = function tileblaster(config){
|
|
|
113
113
|
|
|
114
114
|
self.queue.push(function(next){ // queue tasks
|
|
115
115
|
|
|
116
|
+
args.start = Date.now();
|
|
117
|
+
args.timeout = setTimeout(function(){
|
|
118
|
+
debug.warn("Task timed out: %s (%d total, %d running)", req.path, self.queue.stack.length, self.queue.running);
|
|
119
|
+
if (res.used) return debug.warn("Connection already used.", req.path);
|
|
120
|
+
res.used = true;
|
|
121
|
+
args.timeout = null;
|
|
122
|
+
res.statusCode = 500;
|
|
123
|
+
res.setHeader("content-type", "text/plain");
|
|
124
|
+
res.end("Error.");
|
|
125
|
+
next();
|
|
126
|
+
},30000);
|
|
127
|
+
|
|
116
128
|
// create tasks from map
|
|
117
129
|
tasks(self.maps[req.map]).run(args, function(err, { res }){
|
|
118
130
|
|
|
131
|
+
// check if timed out
|
|
132
|
+
if (args.timeout === null) return;
|
|
133
|
+
clearTimeout(args.timeout);
|
|
134
|
+
debug.info("Task complete: %s (%ds)", req.path, ((Date.now()-args.start)/1000));
|
|
135
|
+
|
|
119
136
|
next(); // free queue
|
|
120
137
|
|
|
121
138
|
// FIXME provide more context
|