tileblaster 1.0.5 → 1.0.7

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
@@ -2,13 +2,27 @@
2
2
 
3
3
  const cache = {};
4
4
 
5
- module.exports = function({ req, res, opts, data }, next){
5
+ module.exports = function({ req, res, opts, data }, next, skip){
6
6
 
7
7
  // fill cache for map
8
8
  if (!cache.hasOwnProperty(data.map)) {
9
9
 
10
10
  cache[data.map] = {};
11
11
 
12
+ // skip function
13
+ cache[data.map].abort = function(err){
14
+ if (res.used) return;
15
+ res.statusCode = cache[data.map].status;
16
+ if (cache[data.map].hints && err) res.setHeader("x-tileblaster-hint", err.message || err.toString());
17
+ res.end();
18
+ res.used = true; // mark connection as used
19
+ return skip(); // skip rest of jobs
20
+ };
21
+
22
+ cache[data.map].status = (opts.status && Number.isInteger(opts.status)) ? opts.status : 204;
23
+
24
+ cache[data.map].hints = !!opts.hints;
25
+
12
26
  // assume reasonable default if no zoom level was specified
13
27
  cache[data.map].zoom = (!opts.zoom || opts.zoom.length === 0) ? [ 0, 24 ] : opts.zoom;
14
28
 
@@ -86,28 +100,28 @@ module.exports = function({ req, res, opts, data }, next){
86
100
  opts = cache[data.map];
87
101
 
88
102
  // check for NaNs
89
- if (isNaN(data.req.params.z) || isNaN(data.req.params.x) || isNaN(data.req.params.y)) return next(new Error("illegal zxy."));
103
+ 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."));
90
104
 
91
105
  // check zoom
92
- if (data.req.params.z < opts.minZoom || data.req.params.z > opts.maxZoom) return next(new Error("illegal zoom."));
106
+ if (data.req.params.z < opts.minZoom || data.req.params.z > opts.maxZoom) return cache[data.map].abort(new Error("illegal zoom."));
93
107
 
94
108
  // check bounds
95
109
  if (opts.bounds) {
96
110
  if (opts.bounds[data.req.params.z][0] < opts.bounds[data.req.params.z][2]) { // check for bounds spanning antimeridian
97
111
  // bounds don't span antimeridian
98
- if (data.req.params.x < opts.bounds[data.req.params.z][0] || data.req.params.x > opts.bounds[data.req.params.z][2]) return next(new Error("x is out of bounds."));
112
+ 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."));
99
113
  } else {
100
114
  // bounds span antimeridian
101
- if (data.req.params.x > opts.bounds[data.req.params.z][0] && data.req.params.x < opts.bounds[data.req.params.z][2]) return next(new Error("x is out of bounds, bounds span antimeridian"));
115
+ 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"));
102
116
  }
103
- if (data.req.params.y < opts.bounds[data.req.params.z][1] || data.req.params.y > opts.bounds[data.req.params.z][3]) return next(new Error("y is out of bounds."));
117
+ 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."));
104
118
  }
105
119
 
106
120
  // check extension
107
- if (opts.extensions.length > 0 && !opts.extensions.includes(data.req.params.e) && !opts.extensions.includes(data.req.params.f)) return next(new Error("illegal extension."));
121
+ 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."));
108
122
 
109
123
  // check density
110
- if (opts.density && !opts.density.includes(data.req.params.d) && !opts.density.includes(data.req.params.f)) return next(new Error("illegal density marker."));
124
+ 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."));
111
125
 
112
126
  // all passed
113
127
  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,23 @@ module.exports = function({ req, res, opts, data }, next){
16
16
  tms: (!!opts.tms),
17
17
  headers: ((opts.hasOwnProperty("headers")) ? opts.headers : {}),
18
18
  });
19
+
20
+ // skip function
21
+ cache[data.map].abort = function(err){
22
+ if (res.used) return;
23
+ res.statusCode = 204; // no content
24
+ res.setHeader("x-tileblaster-hint", err.message || err.toString());
25
+ res.end();
26
+ res.used = true; // mark connection as used
27
+ return skip(); // skip rest of jobs
28
+ };
29
+
19
30
  };
20
31
  const vt = cache[data.map];
21
32
 
22
33
  debug.info("Fetching %s/%s/%s", 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 cache[data.map].abort(err); // fail gracefully
25
36
 
26
37
  // if precompressed, keep in tile stack
27
38
  /* TODO evaluate side effects
@@ -40,7 +51,7 @@ module.exports = function({ req, res, opts, data }, next){
40
51
 
41
52
  // decompress tile
42
53
  vt.decompress(vt.header.tile_precompression, buf, function(err, buf){
43
- if (err) return next(err);
54
+ if (err) return cache[data.map].abort(err); // fail gracefully
44
55
 
45
56
  const tile = {
46
57
  buffer: buf,
package/config.dist.js CHANGED
@@ -68,7 +68,9 @@ const config = module.exports = {
68
68
  density: [ "", "@2x", "@3x" ], // allowed density markeers
69
69
  check: function(params, fn) { // override check function, params from parse
70
70
  fn(new Error("Check failed")); // deliver error if check failed
71
- }
71
+ },
72
+ status: 204,
73
+ hints: true,
72
74
  },{ // get from cache, skip to `skipto` if successful
73
75
  builtin: "cache",
74
76
  skipto: "deliver",
package/docs/config.md CHANGED
@@ -151,7 +151,9 @@ within a bounding box, to specific extensions and densities, or use your own che
151
151
  density: [ "", "@2x", "@3x" ], // allowed density markeers
152
152
  check: function(params, fn) { // override check function, params from parse
153
153
  fn(new Error("Check failed")); // deliver error if check failed
154
- }
154
+ },
155
+ status: 204, // http status code delivered on fail; default: 204
156
+ hints: false, // send `x-tileblaster-hint` header with error message
155
157
  }
156
158
  ```
157
159
 
package/docs/todo.md CHANGED
@@ -70,9 +70,9 @@
70
70
  * [ ] Map Web Interface, Configurable
71
71
  * [ ] Configurable Index Page
72
72
  * [ ] Support for Glyphs and Styles, tile.json proxy
73
- * [ ] Better Error Handling
74
- * [ ] Error wrapper (to pass along http status etc)?
75
- * [ ] 404 for nonexistant tiles
73
+ * [x] Better Error Handling
74
+ * [x] Error wrapper (to pass along http status etc)?
75
+ * [x] Skip for nonexistant tiles
76
76
  * [ ] Render Vector Tiles with MapLibreGL-Native
77
77
  * [ ] Improve Debug Consistency
78
78
  * [ ] Documentation: How to write a plugin
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.5",
3
+ "version": "1.0.7",
4
4
  "description": "a quick and versatile map tile caching proxy",
5
5
  "main": "tileblaster.js",
6
6
  "bin": {