Compare commits

...

57 Commits

Author SHA1 Message Date
Sandro Santilli
1bf7bf66b3 Release 1.10.1 2014-03-21 15:16:19 +01:00
Sandro Santilli
9e495b42ee Do not cache non-success jsonp responses
Closes #186
Includes testcase
2014-03-21 13:58:20 +01:00
Sandro Santilli
898f717254 Prepare for 1.10.1 2014-03-20 17:10:39 +01:00
Sandro Santilli
800ef32959 Release 1.10.0 2014-03-20 17:08:35 +01:00
Sandro Santilli
609d69c4c9 Upgrade of windshaft fixed connection details to client
Closes #183.
2014-03-20 10:21:28 +01:00
Sandro Santilli
9e1be39774 Switch to 3-clause BSD license
Closes #184
2014-03-20 10:20:51 +01:00
Sandro Santilli
87ac44a1f1 Upgrade windshaft to 0.20.0
Reduces noise on the "error" channel (now optionally writing to rollbar)
and avoids caching bogus mapnik renderers.
Details: http://github.com/CartoDB/Windshaft/blob/0.20.0/NEWS
2014-03-20 10:18:33 +01:00
Sandro Santilli
9c4feac19b Ensure make check fails if database preparation fails 2014-03-19 17:04:06 +01:00
Sandro Santilli
471edabe4d Reword uncaught exception error, and log full stack 2014-03-13 11:58:29 +01:00
Sandro Santilli
86841f80ca Use version of node-mapnik with temptative fix for glibc detected corruptions 2014-03-13 10:26:11 +01:00
Sandro Santilli
79348178a7 Upgrade node-varnish to 0.3.0 2014-03-12 18:11:19 +01:00
Sandro Santilli
60b552027b Add optional support for rollbar
Re-targets to 1.10.0
Also installs an uncaught exception handler

Closes #150
2014-03-12 17:21:35 +01:00
Sandro Santilli
62cbb15089 Include tiler version in startup log 2014-03-11 12:21:00 +01:00
Sandro Santilli
667b911023 Prepare for 1.9.1 2014-03-10 17:41:44 +01:00
Sandro Santilli
071e86799b Release 1.9.0 2014-03-10 17:40:55 +01:00
Sandro Santilli
4164cf7adb Set release date for 1.8.5 2014-03-10 17:37:19 +01:00
Sandro Santilli
b61aee36e7 More format changes 2014-03-06 16:29:26 +01:00
Sandro Santilli
7b16676f63 Retarget to 1.9.0 2014-03-06 16:28:13 +01:00
javi
ff4f46abcc Merge branch 'server_metadata' 2014-03-06 16:27:01 +01:00
javi santana
09c1bd96df fix formating 2014-03-06 16:22:25 +01:00
javi santana
40a190c29c added cdn_url option 2014-03-06 16:22:04 +01:00
javi
5bfc360856 added serverMetadata option for layer group, close #182 CDB-1940 2014-03-06 15:19:12 +01:00
Sandro Santilli
7eb26a7326 Upgrade windshaft to 0.19.3, fixing crash on dns error
Closes #180
2014-03-05 18:16:42 +01:00
Sandro Santilli
0afc9c154b Cleanly catch exceptions from sendResponse
Closes #178
2014-03-04 18:04:58 +01:00
Sandro Santilli
97e00fb47d Do not send duplicated stats on template instanciation
Closes #179
2014-03-04 17:51:50 +01:00
Sandro Santilli
dbae0eeb31 It is "cacheDns", not "dnsCache"
See https://github.com/sivy/node-statsd/issues/38
2014-03-04 17:37:19 +01:00
Sandro Santilli
bd9a21b805 Add "dnsCache" statsd setting in the example configs 2014-03-04 16:52:16 +01:00
Sandro Santilli
033f8df500 Include API docs, moved from wiki
Closes #164
2014-03-04 15:39:21 +01:00
Sandro Santilli
ffda103d61 Do not UNWATCH on every redis client release
Closes #161
2014-03-04 15:36:08 +01:00
Sandro Santilli
ecc9ea1226 Use 403 for forbidden, not 401
Includes upgrade of windshaft to 0.19.3
Includes upgrade of redis-mpool to 0.0.4
2014-03-04 15:32:31 +01:00
Sandro Santilli
93345a19b2 Do not log an error on GET /
Closes #177
2014-03-04 14:26:41 +01:00
Sandro Santilli
1741a20575 Do not cache map creation responses
Closes #176
CDB-1908 #resolve
CDB-1901 #resolve

