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 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 cache[data.map].abort(new Error("illegal zxy."));
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 cache[data.map].abort(new Error("illegal zoom."));
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 cache[data.map].abort(new Error("x is out of bounds."));
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 cache[data.map].abort(new Error("x is out of bounds, bounds span antimeridian"));
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 cache[data.map].abort(new Error("y is out of bounds."));
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 cache[data.map].abort(new Error("illegal extension."));
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 cache[data.map].abort(new Error("illegal density marker."));
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();
@@ -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", data.path.magenta, compressed.length-tile.buffer.length);
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", data.path.magenta, err);
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", data.path.magenta, compressed.length-tile.buffer.length);
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", data.path.magenta, err);
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();
@@ -10,6 +10,7 @@ module.exports = function({ res, opts, data }, next){
10
10
  if (data.tile.buffer.length === 0) {
11
11
  res.statusCode = 204;
12
12
  res.end();
13
+ res.used = true;
13
14
  return next();
14
15
  }
15
16
 
package/builtins/dump.js CHANGED
@@ -12,7 +12,6 @@ module.exports = function({ res, data }, next, skip){
12
12
  res.statusCode = 200;
13
13
  res.setHeader("content-type", "text/plain");
14
14
  res.end(dump);
15
-
16
15
  res.used = true;
17
16
  skip();
18
17
 
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
  }
@@ -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
- debug.info("Fetching %s/%s/%s", data.req.params.z, data.req.params.x, data.req.params.y);
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 next(err);
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 next(err);
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 < cache.expires) deletable.push(file.path);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileblaster",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "a quick and versatile map tile caching proxy",
5
5
  "main": "tileblaster.js",
6
6
  "bin": {
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