-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adjustments to #21 for better compatibility, support for "*@2x.png"-s…
…tyle routes + documentation / more tests
- Loading branch information
1 parent
50cb36c
commit e002a71
Showing
5 changed files
with
176 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,15 @@ Once configured and started, tiles can be accessed via: | |
/:layer/:z/:x:/:y/:filename | ||
``` | ||
|
||
### Routing Without Filenames | ||
|
||
As of [2.1.0](https://github.com/naturalatlas/tilestrata/releases/tag/v2.1.0), if you desire a routing scheme that's closer to other tile servers (where there's no filename) like outlined in [#21](https://github.com/naturalatlas/tilestrata/pull/21), use the following format when registering routes: | ||
|
||
```js | ||
.route('*.png') // /layer/0/0/0.png | ||
.route('*@2x.png') // /layer/0/0/[email protected] | ||
``` | ||
|
||
### Integrate with [Express.js](http://expressjs.com/) / [Connect](https://github.com/senchalabs/connect) | ||
|
||
TileStrata comes with middleware for Express that makes serving tiles from an existing application really simple, eliminating the need to call `listen` on `strata`. | ||
|
@@ -206,7 +215,9 @@ The version of TileStrata (useful to plugins, mainly). | |
|
||
##### layer.route(filename, [options]) | ||
|
||
Registers a route. Returns a [TileRequestHandler](#tilerequesthandler) instance to be configured. Setting the filename to `*.{extension}` will omit the filename from the request and make the tiles available at `/{z}/{x}/{y}.{extension}` (see [#21](https://github.com/naturalatlas/tilestrata/pull/21)). The available options are: | ||
Registers a route and returns a [TileRequestHandler](#tilerequesthandler) instance to be configured. Setting `filename` to something like `"*.ext"` or `"*@2x.ext"` will omit the filename from the request and make the tiles available at `/{z}/{x}/{y}.ext` and `/{z}/{x}/{y}@2x.ext`, respectively (see [#21](https://github.com/naturalatlas/tilestrata/pull/21)). | ||
|
||
The available options are: | ||
|
||
- **cacheFetchMode**: Defines how cache fetching happens when multiple caches are configured. The mode can be `"sequential"` or `"race"`. If set to `"race"`, TileStrata will fetch from all caches simultaneously and return the first that wins. | ||
|
||
|
@@ -217,7 +228,9 @@ Registers a plugin, which is either a provider, cache, transform, request hook, | |
|
||
#### [TileRequest](#tilerequest) | ||
|
||
A request contains these properties: `x`, `y`, `z`, `layer` (string), `filename`, `method`, `headers`, and `qs`. | ||
A request contains these properties: `x`, `y`, `z`, `layer` (string), `filename`, `method`, `headers`, `qs`, and `hasFilename`. | ||
|
||
If a tile request is in the filenameless format ([see here](#routing-without-filenames)), `hasFilename` will be `false`. To illustrate: if the request is to `/layer/0/0/[email protected]`, `filename` will be set to `[email protected]` (for compatibility with caches and plugins that expect a filename) and `hasFilename` will be `false`. | ||
|
||
##### tile.clone() | ||
Returns an identical copy of the tile request that's safe to mutate. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ var TileRequest = module.exports = function(x, y, z, layer, filename, headers, m | |
this.filename = filename; | ||
this.headers = headers || {}; | ||
this.method = method || 'GET'; | ||
this.hasFilename = true; | ||
this.qs = qs; | ||
}; | ||
|
||
|
@@ -21,7 +22,7 @@ TileRequest.parse = function(url, headers, method) { | |
// query string | ||
var qs, index_qs = url.indexOf('?'); | ||
if (index_qs > -1) { | ||
qs = url.substring(index_qs+1); | ||
qs = url.substring(index_qs + 1); | ||
} | ||
|
||
// strip off query string | ||
|
@@ -37,17 +38,37 @@ TileRequest.parse = function(url, headers, method) { | |
var layer = parts[0]; | ||
var z = Number(parts[1]); | ||
var x = Number(parts[2]); | ||
var hasFilename = isNaN(Number(parts[3])) ? false : true | ||
var y = hasFilename ? Number(parts[3]) : Number(parts[3].split('.')[0]); | ||
var filename = hasFilename ? parts[4] : '*.' + parts[3].split('.')[1]; | ||
var hasFilename = isNaN(Number(parts[3])) ? false : true; | ||
|
||
var y, filename; | ||
if (hasFilename) { | ||
y = Number(parts[3]); | ||
filename = parts[4]; | ||
} else { | ||
// if the request is in the format of "/0/0/0.png", we set the filename to | ||
// "t.png" in order to have compatiblity with caches and other plugins that | ||
// expect a proper filename to present (like from the "/0/0/0/t.png" format). | ||
var i0 = parts[3].indexOf('.'); // /0/0/0.png | ||
var i1 = parts[3].indexOf('@'); // /0/0/[email protected] | ||
var splitPoint = i0; | ||
if (i0 > -1 && i1 > -1) { | ||
splitPoint = Math.min(i0, i1); | ||
} else if (i1 > -1) { | ||
splitPoint = i1; | ||
} | ||
y = Number(parts[3].substring(0, splitPoint)); | ||
filename = 't' + parts[3].substring(splitPoint); | ||
} | ||
|
||
if (!isInt(x)) return; | ||
if (!isInt(y)) return; | ||
if (!isInt(z)) return; | ||
if (!filename) return; | ||
if (!layer) return; | ||
|
||
return new TileRequest(x, y, z, layer, filename, headers, method, qs); | ||
var req = new TileRequest(x, y, z, layer, filename, headers, method, qs); | ||
req.hasFilename = hasFilename; | ||
return req; | ||
}; | ||
|
||
TileRequest.prototype.clone = function() { | ||
|
@@ -57,5 +78,7 @@ TileRequest.prototype.clone = function() { | |
headers[k] = this.headers[k]; | ||
} | ||
} | ||
return new TileRequest(this.x, this.y, this.z, this.layer, this.filename, headers, this.method, this.qs); | ||
var req = new TileRequest(this.x, this.y, this.z, this.layer, this.filename, headers, this.method, this.qs); | ||
req.hasFilename = this.hasFilename; | ||
return req; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ describe('TileRequest', function() { | |
assert.instanceOf(result, TileRequest); | ||
assert.equal(result.layer, 'lyr'); | ||
assert.equal(result.filename, '[email protected]'); | ||
assert.equal(result.hasFilename, true); | ||
assert.equal(result.z, 1); | ||
assert.equal(result.x, 2); | ||
assert.equal(result.y, 3); | ||
|
@@ -23,6 +24,7 @@ describe('TileRequest', function() { | |
assert.instanceOf(result, TileRequest); | ||
assert.equal(result.layer, 'lyr'); | ||
assert.equal(result.filename, '[email protected]'); | ||
assert.equal(result.hasFilename, true); | ||
assert.equal(result.z, 1); | ||
assert.equal(result.x, 2); | ||
assert.equal(result.y, 3); | ||
|
@@ -35,6 +37,7 @@ describe('TileRequest', function() { | |
assert.instanceOf(result, TileRequest); | ||
assert.equal(result.layer, 'lyr1'); | ||
assert.equal(result.filename, '[email protected]'); | ||
assert.equal(result.hasFilename, true); | ||
assert.equal(result.z, 1); | ||
assert.equal(result.x, 2); | ||
assert.equal(result.y, 3); | ||
|
@@ -46,7 +49,21 @@ describe('TileRequest', function() { | |
result = TileRequest.parse('lyr1/1/2/3.png', null, null); | ||
assert.instanceOf(result, TileRequest); | ||
assert.equal(result.layer, 'lyr1'); | ||
assert.equal(result.filename, '*.png'); | ||
assert.equal(result.filename, 't.png'); | ||
assert.equal(result.hasFilename, false); | ||
assert.equal(result.z, 1); | ||
assert.equal(result.x, 2); | ||
assert.equal(result.y, 3); | ||
assert.equal(result.method, 'GET'); | ||
assert.equal(result.qs, undefined); | ||
assert.deepEqual(result.headers, {}); | ||
|
||
// no filename (with resolution suffix) | ||
result = TileRequest.parse('lyr1/1/2/[email protected]', null, null); | ||
assert.instanceOf(result, TileRequest); | ||
assert.equal(result.layer, 'lyr1'); | ||
assert.equal(result.filename, '[email protected]'); | ||
assert.equal(result.hasFilename, false); | ||
assert.equal(result.z, 1); | ||
assert.equal(result.x, 2); | ||
assert.equal(result.y, 3); | ||
|
@@ -74,7 +91,7 @@ describe('TileRequest', function() { | |
}); | ||
}); | ||
describe('clone()', function() { | ||
it('should return different, but identical, instance', function() { | ||
it('should return different, but identical, instance (with filename)', function() { | ||
var original = TileRequest.parse('lyr1/1/2/3/[email protected]?query=1&test=2', {'x-tilestrata-skipcache': '1'}, 'GET'); | ||
|
||
var clone = original.clone(); | ||
|
@@ -87,6 +104,25 @@ describe('TileRequest', function() { | |
assert.equal(clone.x, 2); | ||
assert.equal(clone.y, 3); | ||
assert.equal(clone.qs, 'query=1&test=2'); | ||
assert.equal(clone.hasFilename, true); | ||
|
||
assert.notEqual(clone.headers, original.headers); | ||
assert.deepEqual(clone.headers, {'x-tilestrata-skipcache': '1'}); | ||
}); | ||
it('should return different, but identical, instance (without filename)', function() { | ||
var original = TileRequest.parse('lyr1/1/2/[email protected]?query=1&test=2', {'x-tilestrata-skipcache': '1'}, 'GET'); | ||
|
||
var clone = original.clone(); | ||
assert.instanceOf(clone, TileRequest); | ||
assert.notEqual(clone, original); | ||
assert.equal(clone.layer, 'lyr1'); | ||
assert.equal(clone.method, 'GET'); | ||
assert.equal(clone.filename, '[email protected]'); | ||
assert.equal(clone.z, 1); | ||
assert.equal(clone.x, 2); | ||
assert.equal(clone.y, 3); | ||
assert.equal(clone.qs, 'query=1&test=2'); | ||
assert.equal(clone.hasFilename, false); | ||
|
||
assert.notEqual(clone.headers, original.headers); | ||
assert.deepEqual(clone.headers, {'x-tilestrata-skipcache': '1'}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -448,6 +448,56 @@ describe('TileServer', function() { | |
done(); | ||
}); | ||
}); | ||
it('should handle requests without a filename', function(done) { | ||
var server = new TileServer(); | ||
server.layer('layer').route('*.png').use({ | ||
serve: function(_server, _req, callback) { | ||
assert.equal(_server, server); | ||
assert.instanceOf(_req, TileRequest); | ||
assert.equal(_req.filename, 't.png'); | ||
assert.equal(_req.hasFilename, false); | ||
callback(null, new Buffer('response', 'utf8'), {'X-Test': 'hello'}); | ||
} | ||
}); | ||
|
||
var req = TileRequest.parse('/layer/1/2/3.png', {}, 'GET'); | ||
server.serve(req, false, function(status, buffer, headers) { | ||
assert.equal(status, 200); | ||
assert.equal(buffer.toString('utf8'), 'response'); | ||
assert.deepEqual(headers, { | ||
'X-Powered-By': HEADER_XPOWEREDBY, | ||
'X-Test': 'hello', | ||
'Content-Length': 8, | ||
'Cache-Control': HEADER_CACHECONTROL | ||
}); | ||
done(); | ||
}); | ||
}); | ||
it('should handle requests without a filename (with resolution)', function(done) { | ||
var server = new TileServer(); | ||
server.layer('layer').route('*@2x.png').use({ | ||
serve: function(_server, _req, callback) { | ||
assert.equal(_server, server); | ||
assert.instanceOf(_req, TileRequest); | ||
assert.equal(_req.filename, '[email protected]'); | ||
assert.equal(_req.hasFilename, false); | ||
callback(null, new Buffer('response', 'utf8'), {'X-Test': 'hello'}); | ||
} | ||
}); | ||
|
||
var req = TileRequest.parse('/layer/1/2/[email protected]', {}, 'GET'); | ||
server.serve(req, false, function(status, buffer, headers) { | ||
assert.equal(status, 200); | ||
assert.equal(buffer.toString('utf8'), 'response'); | ||
assert.deepEqual(headers, { | ||
'X-Powered-By': HEADER_XPOWEREDBY, | ||
'X-Test': 'hello', | ||
'Content-Length': 8, | ||
'Cache-Control': HEADER_CACHECONTROL | ||
}); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('getTile()', function() { | ||
it('should return error if tile unavailable', function(done) { | ||
|
@@ -499,6 +549,33 @@ describe('TileServer', function() { | |
done(); | ||
}); | ||
}); | ||
it('should return tile if available (no filename)', function(done) { | ||
var _served = false; | ||
var server = new TileServer(); | ||
server.layer('layer').route('*@2x.png').use({ | ||
serve: function(_server, _req, callback) { | ||
_served = true; | ||
assert.equal(_req.x, 2); | ||
assert.equal(_req.y, 3); | ||
assert.equal(_req.z, 1); | ||
callback(null, new Buffer('result', 'utf8'), {'X-Test': 'hello'}); | ||
} | ||
}); | ||
|
||
server.getTile('layer', '*@2x.png', 2, 3, 1, function(err, buffer, headers) { | ||
assert.isTrue(_served); | ||
assert.isNull(err); | ||
assert.instanceOf(buffer, Buffer); | ||
assert.equal(buffer.toString('utf8'), 'result'); | ||
assert.deepEqual(headers, { | ||
'X-Powered-By': HEADER_XPOWEREDBY, | ||
'X-Test': 'hello', | ||
'Content-Length': 6, | ||
'Cache-Control': HEADER_CACHECONTROL | ||
}); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('initialize()', function() { | ||
it('should initialize each layer provider', function(done) { | ||
|