Includes testcase
2014-03-04 10:46:15 +01:00
Sandro Santilli
30eb939dc7 Fix error message on missing requested signature
We don't really distinguish between missing or non-authorizing
signature. And that's fine. See #170
2014-03-03 18:14:17 +01:00
Sandro Santilli
40a254922a Raise 403 forbidden on missing requested signature
Closes #170
Includes testcase
2014-03-03 18:06:39 +01:00
Sandro Santilli
7bc5bab432 Properly prefix statsd labels for all endpoints
CDB-1861 #resolve
Will be 100% complete with update of Windshaft to 0.19.3+
2014-03-03 16:24:20 +01:00
Sandro Santilli
6034f49f40 Prepare for 1.8.5 2014-03-03 11:45:23 +01:00
Sandro Santilli
087eff4734 Release 1.8.4 2014-03-03 11:26:16 +01:00
Sandro Santilli
ed5b045a15 Allow using NODE_ENV env variable to determine app configuration
Default to "development" environment.
Forward NODE_ENV variable to childrens (for example, to hush
millstone).
2014-02-28 16:22:24 +01:00
Sandro Santilli
c1a3cbc28c Hush millstone during testsuite 2014-02-28 16:14:44 +01:00
Sandro Santilli
bddc65a504 Forbid instanciating templates of foreign users
Closes #173
Includes testcase
2014-02-28 16:05:46 +01:00
Sandro Santilli
ddd2628c19 Fix database connection settings on template instanciation
Closes #174
Enhances testsuite to ensure test.js settings are read
2014-02-28 15:56:31 +01:00
Sandro Santilli
cf0c33a85d Oops, previous commit closed #172, not #173
Closes #172
Reopens #173
2014-02-28 13:25:28 +01:00
Sandro Santilli
f46dc90035 Forbid using map signatures of foreign users
Closes #173
Includes testcase
2014-02-28 13:24:38 +01:00
Sandro Santilli
73276b1003 Upgrade windshaft to 0.19.2
Fixes obscure "ECONNREFUSED" error message (closes #171)
Change some http status responses to be more appropriate to the case
2014-02-28 10:54:18 +01:00
Sandro Santilli
16e67387c9 Tell npm to use known registrars
See http://blog.npmjs.org/post/78085451721/npms-self-signed-certificate-is-no-more
2014-02-28 10:50:45 +01:00
Sandro Santilli
ca1b31bd9c Add example MapConfig using a torque layer 2014-02-27 17:20:23 +01:00
Sandro Santilli
55f333c0b7 Call userByReq() only once in req2params 2014-02-27 16:40:59 +01:00
Sandro Santilli
f24e4f8a0a Really skip CDB_TableMetadata lookup for sql affected by no tables
Closes #169
2014-02-27 15:34:09 +01:00
Sandro Santilli
eec9933fb8 Accept a slightly different error message on timeout
Node 0.10 uses ESOCKETTIMEDOUT while 0.8 uses ETIMEDOUT
See http://travis-ci.org/CartoDB/Windshaft-cartodb/builds/19722727
2014-02-27 13:37:44 +01:00
Sandro Santilli
238e8f39f2 Fix ticket referenc ein NEWS entry of 1.8.3 2014-02-27 12:46:56 +01:00
Sandro Santilli
919bcb6888 Prepare for 1.8.4 2014-02-27 12:46:43 +01:00
Sandro Santilli
50ebb25205 Release 1.8.3 2014-02-27 12:45:02 +01:00
Sandro Santilli
625642ca33 Oops, previous commit closed #168, not #16
Closes #168
2014-02-27 12:43:15 +01:00
Sandro Santilli
36632c762e Do not query CDB_TableMetadata for queries affected by no tables
Closes #16
2014-02-27 12:32:34 +01:00
Sandro Santilli
f284362988 Reduce sql-api communication timeout, and allow overriding it
Introduces new sqlapi.timeout directive, defaults to 100 ms
Includes testcase.
Closes #167
2014-02-27 10:33:32 +01:00
Sandro Santilli
cf01f01bc9 Upgrades windshaft to 0.19.1 with many performance improvements
Among others:

- Improve speed of instanciating a map
- Give meaningful error on attempts to use map tokens with
  attribute service

Closes #156 -- CDB-1796 #resolve
Closes #147
Closes #159
Closes #165
2014-02-26 17:26:17 +01:00
Sandro Santilli
5d0c71d292 Prepare for 1.8.3 2014-02-25 11:10:56 +01:00
24 changed files with 1246 additions and 182 deletions

View File

@@ -4,6 +4,9 @@ before_install:
- sudo apt-get install -q libmapnik-dev
- createdb template_postgis
- psql -c "CREATE EXTENSION postgis" template_postgis
# Tell npm to use known registrars:
# see http://blog.npmjs.org/post/78085451721/npms-self-signed-certificate-is-no-more
- npm config set ca ""
env:
- NPROCS=1 JOBS=1

27
LICENCE
View File

@@ -1,27 +0,0 @@
Copyright (c) 2011, Vizzuality
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by Vizzuality.
4. Neither the name of Vizzuality nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

27
LICENSE Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2014, Vizzuality
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

87
NEWS.md
View File

@@ -1,3 +1,90 @@
1.10.1 -- 2014-03-21
-------------------
Bug fixes:
- Do not cache non-success jsonp responses (#186)
1.10.0 -- 2014-03-20
-------------------
New features:
- Add optional support for rollbar (#150)
Enhancements:
- Do not send connection details to client (#183)
- Upgrade node-varnish to 0.3.0
- Upgrade Windshaft to 0.20.0, see
http://github.com/CartoDB/Windshaft/blob/0.20.0/NEWS
- Include tiler version in startup log
- Install an uncaught exception handler
- Require own fork of node-mapnik, with temptative fix
for libxml usage (glibc detected corruptions)
Other changes:
- Switch to 3-clause BSD license (#184)
1.9.0 -- 2014-03-10
-------------------
New features:
- Allow to set server related configuration in serverMetadata (#182)
1.8.5 -- 2014-03-10
-------------------
Enhancements:
- Set statsd prefix for all endpoints
- Respond with a permission denied on attempt to access map tiles waiving
signature of someone who had not left any (#170)
- Do not log an error on GET / (#177)
- Do not UNWATCH on every redis client release (#161)
- Include API docs (#164)
- Add "cacheDns" statsd setting in the example configs
- Do not send duplicated stats on template instanciation
- Do not die on dns resolution errors (#178, #180)
Bug fixes:
- Do not cache map creation responses (#176)
1.8.4 -- 2014-03-03
-------------------
Enhancements:
- Really skip CDB_TableMetadata lookup for sql affected by no tables (#169)
- Upgrade windshaft to 0.19.2, see node_modules/windshaft/NEWS
- Clarify obscure "ECONNREFUSED" error message (#171)
- Change some http status responses to be more appropriate to the case
- Forbid using map signatures of foreign users (#172)
- Forbid instanciating templates of foreign users (#173)
- Allow passing environment configuration name via NODE_ENV to app.js
- Print environment configuration name on app start
Bug fixes:
- Fix database connection settings on template instanciation (#174)
1.8.3 -- 2014-02-27
-------------------
Enhancements:
- Upgrades windshaft to 0.19.1 with many performance improvements,
See node_modules/windshaft/NEWS
- Improve speed of instanciating a map (#147, #159, #165)
- Give meaningful error on attempts to use map tokens
with attribute service (#156)
- Reduce sql-api communication timeout, and allow overriding (#167)
[ new sqlapi.timeout directive, defaults to 100 ms ]
- Do not query CDB_TableMetadata for queries affected by no tables (#168)
1.8.2 -- 2014-02-25
-------------------

31
app.js
View File

@@ -8,11 +8,16 @@
*/
if ( process.argv[2] ) ENV = process.argv[2];
else if ( process.env['NODE_ENV'] ) ENV = process.env['NODE_ENV'];
else ENV = 'development';
process.env['NODE_ENV'] = ENV;
// sanity check
var ENV = process.argv[2]
if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
console.error("\nnode app.js [environment]");
console.error("environments: [development, production, staging]\n");
console.error("environments: development, production, staging\n");
process.exit(1);
}
@@ -25,13 +30,22 @@ global.environment = require(__dirname + '/config/environments/' + ENV);
_.extend(global.settings, global.environment);
global.log4js = require('log4js')
log4js.configure({
log4js_config = {
appenders: [
{ type: "console", layout: { type:'basic' } }
],
replaceConsole:true
});
};
if ( global.environment.rollbar ) {
log4js_config.appenders.push({
type: __dirname + "/lib/cartodb/log4js_rollbar.js",
options: global.environment.rollbar
});
}
log4js.configure(log4js_config);
global.logger = log4js.getLogger();
// Include cartodb_windshaft only _after_ the "global" variable is set
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
@@ -48,8 +62,12 @@ ws.maxConnections = global.environment.maxConnections || 128;
ws.listen(global.environment.port, global.environment.host);
var version = require("./package").version;
ws.on('listening', function() {
console.log("Windshaft tileserver started on " + global.environment.host + ':' + global.environment.port);
console.log("Windshaft tileserver " + version + " started on "
+ global.environment.host + ':' + global.environment.port
+ " (" + ENV + ")");
});
// DEPRECATED, use SIGUSR2
@@ -62,3 +80,6 @@ process.on('SIGUSR2', function() {
ws.dumpCacheStats();
});
process.on('uncaughtException', function(err) {
logger.error('Uncaught exception: ' + err.stack);
});

View File

@@ -64,7 +64,8 @@ var config = {
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'dev.'
prefix: 'dev.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -106,7 +107,10 @@ var config = {
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
}
,varnish: {
host: 'localhost',

View File

@@ -59,6 +59,7 @@ var config = {
host: 'localhost',
port: 8125,
prefix: ':host.', // could be hostname, better not containing dots
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -100,7 +101,10 @@ var config = {
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
}
,varnish: {
host: 'localhost',
@@ -112,6 +116,21 @@ var config = {
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:false
,serverMetadata: {
cdn_url: {
http: 'api.cartocdn.com',
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
};
module.exports = config;

View File

@@ -58,7 +58,8 @@ var config = {
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'stage.:host.'
prefix: 'stage.:host.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -100,7 +101,10 @@ var config = {
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
}
,varnish: {
host: 'localhost',
@@ -112,6 +116,21 @@ var config = {
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
,serverMetadata: {
cdn_url: {
http: 'api.cartocdn.com',
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
};
module.exports = config;

View File

@@ -58,7 +58,8 @@ var config = {
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'test.:host.'
prefix: 'test.:host.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -102,7 +103,10 @@ var config = {
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
}
,varnish: {
host: '',

111
docs/Map-API.md Normal file
View File

@@ -0,0 +1,111 @@
# Kind of maps
Windshaft-CartoDB supports these kind of maps:
- [Temporary maps](#temporary-maps) (created by anyone)
- [Detached maps](#detached-maps)
- [Inline maps](#inline-maps) (legacy)
- [Persistent maps](#peristent-maps) (created by CartDB user)
- [Template maps](#template-maps)
- [Table maps](#table-maps) (legacy, deprecated)
## Temporary maps
Temporary maps have no owners and are anonymous in nature.
There are two kind of temporary maps:
- Detached maps (aka MultiLayer-API)
- Inline maps
### Detached maps
Detached maps are maps which are configured with a request
obtaining a temporary token and then used by referencing
the obtained token. The token expires automatically when unused.
Anyone can create detached maps, but users will need read access
to the data source of the map layers.
The configuration format is a [MapConfig]
(http://github.com/CartoDB/Windshaft/wiki/MapConfig-specification) document.
The HTTP endpoints for creating the map and using it are described [here]
(http://github.com/CartoDB/Windshaft-cartodb/wiki/MultiLayer-API)
*TODO* cleanup the referenced document
### Inline maps
Inline maps are maps that only exist for a single request,
being the request for a specific map resource (tile).
Inline maps are always bound to a table, and can only be
obtained by those having read access to the that table.
Additionally, users need to have access to any datasource
specified as part of the configuration.
Inline maps only support PNG and UTF8GRID tiles.
The configuration consist in a set of parameters, to be
specified in the query string of the tile request:
* sql - the query to run as datasource, can be an array
* style - the CartoCSS style for the datasource, can be an array
* style_version - version of the CartoCSS style, can be an array
* interactivity - only for fetching UTF8GRID,
If the style is not provided, style of the associated table is
used; if the sql is not provided, all records of the associated
table are used as the datasource; the two possibilities result
in a mix between _inline_ maps and [Table maps][].
*TODO* specify (or link) api endpoints
## Persistent maps
Persistent maps can only be created by a CartoDB user who has full
responsibility over editing and deleting them. There are two
kind of persistent maps:
- Template maps
- Table maps (legacy, deprecated)
### Templated maps
Templated maps are templated [MapConfig]
(http://github.com/CartoDB/Windshaft/wiki/MapConfig-specification) documents
associated with an authorization certificate.
The authorization certificate determines who can instanciate the
template and use the resulting map. Authorized users of the instanciated
maps will have the same database access privilege of the template owner.
The HTTP endpoints for creating and using templated maps are described [here]
(http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps).
*TODO* cleanup the referenced document
### Table maps
Table maps are maps associated with a table.
Configuration of such maps is limited to the CartoCSS style.
* style - the CartoCSS style for the datasource, can be an array
* style_version - version of the CartoCSS style, can be an array
You can only fetch PNG or UTF8GRID tiles from these maps.
Access method is the same as the one for [Inline maps](#inline-maps)
# Endpoints description
- **/api/maps/** (same interface than https://github.com/CartoDB/Windshaft/wiki/Multilayer-API)
- **/api/maps/named** (same interface than https://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps)
NOTE: in case Multilayer-API does not contain this info yet, the
endpoint for fetching attributes is this:
- **/api/maps/:map_id/:layer_index/attributes/:feature_id**
- would return { c: 1, d: 2 }

28
docs/MultiLayer-API.md Normal file
View File

@@ -0,0 +1,28 @@
The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/Vizzuality/Windshaft/wiki/Multilayer-API) in a few ways.
## Last modification timestamp embedded in the token
It encodes a timestamp of 'last modification time' into the map token (token:EPOCH) returned to the client.
It accepts tokens with encoded timestamp from the client considering the token suffix as a cache_buster value.
Clients don't need to be aware of the extension but rather use the API as they would use the base one.
The only difference will be that the _same_ layergroup configuration may result in different tokens if source data was modified between the mapview requests.
## Additional attributes in the response object
Windshaft-CartoDB adds the following attributes in the response object
- ``last_update`` field with ISO format (2013-11-30T12:23:10).
- ``cdn_url`` object containing CDN url client should use (not mandatory) to access the tiles. It's in the form:
```json
{
http: 'http://cdn_url.com/'
https: 'https://secure.cdn_url.com/'
}
```
## Stats tag
Windshaft-CartoDB adds support for a ``stat_tag`` element in the multilayer configuration to help [stats](Redis-stats-format) gathering.

292
docs/Template-maps.md Normal file
View File

@@ -0,0 +1,292 @@
Template maps are layergroup configurations that rather than being
fully defined contain variables that can be set to produce a different
layergroup configurations (instantiation).
Template maps are persistent, can only be created and deleted by the
CartoDB user showing a valid API_KEY.
Instantiating a signed template map would result in a [signed
map](https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps)
instance that would be signed with the same signature as the template.
Deleting a signed template results in deletion of all signatures created
as a result of instantiation.
# Template format
A templated layergroup would allow using placeholders
in the "cartocss" and "sql" elements in the "option"
field of any "layer" of a layergroup configuration
(see https://github.com/CartoDB/Windshaft/wiki/MapConfig-specification).
Valid placeholder names start with a letter and can only
contain letters, numbers or underscores. They have to be
written between ``<%= `` and `` %>`` strings in order to be
replaced. Example: ``<%= my_color %>``.
The set of supported placeholders for a template will need to be
explicitly defined specifying type and default value for each.
**placeholder types**
Placeholder type will determine the kind of escaping for the
associated value. Supported types are:
* sql_literal (internal single-quotes will be sql-escaped)
* sql_ident (internal double-quotes will be sql-escaped)
* number (can only contain numerical representation)
* css_color (can only contain color names or hex-values)
* ... (add more as need arises)
Placeholder default value will be used when not provided at
instantiation time and could be used to test validity of the
template by creating a default instance.
Additionally you'll be able to embed an authorization
certificate that would be used to sign any instance of the template.
```js
// template.json
{
version: '0.0.1',
// there can be at most 1 template with the same name for any user
// valid names start with a letter and only contains letter, numbers
// or underscores
name: 'template_name',
// embedded authorization certificate
auth: {
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps
method: 'token', // or "open" (the default if no "method" is given)
valid_tokens: ['auth_token1','auth_token2'] // only (required and non empty) for 'token' method
},
// Variables not listed here are not substituted
// Variable not provided at instantiation time trigger an error
// A default is required for optional variables
// Type specification is used for quoting, to avoid injections
placeholders: {
color: {
type:'css_color',
default:'red'
},
cartodb_id: {
type:'number',
default: 1
}
},
layergroup: {
// see https://github.com/CartoDB/Windshaft/wiki/MapConfig-specification
"version": "1.0.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}]
}
}
```
# Creating a templated map
You can create a signed template map with a single call (for simplicity).
You'd use a POST sending JSON data:
```sh
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://docs.cartodb.com/tiles/template?api_key=APIKEY'
```
The response would be like this:
```js
{
"template_id":"@template_name"
}
```
If a template with the same name exists in the user storage,
a 400 response is generated.
Errors are in this form:
```js
{
"error":"Some error string here"
}
```
# Updating an existing template
Update of a template map implies removal all signatures from previous
map instances.
You can update a signed template map with a PUT:
```sh
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://docs.cartodb.com/tiles/template/:template_name?api_key=APIKEY'
```
A template with the same name will be updated, if any.
The response would be like this:
```js
{
"template_id":"@template_name"
}
```
If a template with the same name does NOT exist,
a 400 HTTP response is generated with an error in this format:
```js
{
"error":"Some error string here"
}
```
# Listing available templates
You can get a list of available templates with a GET to ``/template``.
A valid api_key is required.
```sh
curl -X GET 'https://docs.cartodb.com/tiles/template?api_key=APIKEY'
```
The response would be like this:
```js
{
"template_ids": ["@template_name1","@template_name2"]
}
```
Or, on error:
```js
{
"error":"Some error string here"
}
```
# Getting a specific template
You can get the definition of a template with a
GET to ``/template/:template_name``.
A valid api_key is required.
Example:
```sh
curl -X GET 'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
The response would be like this:
```js
{
"template": {...} // see template.json above
}
```
Or, on error:
```js
{
"error":"Some error string here"
}
```
# Instantiating a template map
You can instantiate a template map passing all required parameters with
a POST to ``/template/:template_name``.
Valid credentials will be needed, if required by the template.
```js
// params.js
{
color: '#ff0000',
cartodb_id: 3
}
```
```sh
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.js \
'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
The response would be like this:
```js
{
"layergroupid":"docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated":"2013-11-14T11:20:15.000Z"
}
```
or, on error:
```js
{
"error":"Some error string here"
}
```
You can then use the ``layergroupid`` for fetching tiles and grids as you do
normally ( see https://github.com/CartoDB/Windshaft/wiki/Multilayer-API).
But you'll still have to show the ``auth_token``, if required by the template
(see https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps)
Instances of a signed template map will be signed with the same signature
certificate associated with the template. Such certificate would contain
a reference to the template identifier, so that it can be revoked every
time the template is updated or deleted.
### using JSONP
There is also a special endpoint to be able to instanciate using JSONP (for old browsers)
```
curl 'https://docs.cartodb.com/tiles/template/@template_name/jsonp?auth_token=AUTH_TOKEN&callback=function_name&config=template_params_json'
```
it takes the ``callback`` function (required), ``auth_token`` in case the template needs auth and ``config`` which is the variabñes for the template (in case it has variables). For example config may be created (using javascript)
```
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
the response it's in this format:
```
jQuery17205720721024554223_1390996319118(
{
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
}
)
```
# Deleting a template map
Deletion of a template map will imply removal all instance signatures
You can delete a templated map with a DELETE to ``/template/:template_name``:
```sh
curl -X DELETE 'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
On success, a 204 (No Content) response would be issued.
Otherwise a 4xx response with this format:
```js
{
"error":"Some error string here"
}
```

View File

@@ -40,6 +40,12 @@ var CartodbWindshaft = function(serverOptions) {
callback(err, req);
}
// This is for Templated maps
//
// "named" is the official, "template" is for backward compatibility up to 1.6.x
//
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
serverOptions.signedMaps = new SignedMaps(redisPool);
var templateMapsOpts = {
max_user_templates: global.environment.maxUserTemplates
@@ -58,13 +64,26 @@ var CartodbWindshaft = function(serverOptions) {
}
var ws_sendResponse = ws.sendResponse;
// GET routes for which we don't want to request any caching.
// POST/PUT/DELETE requests are never cached anyway.
var noCacheGETRoutes = [
'/',
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
serverOptions.base_url_mapconfig,
template_baseurl + '/:template_id/jsonp'
];
ws.sendResponse = function(res, args) {
var that = this;
var thatArgs = arguments;
var statusCode;
if ( args.length > 2 ) statusCode = args[2];
else {
statusCode = args[1] || 200;
if ( res._windshaftStatusCode ) {
// Added by our override of sendError
statusCode = res._windshaftStatusCode;
} else {
if ( args.length > 2 ) statusCode = args[2];
else {
statusCode = args[1] || 200;
}
}
var req = res.req;
Step (
@@ -85,18 +104,39 @@ var CartodbWindshaft = function(serverOptions) {
// unsuccessful responses
return false;
}
if ( _.contains(noCacheGETRoutes, req.route.path) ) {
//console.log("Skipping cache channel in route:\n" + req.route.path);
return false;
}
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" + mapCreateRoutes.join("\n"));
serverOptions.addCacheChannel(that, req, this);
},
function sendResponse(err, added) {
if ( err ) console.log(err + err.stack);
ws_sendResponse.apply(that, thatArgs);
return null;
},
function finish(err) {
if ( err ) console.log(err + err.stack);
}
);
};
var ws_sendError = ws.sendError;
ws.sendError = function() {
var res = arguments[0];
var statusCode = arguments[2];
res._windshaftStatusCode = statusCode;
ws_sendError.apply(this, arguments);
};
/**
* Helper to allow access to the layer to be used in the maps infowindow popup.
*/
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_infowindow');
}
ws.doCORS(res);
Step(
function(){
@@ -118,6 +158,9 @@ var CartodbWindshaft = function(serverOptions) {
* Helper to allow access to metadata to be used in embedded maps.
*/
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_map_metadata');
}
ws.doCORS(res);
Step(
function(){
@@ -139,6 +182,9 @@ var CartodbWindshaft = function(serverOptions) {
* TODO: Move?
*/
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.flush_cache');
}
ws.doCORS(res);
Step(
function flushCache(){
@@ -161,12 +207,6 @@ var CartodbWindshaft = function(serverOptions) {
return serverOptions.userByReq(req);
}
// This is for Templated maps
//
// "named" is the official, "template" is for backward compatibility up to 1.6.x
//
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
// Add a template
ws.post(template_baseurl, function(req, res) {
ws.doCORS(res);
@@ -181,7 +221,7 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can create templated maps");
err.http_status = 401;
err.http_status = 403;
throw err;
}
var next = this;
@@ -230,7 +270,7 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can list templated maps");
err.http_status = 401;
err.http_status = 403;
throw err;
}
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
@@ -273,6 +313,9 @@ var CartodbWindshaft = function(serverOptions) {
// Get a specific template
ws.get(template_baseurl + '/:template_id', function(req, res) {
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_template');
}
ws.doCORS(res);
var that = this;
var response = {};
@@ -287,7 +330,7 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated users can get template maps");
err.http_status = 401;
err.http_status = 403;
throw err;
}
tpl_id = req.params.template_id.split('@');
@@ -330,7 +373,10 @@ var CartodbWindshaft = function(serverOptions) {
});
// Delete a specific template
ws.delete(template_baseurl + '/:template_id', function(req, res) {
ws.del(template_baseurl + '/:template_id', function(req, res) {
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.delete_template');
}
ws.doCORS(res);
var that = this;
var response = {};
@@ -345,7 +391,7 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated users can delete template maps");
err.http_status = 401;
err.http_status = 403;
throw err;
}
tpl_id = req.params.template_id.split('@');
@@ -381,6 +427,9 @@ var CartodbWindshaft = function(serverOptions) {
// Get a list of owned templates
ws.get(template_baseurl, function(req, res) {
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_template_list');
}
ws.doCORS(res);
var that = this;
var response = {};
@@ -393,7 +442,7 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can list templated maps");
err.http_status = 401;
err.http_status = 403;
throw err;
}
templateMaps.listTemplates(cdbuser, this);
@@ -454,7 +503,14 @@ var CartodbWindshaft = function(serverOptions) {
// Format of template_id: [<template_owner>]@<template_id>
var tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) {
if ( tpl_id[0] ) cdbuser = tpl_id[0];
if ( tpl_id[0] && tpl_id[0] != cdbuser ) {
var err = new Error('Cannot instanciate map of user "'
+ tpl_id[0] + '" on database of user "'
+ cdbuser + '"')
err.http_status = 403;
callback(err);
return;
}
tpl_id = tpl_id[1];
}
var auth_token = req.query.auth_token;
@@ -478,12 +534,12 @@ var CartodbWindshaft = function(serverOptions) {
authorized = signedMaps.authorizedByCert(cert, auth_token);
} catch (err) {
// we catch to add http_status
err.http_status = 401;
err.http_status = 403;
throw err;
}
if ( ! authorized ) {
err = new Error('Unauthorized template instanciation');
err.http_status = 401;
err.http_status = 403;
throw err;
}
/*if ( (! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') && req.query.callback === undefined) {
@@ -564,9 +620,6 @@ var CartodbWindshaft = function(serverOptions) {
} else {
ws.sendResponse(res, [response, 200]);
}
if ( req.profiler && req.profiler.statsd_client) {
req.profiler.sendStats();
}
}
ws.post(template_baseurl + '/:template_id', function(req, res) {

View File

@@ -0,0 +1,49 @@
var rollbar = require("rollbar");
/**
* Rollbar Appender. Sends logging events to Rollbar using node-rollbar
*
* @param config object with rollbar configuration data
* {
* token: 'your-secret-token',
* options: node-rollbar options
* }
*/
function rollbarAppender(config) {
var opt = config.options;
rollbar.init(opt.token, opt.options);
return function(loggingEvent) {
/*
For logger.trace('one','two','three'):
{ startTime: Wed Mar 12 2014 16:27:40 GMT+0100 (CET),
categoryName: '[default]',
data: [ 'one', 'two', 'three' ],
level: { level: 5000, levelStr: 'TRACE' },
logger: { category: '[default]', _events: { log: [Object] } } }
*/
// Levels:
// TRACE 5000
// DEBUG 10000
// INFO 20000
// WARN 30000
// ERROR 40000
// FATAL 50000
//
// We only log error and higher errors
//
if ( loggingEvent.level.level < 40000 ) return;
rollbar.reportMessage(loggingEvent.data);
};
}
function configure(config) {
return rollbarAppender(config);
}
exports.name = "rollbar";
exports.appender = rollbarAppender;
exports.configure = configure;

View File

@@ -67,6 +67,10 @@ module.exports = function(){
log_format: global.environment.log_format,
useProfiler: global.environment.useProfiler
};
// Do not send unwatch on release
// See http://github.com/CartoDB/Windshaft-cartodb/issues/161
me.redis.unwatchOnRelease = false;
// Be nice and warn if configured mapnik version
// is != instaled mapnik version
@@ -116,13 +120,15 @@ module.exports = function(){
//
var maxSockets = global.environment.maxConnections || 128;
var maxGetLen = api.max_get_sql_length || 2048;
var maxSQLTime = api.timeout || 100; // 1/10 of a second by default
var reqSpec = {
url:sqlapi,
json:true,
headers:{host: sqlapihostname}
// http://nodejs.org/api/http.html#http_agent_maxsockets
,pool:{maxSockets:maxSockets}
//,timeout:100
// timeout in milliseconds
,timeout:maxSQLTime
}
if ( sql.length > maxGetLen ) {
reqSpec.method = 'POST';
@@ -191,7 +197,7 @@ module.exports = function(){
}
var qtables = rows[0].cdb_querytables;
var tableNames = qtables.split(/^\{(.*)\}$/)[1];
tableNames = tableNames.split(',');
tableNames = tableNames ? tableNames.split(',') : [];
callback(null, tableNames);
});
};
@@ -369,6 +375,14 @@ module.exports = function(){
}
}
// include in layergroup response the variables in serverMedata
// those variables are useful to send to the client information
// about how to reach this server or information about it
var serverMetadata = global.environment.serverMetadata;
if (serverMetadata) {
_.extend(response, serverMetadata);
}
// Don't wait for the mapview count increment to
// take place before proceeding. Error will be logged
// asyncronously
@@ -389,23 +403,34 @@ module.exports = function(){
var key = req.params.map_key || req.params.api_key;
var cacheKey = dbName + ':' + token;
var tabNames;
me.affectedTables(usr, key, sql, function(err, tableNames) {
Step(
function getTables() {
me.affectedTables(usr, key, sql, this);
},
function getLastupdated(err, tableNames) {
if (req.profiler) req.profiler.done('affectedTables');
if ( err ) { done(err); return; }
if ( err ) throw err;
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
// store for caching from me.afterLayergroupCreate
me.channelCache[cacheKey] = cacheChannel;
// find last updated
me.findLastUpdated(usr, key, tableNames, function(err, lastUpdated) {
if (req.profiler) req.profiler.done('findLastUpdated');
if ( err ) { done(err); return; }
response.layergroupid = response.layergroupid + ':' + lastUpdated; // use epoch
response.last_updated = new Date(lastUpdated).toISOString(); // TODO: use ISO format
done(null);
});
});
if ( ! tableNames.length ) return 0; // skip for no affected tables
tabNames = tableNames;
me.findLastUpdated(usr, key, tableNames, this);
},
function(err, lastUpdated) {
if ( err ) throw err;
if (req.profiler && tabNames) req.profiler.done('findLastUpdated');
response.layergroupid = response.layergroupid + ':' + lastUpdated; // use epoch
response.last_updated = new Date(lastUpdated).toISOString();
return null;
},
function finish(err) {
done(err);
}
);
};
/* X-Cache-Channel generation } */
@@ -490,6 +515,14 @@ module.exports = function(){
// @param callback function(err)
//
me.setDBConn = function(dbowner, params, callback) {
// Add default database connection parameters
// if none given
_.defaults(params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
Step(
function getDatabaseHost(){
cartoData.getUserDBHost(dbowner, this);
@@ -619,12 +652,21 @@ module.exports = function(){
}
if ( ! signed_by ) {
// request not authorized by signer,
// continue to check table privacy,
// if table was given
// request not authorized by signer.
// if table was given, continue to check table privacy
if ( req.params.table ) return null;
// otherwise return no authorization
callback(err, null);
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
//
if ( ! req.params.signer ) {
callback(null, true); // authorized so far
return;
}
// if signer name was given, return no authorization
callback(null, false);
return;
}
@@ -646,7 +688,7 @@ module.exports = function(){
},
function(err, privacy){
if (req.profiler) req.profiler.done('getTablePrivacy');
callback(err, privacy);
callback(err, privacy !== "0");
}
);
};
@@ -694,6 +736,8 @@ module.exports = function(){
_.each(bad_query, function(key){ delete req.query[key]; });
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
var user = me.userByReq(req);
if ( req.params.token ) {
//console.log("Request parameters include token " + req.params.token);
var tksplit = req.params.token.split(':');
@@ -702,7 +746,13 @@ module.exports = function(){
tksplit = req.params.token.split('@');
if ( tksplit.length > 1 ) {
req.params.signer = tksplit.shift();
if ( ! req.params.signer ) req.params.signer = this.userByReq(req);
if ( ! req.params.signer ) req.params.signer = user;
else if ( req.params.signer != user ) {
var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' + user + '"')
err.http_status = 403;
callback(err);
return;
}
if ( tksplit.length > 1 ) {
var template_hash = tksplit.shift(); // unused
}
@@ -721,19 +771,21 @@ module.exports = function(){
if (req.profiler) req.profiler.done('req2params.setup');
var user = me.userByReq(req);
Step(
function getPrivacy(){
me.authorize(req, this);
},
function gatekeep(err, data){
function gatekeep(err, authorized){
if (req.profiler) req.profiler.done('authorize');
if(err) throw err;
if(data === "0") throw new Error("Sorry, you are unauthorized (permission denied)");
return data;
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
throw err;
}
return null;
},
function getDatabase(err, data){
function getDatabase(err){
if(err) throw err;
that.setDBConn(user, req.params, this);
},

98
npm-shrinkwrap.json generated
View File

@@ -1,21 +1,49 @@
{
"name": "windshaft-cartodb",
"version": "1.8.2",
"version": "1.10.0",
"dependencies": {
"node-varnish": {
"version": "0.2.0",
"from": "http://github.com/Vizzuality/node-varnish/tarball/v0.2.0"
"version": "0.3.0",
"from": "http://github.com/Vizzuality/node-varnish/tarball/0.3.0"
},
"underscore": {
"version": "1.3.3"
},
"windshaft": {
"version": "0.19.0",
"from": "http://github.com/CartoDB/Windshaft/tarball/0.19.0-rc1",
"version": "0.20.0",
"from": "http://github.com/CartoDB/Windshaft/tarball/0.20.0",
"dependencies": {
"grainstore": {
"version": "0.18.0",
"version": "0.18.1",
"dependencies": {
"carto": {
"version": "0.9.5-cdb2",
"from": "http://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
"dependencies": {
"underscore": {
"version": "1.4.4"
},
"xml2js": {
"version": "0.2.8",
"dependencies": {
"sax": {
"version": "0.5.8"
}
}
},
"optimist": {
"version": "0.6.1",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
},
"minimist": {
"version": "0.0.8"
}
}
}
}
},
"mapnik-reference": {
"version": "5.0.7"
},
@@ -102,7 +130,7 @@
}
},
"srs": {
"version": "0.3.10"
"version": "0.3.11"
},
"zipfile": {
"version": "0.4.3"
@@ -145,7 +173,7 @@
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
}
}
}
@@ -167,7 +195,7 @@
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
},
"inherits": {
"version": "2.0.1"
@@ -191,7 +219,7 @@
}
},
"readable-stream": {
"version": "1.0.25-1",
"version": "1.0.26-2",
"dependencies": {
"string_decoder": {
"version": "0.10.25-1"
@@ -220,15 +248,10 @@
}
},
"rc": {
"version": "0.3.3",
"version": "0.3.4",
"dependencies": {
"optimist": {
"version": "0.3.7",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
}
}
"minimist": {
"version": "0.0.8"
},
"deep-extend": {
"version": "0.2.8"
@@ -258,7 +281,7 @@
"version": "0.0.2"
},
"minimist": {
"version": "0.0.7"
"version": "0.0.8"
}
}
}
@@ -308,7 +331,7 @@
}
},
"tilelive-mapnik": {
"version": "0.6.5",
"version": "0.6.8",
"dependencies": {
"eio": {
"version": "0.2.2"
@@ -325,8 +348,8 @@
"version": "2.3.1"
},
"carto": {
"version": "0.9.5-cdb2",
"from": "http://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
"version": "0.9.5-cdb3",
"from": "http://github.com/CartoDB/carto/tarball/0.9.5-cdb3",
"dependencies": {
"underscore": {
"version": "1.4.4"
@@ -349,12 +372,16 @@
"version": "0.0.2"
},
"minimist": {
"version": "0.0.7"
"version": "0.0.8"
}
}
}
}
},
"step-profiler": {
"version": "0.0.1",
"from": "git://github.com/CartoDB/node-step-profiler.git#0.0.1"
},
"underscore.string": {
"version": "1.1.6",
"dependencies": {
@@ -392,7 +419,8 @@
"version": "0.3.0"
},
"redis-mpool": {
"version": "0.0.3",
"version": "0.0.4",
"from": "http://github.com/CartoDB/node-redis-mpool/tarball/0.0.4",
"dependencies": {
"generic-pool": {
"version": "2.0.4"
@@ -408,7 +436,8 @@
}
},
"mapnik": {
"version": "0.7.26"
"version": "0.7.26-cdb1",
"from": "http://github.com/Vizzuality/node-mapnik/tarball/0.7.26-cdb1"
},
"lzma": {
"version": "1.2.3"
@@ -420,7 +449,7 @@
"version": "0.1.15"
},
"readable-stream": {
"version": "1.0.25-1",
"version": "1.0.26",
"dependencies": {
"string_decoder": {
"version": "0.10.25-1"
@@ -429,8 +458,16 @@
}
}
},
"redis": {
"version": "0.8.6"
"rollbar": {
"version": "0.3.1",
"dependencies": {
"node-uuid": {
"version": "1.4.1"
},
"lru-cache": {
"version": "2.2.4"
}
}
},
"strftime": {
"version": "0.6.2"
@@ -438,6 +475,9 @@
"semver": {
"version": "1.1.4"
},
"redis": {
"version": "0.8.6"
},
"mocha": {
"version": "1.14.0",
"dependencies": {
@@ -482,7 +522,7 @@
}
},
"graceful-fs": {
"version": "2.0.1"
"version": "2.0.2"
},
"inherits": {
"version": "2.0.1"

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "1.8.2",
"version": "1.10.1",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -22,16 +22,17 @@
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"node-varnish": "http://github.com/Vizzuality/node-varnish/tarball/v0.2.0",
"node-varnish": "http://github.com/Vizzuality/node-varnish/tarball/0.3.0",
"underscore" : "~1.3.3",
"windshaft" : "http://github.com/CartoDB/Windshaft/tarball/0.19.0",
"windshaft" : "http://github.com/CartoDB/Windshaft/tarball/0.20.0",
"step": "0.0.x",
"request": "2.9.202",
"cartodb-redis": "~0.3.0",
"redis-mpool": "~0.0.2",
"mapnik": "~0.7.22",
"redis-mpool": "http://github.com/CartoDB/node-redis-mpool/tarball/0.0.4",
"mapnik": "http://github.com/Vizzuality/node-mapnik/tarball/0.7.26-cdb1",
"lzma": "~1.2.3",
"log4js": "~0.6.10"
"log4js": "~0.6.10",
"rollbar": "~0.3.1"
},
"devDependencies": {
"mocha": "1.14.0",

View File

@@ -10,7 +10,7 @@ var strftime = require('strftime');
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
var redis_stats_db = 5;
require(__dirname + '/../support/test_helper');
var helper = require(__dirname + '/../support/test_helper');
var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures';
@@ -20,14 +20,6 @@ serverOptions = ServerOptions();
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
// Check that the response headers do not request caching
// Throws on failure
function checkNoCache(res) {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
assert.ok(!res.headers.hasOwnProperty('cache-control')); // is this correct ?
assert.ok(!res.headers.hasOwnProperty('last-modified')); // is this correct ?
}
suite('multilayer', function() {
var redis_client = redis.createClient(global.environment.redis.port);
@@ -127,6 +119,24 @@ suite('multilayer', function() {
});
});
},
// See https://github.com/CartoDB/Windshaft-cartodb/issues/170
function do_get_tile_nosignature(err)
{
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/localhost@' + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 403, res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
var msg = parsed.error; // TODO: should it be "errors" ?
assert.ok(msg.match(/permission denied/i), msg);
next(err);
});
},
function do_get_grid_layer0(err)
{
if ( err ) throw err;
@@ -183,6 +193,93 @@ suite('multilayer', function() {
});
test("should include serverMedata in the response", function(done) {
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } }
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1'
} }
]
};
var expected_token;
Step(
function do_create_get()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
},
function do_check_create(err, res) {
var parsed = JSON.parse(res.body);
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
done();
}
)
});
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
// NOTE: another test like this is in templates.js
test("get creation requests no cache", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1'
} }
]
};
var expected_token;
Step(
function do_create_get()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
},
function do_check_create(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
expected_token = parsedBody.layergroupid.split(':')[0];
helper.checkNoCache(res);
return null;
},
function finish(err) {
var errors = [];
if ( err ) {
errors.push(err.message);
console.log("Error: " + err);
}
redis_client.keys("map_cfg|" + expected_token, function(err, matches) {
if ( err ) errors.push(err.message);
assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches);
redis_client.del(matches, function(err) {
if ( err ) errors.push(err.message);
if ( errors.length ) done(new Error(errors));
else done(null);
});
});
}
);
});
test("layergroup can hold substitution tokens", function(done) {
var layergroup = {
@@ -493,11 +590,11 @@ suite('multilayer', function() {
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 400, res.body);
assert.equal(res.statusCode, 404, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
var msg = parsed.errors[0];
assert.ok(msg.match(/bogus.*exist/), msg);
checkNoCache(res);
helper.checkNoCache(res);
done();
});
});
@@ -611,7 +708,7 @@ suite('multilayer', function() {
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 401);
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
@@ -627,7 +724,7 @@ suite('multilayer', function() {
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 401);
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
@@ -643,7 +740,7 @@ suite('multilayer', function() {
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 401);
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
@@ -1128,6 +1225,47 @@ suite('multilayer', function() {
);
});
// See https://github.com/CartoDB/Windshaft-cartodb/issues/167
test("lack of response from sql-api will result in a timeout", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: "select *, 'SQLAPINOANSWER' from test_table",
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.1.0'
} }
]
};
Step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res, err) { next(err, res); });
},
function check_post(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.errors, 'Missing "errors" in response: ' + JSON.stringify(parsed));
assert.equal(parsed.errors.length, 1);
var msg = parsed.errors[0];
assert.ok(msg, /could not fetch source tables/, msg);
return null;
},
function finish(err) {
done(err);
}
);
});
suiteTeardown(function(done) {

View File

@@ -134,8 +134,7 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// FIXME: should be 401 Unauthorized
assert.equal(res.statusCode, 400, res.body);
assert.equal(res.statusCode, 403, res.statusCode + ':' + res.body);
assert.deepEqual(JSON.parse(res.body),
{error: 'Sorry, you are unauthorized (permission denied)'});
assert.ok(!res.headers.hasOwnProperty('cache-control'));
@@ -152,7 +151,7 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden or 404 User Not Found
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
assert.deepEqual(JSON.parse(res.body),
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
@@ -320,7 +319,7 @@ suite('server', function() {
headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' },
data: querystring.stringify({style: 'Map { background-color:#aaa; }'})
},{}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
assert.ok(res.body.indexOf('map state cannot be changed by unauthenticated request') != -1, res.body);
@@ -418,8 +417,8 @@ suite('server', function() {
method: 'DELETE',
headers: {host: 'localhost'},
},{}, function(res) {
// FIXME: should be 401 Unauthorized
assert.equal(res.statusCode, 500, res.body);
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 400, res.body);
assert.ok(res.body.indexOf('map state cannot be changed by unauthenticated request') != -1, res.body);
// check that the style wasn't really deleted !
assert.response(server, {
@@ -532,7 +531,7 @@ suite('server', function() {
url: '/tiles/test_table_private_1/infowindow',
method: 'GET'
},{}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
done();
});
@@ -547,7 +546,7 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
assert.deepEqual(JSON.parse(res.body),
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
@@ -615,8 +614,26 @@ suite('server', function() {
url: '/tiles/test_table_private_1/6/31/24.grid.json',
method: 'GET'
},{}, function(res) {
// 401 Unauthorized
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
// 403 Forbidden
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
done();
});
});
// See http://github.com/CartoDB/Windshaft-cartodb/issues/186
test("get'ing the grid of a private table should fail when unauthenticated (jsonp)",
function(done) {
assert.response(server, {
headers: {host: 'localhost'},
url: '/tiles/test_table_private_1/6/31/24.grid.json?callback=x',
method: 'GET'
},{}, function(res) {
// It's forbidden, but jsonp calls for status = 200
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
// Still, we do NOT want to add caching headers here
// See https://github.com/CartoDB/Windshaft-cartodb/issues/186
assert.ok(!res.headers.hasOwnProperty('cache-control'),
"Unexpected Cache-Control: " + res.headers['cache-control']);
done();
});
});
@@ -630,7 +647,7 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
assert.deepEqual(JSON.parse(res.body),
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
@@ -764,8 +781,8 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// 401 Unauthorized
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
// 403 Forbidden
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
done();
});
});
@@ -781,7 +798,7 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// FIXME: should be 401 Unauthorized
// FIXME: should be 403 Forbidden
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
assert.deepEqual(JSON.parse(res.body),
{error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"});
@@ -805,8 +822,8 @@ suite('server', function() {
method: 'GET'
},{
}, function(res) {
// 401 Unauthorized
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
// 403 Forbidden
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
// Failed in 1.6.0 of https://github.com/CartoDB/Windshaft-cartodb/issues/107
assert.ok(!res.headers.hasOwnProperty('cache-control'),
"Unexpected Cache-Control: " + res.headers['cache-control']);
@@ -1279,7 +1296,7 @@ suite('server', function() {
url: '/tiles/test_table_private_1/map_metadata',
method: 'GET'
},{}, function(res) {
// FIXME: should be 401 instead
// FIXME: should be 403 instead
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
assert.ok(!res.headers.hasOwnProperty('cache-control'));
done();

View File

@@ -1,5 +1,4 @@
var assert = require('../support/assert');
var tests = module.exports = {};
var _ = require('underscore');
var redis = require('redis');
var querystring = require('querystring');
@@ -10,7 +9,13 @@ var strftime = require('strftime');
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
var redis_stats_db = 5;
require(__dirname + '/../support/test_helper');
// Pollute the PG environment to make sure
// configuration settings are always enforced
// See https://github.com/CartoDB/Windshaft-cartodb/issues/174
process.env['PGPORT'] = '666';
process.env['PGHOST'] = 'fake';
var helper = require(__dirname + '/../support/test_helper');
var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures';
@@ -69,7 +74,7 @@ suite('template_api', function() {
function postTemplate(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401);
assert.equal(res.statusCode, 403);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'), res.body);
err = parsed.error;
@@ -303,6 +308,52 @@ suite('template_api', function() {
});
});
test("instance endpoint should return server metadata", function(done){
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } }
var tmpl = _.clone(template_acceptance1)
tmpl.name = "rambotemplate2"
Step(function postTemplate1(err, res) {
var next = this;
var post_request = {
url: '/tiles/template?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(tmpl)
};
assert.response(server, post_request, {}, function(res) {
next(null, res);
});
},
function testCORS() {
var next = this;
assert.response(server, {
url: '/tiles/template/' + tmpl.name,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
},{
status: 200
}, function(res) {
var parsed = JSON.parse(res.body);
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
next(null);
});
},
function deleteTemplate(err) {
if ( err ) throw err;
var del_request = {
url: '/tiles/template/' + tmpl.name + '?api_key=1234',
method: 'DELETE',
headers: {host: 'localhost', 'Content-Type': 'application/json' }
}
var next = this;
assert.response(server, del_request, {},
function(res) { done(); });
}
);
});
test("can list templates", function(done) {
@@ -364,7 +415,7 @@ suite('template_api', function() {
function litsTemplates(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401, res.statusCode + ': ' + res.body);
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
'Missing error from response: ' + res.body);
@@ -577,7 +628,7 @@ suite('template_api', function() {
function getTemplate(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401, res.statusCode + ": " + res.body);
assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.error.match(/only.*authenticated.*user/i),
@@ -686,7 +737,7 @@ suite('template_api', function() {
function deleteTemplate(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401, res.statusCode + ": " + res.body);
assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
@@ -811,10 +862,10 @@ suite('template_api', function() {
assert.response(server, post_request, {},
function(res) { next(null, res); });
},
function instanciateAuth(err, res)
{
// See https://github.com/CartoDB/Windshaft-cartodb/issues/173
function instanciateForeignDB(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: '
+ res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
@@ -822,6 +873,25 @@ suite('template_api', function() {
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.error);
var post_request = {
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
method: 'POST',
headers: {host: 'foreign', 'Content-Type': 'application/json' },
data: JSON.stringify(template_params)
}
var next = this;
assert.response(server, post_request, {},
function(res) { next(null, res); });
},
function instanciateAuth(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/cannot instanciate/i),
'Unexpected error for forbidden instance : ' + parsed.error);
var post_request = {
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
method: 'POST',
@@ -858,14 +928,14 @@ suite('template_api', function() {
},
function fetchTileAuth(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/permission denied/i),
'Unexpected error for unauthorized instance '
+ '(expected /permission denied): ' + parsed.error);
+ '(expected /permission denied/): ' + parsed.error);
var get_request = {
url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1',
method: 'GET',
@@ -884,6 +954,33 @@ suite('template_api', function() {
assert.equal(res.headers['content-type'], "image/png");
return null;
},
// See https://github.com/CartoDB/Windshaft-cartodb/issues/172
function fetchTileForeignSignature(err, res) {
if ( err ) throw err;
var foreignsigned = layergroupid.replace(/[^@]*@/, 'foreign@');
var get_request = {
url: '/tiles/layergroup/' + foreignsigned + '/0/0/0.png?auth_token=valid1',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}
var next = this;
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkForeignSignerError(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 403,
'Unexpected error for authorized instance: '
+ res.statusCode + ' -- ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/cannot use/i),
'Unexpected error for unauthorized instance '
+ '(expected /cannot use/): ' + parsed.error);
return null;
},
function deleteTemplate(err)
{
if ( err ) throw err;
@@ -912,7 +1009,7 @@ suite('template_api', function() {
},
function checkTileDeleted(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected statusCode fetch tile after signature revokal: '
+ res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
@@ -1008,7 +1105,7 @@ suite('template_api', function() {
function instanciateAuth(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: '
+ res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
@@ -1052,7 +1149,7 @@ suite('template_api', function() {
},
function fetchTileAuth(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
@@ -1106,7 +1203,7 @@ suite('template_api', function() {
},
function checkTileDeleted(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected statusCode fetch tile after signature revokal: '
+ res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
@@ -1204,7 +1301,7 @@ suite('template_api', function() {
function instanciateAuth(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: '
+ res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
@@ -1248,7 +1345,7 @@ suite('template_api', function() {
},
function fetchAttributeAuth(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
@@ -1302,7 +1399,7 @@ suite('template_api', function() {
},
function checkTileDeleted(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 401,
assert.equal(res.statusCode, 403,
'Unexpected statusCode fetch tile after signature revokal: '
+ res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
@@ -1393,6 +1490,7 @@ suite('template_api', function() {
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template_params)
}
helper.checkNoCache(res);
var next = this;
assert.response(server, post_request, {},
function(res) { next(null, res); });
@@ -1464,13 +1562,16 @@ suite('template_api', function() {
assert.response(server, post_request, {},
function(res) { next(null, res); });
},
function instanciateAuth(err, res)
function checkInstanciation(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 200,
'Unexpected success instanciating template with no auth: '
+ res.statusCode + ': ' + res.body);
done();
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
helper.checkNoCache(res);
return null;
},
function finish(err) {
done(err);
}
);
});
@@ -1535,12 +1636,12 @@ suite('template_api', function() {
assert.response(server, post_request, {},
function(res) { next(null, res); });
},
function instanciateAuth(err, res)
function checkInstanciation(err, res)
{
if ( err ) throw err;
assert.equal(res.statusCode, 200,
'Unexpected success instanciating template with no auth: '
+ res.statusCode + ': ' + res.body);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
helper.checkNoCache(res);
return null;
},
function finish(err) {

View File

@@ -42,6 +42,9 @@ o.prototype.handleQuery = function(query, res) {
if ( query.q.match('SQLAPIERROR') ) {
res.statusCode = 400;
res.write(JSON.stringify({'error':'Some error occurred'}));
} else if ( query.q.match('SQLAPINOANSWER') ) {
console.log("SQLAPIEmulator will never respond, on request");
return;
} else if ( query.q.match('EPOCH.* as max') ) {
// This is the structure of the known query sent by tiler
var row = {

View File

@@ -76,7 +76,7 @@ if test x"$PREPARE_PGSQL" = xyes; then
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
psql ${TEST_DB}
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
fi

View File

@@ -6,12 +6,14 @@
*/
var _ = require('underscore');
var assert = require('assert');
var LZMA = require('lzma/lzma_worker.js').LZMA;
// set environment specific variables
global.settings = require(__dirname + '/../../config/settings');
global.environment = require(__dirname + '/../../config/environments/test');
_.extend(global.settings, global.environment);
process.env.NODE_ENV = 'test';
// Utility function to compress & encode LZMA
@@ -28,7 +30,17 @@ function lzma_compress_to_base64(payload, mode, callback) {
);
}
module.exports = {
lzma_compress_to_base64: lzma_compress_to_base64
// Check that the response headers do not request caching
// Throws on failure
function checkNoCache(res) {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
assert.ok(!res.headers.hasOwnProperty('cache-control')); // is this correct ?
assert.ok(!res.headers.hasOwnProperty('last-modified')); // is this correct ?
}
module.exports = {
lzma_compress_to_base64: lzma_compress_to_base64,
checkNoCache: checkNoCache
}

View File

@@ -0,0 +1,10 @@
{"version":"1.0.1",
"layers":[{
"type":"torque",
"options":{
"sql":"select 1 as id, ST_SetSRID(ST_MakePoint(0,0),3857) as the_geom_webmercator",
"cartocss":"Map{ -torque-time-attribute:'id'; -torque-aggregation-function:'count(id)'; -torque-frame-count:2; -torque-resolution:2}",
"cartocss_version": "2.1.1"
}
}]
}