Compare commits

...

92 Commits

Author SHA1 Message Date
Raul Ochoa
bc3baf3094 CDB-3256 Prepares 1.12.1 release 2014-06-24 16:26:57 +02:00
Raul Ochoa
8a91b5cfb5 CDB-3256 Fixes test related to cache in templated layergroup creation 2014-06-24 16:05:54 +02:00
Raul Ochoa
4cf1ddd6fc CDB-3256 Adds response and method references to fake request object 2014-06-24 15:52:47 +02:00
Raul Ochoa
cb781aeb00 CDB-3256 Prepares 1.12.0 Release 2014-06-24 14:24:14 +02:00
Raul Ochoa
2dd03e21e1 CDB-3256 fix test and adds a couple more of tests for testing the no-cache scenarios 2014-06-24 13:13:00 +02:00
Raul Ochoa
055bacbad7 Sets PGUSER environment variable 2014-06-24 12:39:57 +02:00
Raul Ochoa
46ae6d1fe4 Changes travis configuration to be similar to windshaft one 2014-06-24 12:39:46 +02:00
Raul Ochoa
5e73b12cf5 CDB-3256 adds headers based on affected tables when creating a layergroup via HTTP GET 2014-06-24 12:16:30 +02:00
Sandro Santilli
86c6f3eeac Wrap all json strings and string values in double-quotes 2014-06-09 12:19:16 +02:00
Raul Ochoa
8922ae3a45 adds document about metrics being tracked 2014-05-29 13:10:46 +02:00
Raul Ochoa
318e22e9fa Merge commit '4738b880a6c29a6d10dda3ad178f35a54bd576d3'
Conflicts:
	NEWS.md
	package.json
2014-05-07 19:07:20 +02:00
Raul Ochoa
4738b880a6 Prepares release 1.10.3 2014-05-07 18:28:10 +02:00
Sandro Santilli
49829f8935 Set default PostgreSQL application name to "cartodb_tiler" 2014-05-07 16:19:22 +02:00
Sandro Santilli
8e9d72982a Refuse to start if log_filename points to a non-existing directory
Closes #189
2014-05-07 11:03:25 +02:00
Raul Ochoa
d2f0180475 Merge remote-tracking branch 'rochoa/master' 2014-04-22 11:40:48 +02:00
Raul Ochoa
4da0b1e07c CDB-2096 Configures the CWD for log4js logger. 2014-04-22 10:52:59 +02:00
Sandro Santilli
5a4a35b665 Fix documentation for redis.max setting
Closes #192
2014-04-16 17:53:42 +02:00
Raul Ochoa
248cb4bd76 Removes unused dependency. 2014-04-11 15:14:59 +02:00
Sandro Santilli
140001f036 Update release document 2014-04-09 09:14:20 +02:00
Sandro Santilli
3917cac800 Add 1.10.2 section 2014-04-08 10:00:41 +02:00
Sandro Santilli
ee37da5b35 Prepare for 1.10.3 2014-04-08 10:00:10 +02:00
Sandro Santilli
6f8f3d2057 Release 1.10.2 2014-04-08 09:57:49 +02:00
Sandro Santilli
882ec65ba0 Use signer's map_key when contacting sql-api
Includes testcase.
Fixes #188
2014-04-08 09:44:49 +02:00
Sandro Santilli
7e1aba3368 Use signer's map_key when contacting sql-api
Includes testcase.
Fixes #188
2014-04-08 09:44:00 +02:00
Sandro Santilli
8aeadd1960 Fix show_style tool broken since 1.8.1 2014-03-31 12:55:30 +02:00
Sandro Santilli
a5b091eec8 Prepare for 1.10.2 2014-03-31 12:55:04 +02:00
Sandro Santilli
bbd4db6ddb Fix show_style tool broken since 1.8.1 2014-03-31 12:53:48 +02:00
Sandro Santilli
312194228a Stop duplicating global.environment as global.settings 2014-03-28 18:47:59 +01:00
Sandro Santilli
5c1125900b Add support for log_filename directive, reopen logfile on SIGHUP 2014-03-28 18:05:18 +01:00
Sandro Santilli
08b8741282 Reload log files on SIGUSR2
This is an attempt to play more nicely with logrotate
2014-03-28 17:06:44 +01:00
Sandro Santilli
e8367b765a Add persist_connection setting in .example configs 2014-03-24 17:40:43 +01:00
Sandro Santilli
91cd0df7b3 Typo in comment 2014-03-24 17:03:32 +01:00
Sandro Santilli
dff0a2aa1f Merge branch 'b1.10'
Fixes bogus caching of failing jsonp responses
2014-03-21 15:17:43 +01:00
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
5f30b9e798 Add an example of a slow mapconfig (using lots of data) 2014-03-20 18:19:30 +01:00
Sandro Santilli
7c892de7b1 Prepare for 1.11.0 2014-03-20 17:11:06 +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
30 changed files with 2204 additions and 292 deletions

View File

@@ -1,12 +1,27 @@
before_install:
- sudo mv /etc/apt/sources.list.d/pgdg-source.list* /tmp
- sudo apt-get -qq purge postgis* postgresql*
- sudo apt-add-repository --yes ppa:cartodb/postgresql-9.3
- sudo apt-add-repository --yes ppa:cartodb/gis
- sudo rm -Rf /var/lib/postgresql /etc/postgresql
- sudo apt-add-repository --yes ppa:mapnik/v2.1.0
- sudo apt-get update -q
- sudo apt-get install -q libmapnik-dev
- sudo apt-get update
- sudo apt-get install -y postgresql-9.3-postgis-2.1
- sudo apt-get install -y postgresql-contrib-9.3
- sudo apt-get install -y libmapnik-dev
- sudo apt-get install -y gdal-bin
- echo -e "local\tall\tall\ttrust\nhost\tall\tall\t127.0.0.1/32\ttrust\nhost\tall\tall\t::1/128\ttrust" |sudo tee /etc/postgresql/9.3/main/pg_hba.conf
- sudo service postgresql restart
- createdb template_postgis
- psql -c "CREATE EXTENSION postgis" template_postgis
before_script:
# 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
- NPROCS=1 JOBS=1 PGUSER=postgres
language: node_js
node_js:

View File

@@ -1,13 +1,20 @@
1. Ensure proper version in package.json
2. Ensure NEWS section exists for the new version, review it, add release date
3. Drop npm-shrinkwrap.json
4. Run npm install
5. Test (make check or npm test), fix if broken before proceeding
6. Run npm shrinkwrap
7. Set "from" in npm-shrinkwrap.json for known packages
(windshaft, node-varnish, grainstore...)
8. Commit package.json, npm-shrinwrap.json, NEWS
9. git tag -a Major.Minor.Patch # use NEWS section as content
10. Announce
11. Stub NEWS/package for next version
1. Test (make clean all check), fix if broken before proceeding
2. Ensure proper version in package.json
3. Ensure NEWS section exists for the new version, review it, add release date
4. Drop npm-shrinkwrap.json
5. Run npm shrinkwrap to recreate npm-shrinkwrap.json
6. Commit package.json, npm-shrinwrap.json, NEWS
7. git tag -a Major.Minor.Patch # use NEWS section as content
8. Announce on cartodb@googlegroups.com
9. Stub NEWS/package for next version
Versions:
Bugfix releases increment Patch component of version.
Feature releases increment Minor and set Patch to zero.
If backward compatibility is broken, increment Major and
set to zero Minor and Patch.
Branches named 'b<Major>.<Minor>' are kept for any critical
fix that might need to be shipped before next feature release
is ready.

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.

127
NEWS.md
View File

@@ -1,3 +1,130 @@
1.12.1 -- 2014-06-24
--------------------
Enhancements:
- Caches layergroup and sets X-Cache-Channel in GET requests also in named maps
1.12.0 -- 2014-06-24
--------------------
New features:
- Caches layergroup and sets X-Cache-Channel in GET requests
1.11.1 -- 2014-05-07
--------------------
Enhancements:
- Upgrade Windshaft to 0.21.0, see
http://github.com/CartoDB/Windshaft/blob/0.21.0/NEWS
1.11.0 -- 2014-04-28
--------------------
New features:
- Add support for log_filename directive
- Reopen log file on SIGHUP, for better logrotate integration
Enhancements:
- Set default PostgreSQL application name to "cartodb_tiler"
1.10.2 -- 2014-04-08
--------------------
Bug fixes:
- Fix show_style tool broken since 1.8.1
- Fix X-Cache-Channel of tiles accessed via signed token (#188)
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
-------------------

69
app.js
View File

@@ -7,31 +7,62 @@
* environments: [development, production]
*/
var path = require('path'),
fs = require('fs')
;
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);
}
var _ = require('underscore')
, Step = require('step')
;
var _ = require('underscore');
// set environment specific variables
global.settings = require(__dirname + '/config/settings');
global.environment = require(__dirname + '/config/environments/' + ENV);
_.extend(global.settings, global.environment);
global.log4js = require('log4js')
log4js.configure({
appenders: [
{ type: "console", layout: { type:'basic' } }
],
log4js_config = {
appenders: [],
replaceConsole:true
});
};
if ( global.environment.log_filename ) {
var logdir = path.dirname(global.environment.log_filename);
// See cwd inlog4js.configure call below
logdir = path.resolve(__dirname, logdir);
if ( ! fs.existsSync(logdir) ) {
console.error("Log filename directory does not exist: " + logdir);
process.exit(1);
}
console.log("Logs will be written to " + global.environment.log_filename);
log4js_config.appenders.push(
{ type: "file", filename: global.environment.log_filename }
);
} else {
log4js_config.appenders.push(
{ type: "console", layout: { type:'basic' } }
);
}
if ( global.environment.rollbar ) {
log4js_config.appenders.push({
type: __dirname + "/lib/cartodb/log4js_rollbar.js",
options: global.environment.rollbar
});
}
log4js.configure(log4js_config, { cwd: __dirname });
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 +79,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 +97,11 @@ process.on('SIGUSR2', function() {
ws.dumpCacheStats();
});
process.on('SIGHUP', function() {
log4js.configure(log4js_config);
console.log('Log files reloaded');
});
process.on('uncaughtException', function(err) {
logger.error('Uncaught exception: ' + err.stack);
});

View File

@@ -31,11 +31,15 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in miliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
,log_filename: 'logs/node-windshaft.log'
// Templated database username for authorized user
// Supported labels: 'user_id' (read from redis)
,postgres_auth_user: 'development_cartodb_user_<%= user_id %>'
@@ -58,13 +62,22 @@ var config = {
*/
row_limit: 65535,
simplify_geometries: true,
/*
* Set persist_connection to false if you want
* database connections to be closed on renderer
* expiration (1 minute after last use).
* Setting to true (the default) would never
* close any connection for the server's lifetime
*/
persist_connection: false,
max_size: 500
}
,mapnik_version: undefined
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'dev.'
prefix: 'dev.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -83,9 +96,9 @@ var config = {
// Max number of connections in each pool.
// Users will be put on a queue when the limit is hit.
// Set to maxConnection to have no possible queues.
// There are currently 3 pools involved in serving
// There are currently 2 pools involved in serving
// windshaft-cartodb requests so multiply this number
// by 3 to know how many possible connections will be
// by 2 to know how many possible connections will be
// kept open by the server. The default is 50.
max: 50,
idleTimeoutMillis: 1, // idle time before dropping connection
@@ -106,7 +119,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

@@ -31,11 +31,15 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in miliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
,log_filename: 'logs/node-windshaft.log'
// Templated database username for authorized user
// Supported labels: 'user_id' (read from redis)
,postgres_auth_user: 'cartodb_user_<%= user_id %>'
@@ -51,6 +55,14 @@ var config = {
port: 6432,
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
row_limit: 65535,
/*
* Set persist_connection to false if you want
* database connections to be closed on renderer
* expiration (1 minute after last use).
* Setting to true (the default) would never
* close any connection for the server's lifetime
*/
persist_connection: false,
simplify_geometries: true,
max_size: 500
}
@@ -59,6 +71,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: {
@@ -77,9 +90,9 @@ var config = {
// Max number of connections in each pool.
// Users will be put on a queue when the limit is hit.
// Set to maxConnection to have no possible queues.
// There are currently 3 pools involved in serving
// There are currently 2 pools involved in serving
// windshaft-cartodb requests so multiply this number
// by 3 to know how many possible connections will be
// by 2 to know how many possible connections will be
// kept open by the server. The default is 50.
max: 50,
idleTimeoutMillis: 30000, // idle time before dropping connection
@@ -100,7 +113,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 +128,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

@@ -31,11 +31,15 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in miliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
,log_filename: 'logs/node-windshaft.log'
// Templated database username for authorized user
// Supported labels: 'user_id' (read from redis)
,postgres_auth_user: 'cartodb_staging_user_<%= user_id %>'
@@ -52,13 +56,22 @@ var config = {
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
row_limit: 65535,
simplify_geometries: true,
/*
* Set persist_connection to false if you want
* database connections to be closed on renderer
* expiration (1 minute after last use).
* Setting to true (the default) would never
* close any connection for the server's lifetime
*/
persist_connection: false,
max_size: 500
}
,mapnik_version: undefined
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'stage.:host.'
prefix: 'stage.:host.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -77,9 +90,9 @@ var config = {
// Max number of connections in each pool.
// Users will be put on a queue when the limit is hit.
// Set to maxConnection to have no possible queues.
// There are currently 3 pools involved in serving
// There are currently 2 pools involved in serving
// windshaft-cartodb requests so multiply this number
// by 3 to know how many possible connections will be
// by 2 to know how many possible connections will be
// kept open by the server. The default is 50.
max: 50,
idleTimeoutMillis: 30000, // idle time before dropping connection
@@ -100,7 +113,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 +128,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

@@ -31,11 +31,15 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in miliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
//,log_filename: 'logs/node-windshaft.log'
// Templated database username for authorized user
// Supported labels: 'user_id' (read from redis)
,postgres_auth_user: 'test_windshaft_cartodb_user_<%= user_id %>'
@@ -52,13 +56,22 @@ var config = {
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
row_limit: 65535,
simplify_geometries: true,
/*
* Set persist_connection to false if you want
* database connections to be closed on renderer
* expiration (1 minute after last use).
* Setting to true (the default) would never
* close any connection for the server's lifetime
*/
persist_connection: false,
max_size: 500
}
,mapnik_version: ''
,statsd: {
host: 'localhost',
port: 8125,
prefix: 'test.:host.'
prefix: 'test.:host.',
cacheDns: true
// support all allowed node-statsd options
}
,renderer: {
@@ -77,9 +90,9 @@ var config = {
// Max number of connections in each pool.
// Users will be put on a queue when the limit is hit.
// Set to maxConnection to have no possible queues.
// There are currently 3 pools involved in serving
// There are currently 2 pools involved in serving
// windshaft-cartodb requests so multiply this number
// by 3 to know how many possible connections will be
// by 2 to know how many possible connections will be
// kept open by the server. The default is 50.
max: 50,
idleTimeoutMillis: 1, // idle time before dropping connection
@@ -102,7 +115,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: '',

View File

@@ -1 +0,0 @@
module.exports.oneDay = 86400000;

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.

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

@@ -0,0 +1,293 @@
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)
// only (required and non empty) for "token" method
valid_tokens: ["auth_token1","auth_token2"]
},
// 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"
}
```

50
docs/metrics.md Normal file
View File

@@ -0,0 +1,50 @@
Windshaft-cartodb metrics
=========================
See [Windshaft metrics documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/metrics.md) to understand the full picture.
The next list includes the API endpoints, each endpoint may have several inner timers, some of them are displayed within this list as subitems. Find the description for them in the Inner timers section.
## Timers
- **windshaft-cartodb.get_infowindow**: time to retrieve an infowindow popup
- **windshaft-cartodb.get_map_metadata**: time to retrieve metadata for embedded maps
- **windshaft-cartodb.flush_cache**: time to flush the tile and sql cache
- **windshaft-cartodb.get_template**: time to retrieve an specific template
- **windshaft-cartodb.delete_template**: time to delete an specific template
- **windshaft-cartodb.get_template_list**: time to retrieve the list of owned templates
- **windshaft-cartodb.instance_template_post**: time to create a template via HTTP POST
- **windshaft-cartodb.instance_template_get**: time to create a template via HTTP GET
+ TemplateMaps_instance
+ createLayergroup
There are some endpoints that are not being tracked:
- Adding a template
- Updating a template
### Inner timers
Again, each inner timer may have several inner timers.
- **addCacheChannel**: time to add X-Cache-Channel header based on table last modifications
- **LZMA decompress**: time to decompress request params with LZMA
- **TemplateMaps_instance**: time to retrieve a map template instance, see *getTemplate* and *authorizedByCert*
- **affectedTables**: time to check what are the affected tables for adding the cache channel, see *addCacheChannel*
- **authorize**: time to authorize a request, see *authorizedByAPIKey*, *authorizedByCert*, *authorizedBySigner*
- **authorizedByAPIKey**: time to authorize using an API KEY
- **authorizedByCert**: time to authorize a request by a cert, see [signed map](https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps)
- **authorizedBySigner**: time to authorize a request for a [signed map](https://github.com/CartoDB/Windshaft-cartodb/wiki/Signed-maps)
- **cartoData.getTableGeometryType**: time to retrieve from redis the geom type for a given table
- **cors**: time to set the CORS headers
- **findLastUpdated**: time to retrieve the last update time for a list of tables, see *affectedTables*
- **fingerPrint**: time to create a fingerprint for a signed map
- **generateCacheChannel**: time to generate the headers for the cache channel based on the request, see *addCacheChannel*
- **getSignerMapKey**: time to retrieve from redis the authorized key for a signed map
- **getTablePrivacy**: time to retrieve from redis the privacy of a table
- **getTemplate**: time to retrieve from redis the template for a map
- **getUserMapKey**: time to retrieve from redis the user key for a map
- **incMapviewCount**: time to incremenent in redis the map views
- **mapStore_load**: time to retrieve from redis a map configuration
- **req2params.setup**: time to prepare the params from a request, see *req2params* in Windshaft documentation
- **setDBAuth**: time to retrieve from redis and set db user and db password from a user
- **setDBConn**: time to retrieve from redis and set db host and db name from a user
- **setDBParams**: time to prepare all db params to be able to connect/query a database, see *setDBAuth* and *setDBConn*
- **signMap**: time to sign in redis layergroup for a map, see signed maps
- **tablePrivacy_getUserDBName**: time to retrieve from redis the database for a user

View File

@@ -11,6 +11,9 @@ var _ = require('underscore')
, os = require('os')
;
if ( ! process.env['PGAPPNAME'] )
process.env['PGAPPNAME']='cartodb_tiler';
var CartodbWindshaft = function(serverOptions) {
var debug = global.environment.debug;
@@ -40,6 +43,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 +67,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 +107,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 +161,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 +185,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 +210,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 +224,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 +273,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 +316,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 +333,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 +376,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 +394,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 +430,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 +445,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 +506,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 +537,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) {
@@ -498,6 +557,8 @@ var CartodbWindshaft = function(serverOptions) {
if ( err ) throw err;
layergroup = instance;
fakereq = { query: {}, params: {}, headers: _.clone(req.headers),
method: req.method,
res: res,
profiler: req.profiler
};
ws.setDBParams(cdbuser, fakereq.params, this);
@@ -564,9 +625,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);
});
};
@@ -279,8 +285,31 @@ module.exports = function(){
}
return [req.params.table];
}
var username = that.userByReq(req);
me.affectedTables(username, req.params.map_key, sql, this);
var user, key;
var next = this;
Step (
function findUserKey() {
if ( req.params.hasOwnProperty('_authorizedBySigner') ) {
user = req.params._authorizedBySigner;
cartoData.getUserMapKey(user, this);
} else {
user = that.userByReq(req);
key = req.params.map_key || req.params.api_key;
return null;
}
},
function getAffected(err, data) {
if ( err ) throw err;
if ( data ) {
if ( req.profiler ) req.profiler.done('getSignerMapKey');
key = data;
}
me.affectedTables(user, key, sql, this); // in addCacheChannel
},
function finish(err, data) {
next(err,data);
}
);
},
function buildCacheChannel(err, tableNames) {
if ( err ) throw err;
@@ -369,6 +398,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 +426,45 @@ 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); // in afterLayergroupCreate
},
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;
if (req.res && req.method == 'GET') {
var res = req.res;
if ( req.query && req.query.cache_policy == 'persist' ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
} else {
var ttl = global.environment.varnish.ttl || 86400;
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
}
res.header('Last-Modified', (new Date()).toUTCString());
res.header('X-Cache-Channel', 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 +549,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,16 +686,26 @@ 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;
}
// Authorized by "signed_by" !
_.extend(req.params, { _authorizedBySigner: signed_by });
that.setDBAuth(signed_by, req.params, function(err) {
if (req.profiler) req.profiler.done('setDBAuth');
callback(err, true); // authorized (or error)
@@ -646,7 +723,7 @@ module.exports = function(){
},
function(err, privacy){
if (req.profiler) req.profiler.done('getTablePrivacy');
callback(err, privacy);
callback(err, privacy !== "0");
}
);
};
@@ -694,6 +771,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 +781,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 +806,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);
},

763
npm-shrinkwrap.json generated
View File

@@ -1,32 +1,60 @@
{
"name": "windshaft-cartodb",
"version": "1.8.2",
"version": "1.12.1",
"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.21.0",
"from": "http://github.com/CartoDB/Windshaft/tarball/0.21.0",
"dependencies": {
"grainstore": {
"version": "0.18.0",
"version": "0.18.1",
"dependencies": {
"mapnik-reference": {
"version": "5.0.7"
},
"millstone": {
"version": "0.6.11",
"carto": {
"version": "0.9.5-cdb2",
"from": "http://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
"dependencies": {
"underscore": {
"version": "1.5.2"
"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.10"
}
}
}
}
},
"mapnik-reference": {
"version": "5.0.9"
},
"millstone": {
"version": "0.6.14",
"dependencies": {
"underscore": {
"version": "1.6.0"
},
"request": {
"version": "2.26.0",
"version": "2.34.0",
"dependencies": {
"qs": {
"version": "0.6.6"
@@ -37,6 +65,33 @@
"forever-agent": {
"version": "0.5.2"
},
"node-uuid": {
"version": "1.4.1"
},
"tough-cookie": {
"version": "0.12.1",
"dependencies": {
"punycode": {
"version": "1.2.4"
}
}
},
"form-data": {
"version": "0.1.4",
"dependencies": {
"combined-stream": {
"version": "0.0.5",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
}
}
},
"async": {
"version": "0.9.0"
}
}
},
"tunnel-agent": {
"version": "0.3.0"
},
@@ -54,6 +109,9 @@
}
}
},
"oauth-sign": {
"version": "0.3.0"
},
"hawk": {
"version": "1.0.0",
"dependencies": {
@@ -71,50 +129,252 @@
}
}
},
"aws-sign": {
"version": "0.3.0"
"aws-sign2": {
"version": "0.5.0"
}
}
},
"srs": {
"version": "0.4.3",
"dependencies": {
"nan": {
"version": "1.2.0"
},
"oauth-sign": {
"version": "0.3.0"
},
"cookie-jar": {
"version": "0.3.0"
},
"node-uuid": {
"version": "1.4.1"
},
"form-data": {
"version": "0.1.2",
"node-pre-gyp": {
"version": "0.5.17",
"dependencies": {
"combined-stream": {
"version": "0.0.4",
"nopt": {
"version": "2.2.1",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
"abbrev": {
"version": "1.0.5"
}
}
},
"async": {
"version": "0.2.10"
"npmlog": {
"version": "0.0.6",
"dependencies": {
"ansi": {
"version": "0.2.1"
}
}
},
"request": {
"version": "2.36.0",
"dependencies": {
"qs": {
"version": "0.6.6"
},
"json-stringify-safe": {
"version": "5.0.0"
},
"mime": {
"version": "1.2.11"
},
"forever-agent": {
"version": "0.5.2"
},
"node-uuid": {
"version": "1.4.1"
},
"tough-cookie": {
"version": "0.12.1",
"dependencies": {
"punycode": {
"version": "1.2.4"
}
}
},
"form-data": {
"version": "0.1.2",
"dependencies": {
"combined-stream": {
"version": "0.0.4",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
}
}
},
"async": {
"version": "0.2.10"
}
}
},
"tunnel-agent": {
"version": "0.4.0"
},
"http-signature": {
"version": "0.10.0",
"dependencies": {
"assert-plus": {
"version": "0.1.2"
},
"asn1": {
"version": "0.1.11"
},
"ctype": {
"version": "0.5.2"
}
}
},
"oauth-sign": {
"version": "0.3.0"
},
"hawk": {
"version": "1.0.0",
"dependencies": {
"hoek": {
"version": "0.9.1"
},
"boom": {
"version": "0.4.2"
},
"cryptiles": {
"version": "0.2.2"
},
"sntp": {
"version": "0.2.4"
}
}
},
"aws-sign2": {
"version": "0.5.0"
}
}
},
"semver": {
"version": "2.3.0"
},
"tar": {
"version": "0.1.19",
"dependencies": {
"inherits": {
"version": "2.0.1"
},
"block-stream": {
"version": "0.0.7"
},
"fstream": {
"version": "0.1.25",
"dependencies": {
"mkdirp": {
"version": "0.3.5"
},
"graceful-fs": {
"version": "2.0.3"
}
}
}
}
},
"tar-pack": {
"version": "2.0.0",
"dependencies": {
"uid-number": {
"version": "0.0.3"
},
"once": {
"version": "1.1.1"
},
"debug": {
"version": "0.7.4"
},
"fstream": {
"version": "0.1.25",
"dependencies": {
"mkdirp": {
"version": "0.3.5"
},
"graceful-fs": {
"version": "2.0.3"
},
"inherits": {
"version": "2.0.1"
}
}
},
"fstream-ignore": {
"version": "0.0.7",
"dependencies": {
"minimatch": {
"version": "0.2.14",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"inherits": {
"version": "2.0.1"
}
}
},
"readable-stream": {
"version": "1.0.27-1",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"inherits": {
"version": "2.0.1"
}
}
},
"graceful-fs": {
"version": "1.2.3"
}
}
},
"mkdirp": {
"version": "0.5.0",
"dependencies": {
"minimist": {
"version": "0.0.8"
}
}
},
"rc": {
"version": "0.4.0",
"dependencies": {
"minimist": {
"version": "0.0.10"
},
"deep-extend": {
"version": "0.2.10"
},
"strip-json-comments": {
"version": "0.1.3"
},
"ini": {
"version": "1.1.0"
}
}
},
"rimraf": {
"version": "2.2.8"
}
}
}
}
},
"srs": {
"version": "0.3.10"
},
"zipfile": {
"version": "0.4.3"
},
"sqlite3": {
"version": "2.2.0",
"version": "0.5.2",
"dependencies": {
"node-pre-gyp": {
"version": "0.2.6",
"version": "0.5.8",
"dependencies": {
"nopt": {
"version": "2.1.2",
"version": "2.2.0",
"dependencies": {
"abbrev": {
"version": "1.0.4"
@@ -129,8 +389,92 @@
}
}
},
"request": {
"version": "2.34.0",
"dependencies": {
"qs": {
"version": "0.6.6"
},
"json-stringify-safe": {
"version": "5.0.0"
},
"forever-agent": {
"version": "0.5.2"
},
"node-uuid": {
"version": "1.4.1"
},
"mime": {
"version": "1.2.11"
},
"tough-cookie": {
"version": "0.12.1",
"dependencies": {
"punycode": {
"version": "1.2.4"
}
}
},
"form-data": {
"version": "0.1.2",
"dependencies": {
"combined-stream": {
"version": "0.0.4",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
}
}
},
"async": {
"version": "0.2.10"
}
}
},
"tunnel-agent": {
"version": "0.3.0"
},
"http-signature": {
"version": "0.10.0",
"dependencies": {
"assert-plus": {
"version": "0.1.2"
},
"asn1": {
"version": "0.1.11"
},
"ctype": {
"version": "0.5.2"
}
}
},
"oauth-sign": {
"version": "0.3.0"
},
"hawk": {
"version": "1.0.0",
"dependencies": {
"hoek": {
"version": "0.9.1"
},
"boom": {
"version": "0.4.2"
},
"cryptiles": {
"version": "0.2.2"
},
"sntp": {
"version": "0.2.4"
}
}
},
"aws-sign2": {
"version": "0.5.0"
}
}
},
"semver": {
"version": "2.1.0"
"version": "2.2.1"
},
"tar": {
"version": "0.1.19",
@@ -145,7 +489,7 @@
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
}
}
}
@@ -167,7 +511,7 @@
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
},
"inherits": {
"version": "2.0.1"
@@ -180,6 +524,9 @@
"minimatch": {
"version": "0.2.14",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
@@ -191,10 +538,19 @@
}
},
"readable-stream": {
"version": "1.0.25-1",
"version": "1.0.26-4",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"inherits": {
"version": "2.0.1"
}
}
},
@@ -203,33 +559,231 @@
}
}
},
"aws-sdk": {
"version": "2.0.0-rc9",
"mkdirp": {
"version": "0.3.5"
},
"rc": {
"version": "0.3.4",
"dependencies": {
"xml2js": {
"version": "0.2.4",
"dependencies": {
"sax": {
"version": "0.6.0"
}
}
"minimist": {
"version": "0.0.8"
},
"xmlbuilder": {
"version": "0.4.2"
"deep-extend": {
"version": "0.2.8"
},
"ini": {
"version": "1.1.0"
}
}
},
"rc": {
"version": "0.3.3",
"rimraf": {
"version": "2.2.6"
}
}
}
}
},
"sqlite3": {
"version": "2.2.3",
"dependencies": {
"node-pre-gyp": {
"version": "0.5.9",
"dependencies": {
"nopt": {
"version": "2.2.0",
"dependencies": {
"optimist": {
"version": "0.3.7",
"abbrev": {
"version": "1.0.4"
}
}
},
"npmlog": {
"version": "0.0.6",
"dependencies": {
"ansi": {
"version": "0.2.1"
}
}
},
"request": {
"version": "2.34.0",
"dependencies": {
"qs": {
"version": "0.6.6"
},
"json-stringify-safe": {
"version": "5.0.0"
},
"forever-agent": {
"version": "0.5.2"
},
"node-uuid": {
"version": "1.4.1"
},
"mime": {
"version": "1.2.11"
},
"tough-cookie": {
"version": "0.12.1",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
"punycode": {
"version": "1.2.4"
}
}
},
"form-data": {
"version": "0.1.2",
"dependencies": {
"combined-stream": {
"version": "0.0.4",
"dependencies": {
"delayed-stream": {
"version": "0.0.5"
}
}
},
"async": {
"version": "0.2.10"
}
}
},
"tunnel-agent": {
"version": "0.3.0"
},
"http-signature": {
"version": "0.10.0",
"dependencies": {
"assert-plus": {
"version": "0.1.2"
},
"asn1": {
"version": "0.1.11"
},
"ctype": {
"version": "0.5.2"
}
}
},
"oauth-sign": {
"version": "0.3.0"
},
"hawk": {
"version": "1.0.0",
"dependencies": {
"hoek": {
"version": "0.9.1"
},
"boom": {
"version": "0.4.2"
},
"cryptiles": {
"version": "0.2.2"
},
"sntp": {
"version": "0.2.4"
}
}
},
"aws-sign2": {
"version": "0.5.0"
}
}
},
"semver": {
"version": "2.2.1"
},
"tar": {
"version": "0.1.19",
"dependencies": {
"inherits": {
"version": "2.0.1"
},
"block-stream": {
"version": "0.0.7"
},
"fstream": {
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.3"
}
}
}
}
},
"tar-pack": {
"version": "2.0.0",
"dependencies": {
"uid-number": {
"version": "0.0.3"
},
"once": {
"version": "1.1.1"
},
"debug": {
"version": "0.7.4"
},
"fstream": {
"version": "0.1.25",
"dependencies": {
"graceful-fs": {
"version": "2.0.3"
},
"inherits": {
"version": "2.0.1"
}
}
},
"fstream-ignore": {
"version": "0.0.7",
"dependencies": {
"minimatch": {
"version": "0.2.14",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"inherits": {
"version": "2.0.1"
}
}
},
"readable-stream": {
"version": "1.0.26-4",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"inherits": {
"version": "2.0.1"
}
}
},
"graceful-fs": {
"version": "1.2.3"
}
}
},
"mkdirp": {
"version": "0.3.5"
},
"rc": {
"version": "0.3.4",
"dependencies": {
"minimist": {
"version": "0.0.8"
},
"deep-extend": {
"version": "0.2.8"
},
@@ -258,7 +812,7 @@
"version": "0.0.2"
},
"minimist": {
"version": "0.0.7"
"version": "0.0.10"
}
}
}
@@ -276,7 +830,7 @@
"version": "1.9.2",
"dependencies": {
"formidable": {
"version": "1.0.14"
"version": "1.0.15"
}
}
},
@@ -308,11 +862,8 @@
}
},
"tilelive-mapnik": {
"version": "0.6.5",
"version": "0.6.9",
"dependencies": {
"eio": {
"version": "0.2.2"
},
"mime": {
"version": "1.2.11"
},
@@ -321,18 +872,15 @@
}
}
},
"lru-cache": {
"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"
},
"mapnik-reference": {
"version": "5.0.7"
"version": "5.0.9"
},
"xml2js": {
"version": "0.2.8",
@@ -349,19 +897,15 @@
"version": "0.0.2"
},
"minimist": {
"version": "0.0.7"
"version": "0.0.10"
}
}
}
}
},
"underscore.string": {
"version": "1.1.6",
"dependencies": {
"underscore": {
"version": "1.1.7"
}
}
"step-profiler": {
"version": "0.0.1",
"from": "git://github.com/CartoDB/node-step-profiler.git#0.0.1"
},
"pg": {
"version": "2.6.2",
@@ -392,52 +936,80 @@
"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"
},
"hiredis": {
"version": "0.1.16",
"version": "0.1.17",
"dependencies": {
"bindings": {
"version": "1.1.1"
"version": "1.2.0"
},
"nan": {
"version": "1.1.2"
}
}
}
}
},
"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"
},
"log4js": {
"version": "0.6.10",
"version": "0.6.14",
"dependencies": {
"async": {
"version": "0.1.15"
},
"readable-stream": {
"version": "1.0.25-1",
"version": "1.0.27-1",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"inherits": {
"version": "2.0.1"
}
}
}
}
},
"rollbar": {
"version": "0.3.8",
"dependencies": {
"node-uuid": {
"version": "1.4.1"
},
"lru-cache": {
"version": "2.2.4"
},
"json-stringify-safe": {
"version": "5.0.0"
}
}
},
"redis": {
"version": "0.8.6"
},
"strftime": {
"version": "0.6.2"
},
"semver": {
"version": "1.1.4"
},
"strftime": {
"version": "0.6.2"
},
"mocha": {
"version": "1.14.0",
"dependencies": {
@@ -462,7 +1034,12 @@
"version": "1.0.7"
},
"debug": {
"version": "0.7.4"
"version": "1.0.2",
"dependencies": {
"ms": {
"version": "0.6.2"
}
}
},
"mkdirp": {
"version": "0.3.5"
@@ -482,7 +1059,7 @@
}
},
"graceful-fs": {
"version": "2.0.1"
"version": "2.0.3"
},
"inherits": {
"version": "2.0.1"

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "1.8.2",
"version": "1.12.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.21.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

@@ -5,6 +5,8 @@ OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
OPT_DROP_REDIS=yes # drop the redis test environment
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
export PGAPPNAME=cartodb_tiler_tester
cd $(dirname $0)
BASEDIR=$(pwd)
cd -

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,134 @@ 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();
}
)
});
test("get creation requests has 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.checkCache(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("get creation has no cache if sql is bogus", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: 'select bogus(0,0) as the_geom_webmercator',
cartocss: '#layer { polygon-fill: red; }',
cartocss_version: '2.0.1'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res) {
assert.notEqual(res.statusCode, 200);
helper.checkNoCache(res);
done();
});
});
test("get creation has no cache if cartocss is not valid", 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 { invalid-rule:red; }',
cartocss_version: '2.0.1'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res) {
assert.notEqual(res.statusCode, 200);
helper.checkNoCache(res);
done();
});
});
test("layergroup can hold substitution tokens", function(done) {
var layergroup = {
@@ -493,11 +631,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 +749,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 +765,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 +781,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 +1266,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']);
@@ -872,8 +889,8 @@ suite('server', function() {
// See https://github.com/CartoDB/Windshaft-cartodb/issues/89
test("get'ing a tile with a user-specific database password", function(done){
var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'});
var backupDBPass = global.settings.postgres_auth_pass;
global.settings.postgres_auth_pass = '<%= user_password %>';
var backupDBPass = global.environment.postgres_auth_pass;
global.environment.postgres_auth_pass = '<%= user_password %>';
Step (
function() {
var next = this;
@@ -900,7 +917,7 @@ suite('server', function() {
return null
},
function finish(err) {
global.settings.postgres_auth_pass = backupDBPass;
global.environment.postgres_auth_pass = backupDBPass;
done(err);
}
);
@@ -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,12 +9,19 @@ 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';
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
var ServerOptions = require(__dirname + '/../../lib/cartodb/server_options');
var serverOptions = ServerOptions();
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
@@ -69,7 +75,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 +309,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 +416,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 +629,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 +738,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 +863,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 +874,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 +929,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 +955,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 +1010,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 +1106,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 +1150,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'),
@@ -1070,12 +1168,37 @@ suite('template_api', function() {
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkTile(err, res) {
function checkTile_fetchOnRestart(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 200,
'Unexpected error for authorized instance: '
+ res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
var cc = res.headers['x-cache-channel'];
assert.ok(cc);
assert.ok(cc.match, /ciao/, cc);
// hack simulating restart...
serverOptions = ServerOptions(); // need to clean channel cache
server = new CartodbWindshaft(serverOptions);
var get_request = {
url: '/tiles/layergroup/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}
var next = this;
assert.response(server, get_request, {},
function(res) { next(null, res); });
},
function checkCacheChannel(err, res) {
if ( err ) throw err;
assert.equal(res.statusCode, 200,
'Unexpected error for authorized instance: '
+ res.statusCode + ' -- ' + res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
var cc = res.headers['x-cache-channel'];
assert.ok(cc, "Missing X-Cache-Channel on fetch-after-restart");
assert.ok(cc.match, /ciao/, cc);
return null;
},
function deleteTemplate(err)
@@ -1106,7 +1229,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 +1327,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 +1371,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 +1425,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 +1516,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 +1588,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.checkCache(res);
return null;
},
function finish(err) {
done(err);
}
);
});
@@ -1535,12 +1662,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,12 @@
*/
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 +28,30 @@ 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 ?
}
/**
* Check that the response headers do not request caching
* @see checkNoCache
* @param res
*/
function checkCache(res) {
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
assert.ok(res.headers.hasOwnProperty('cache-control'));
assert.ok(res.headers.hasOwnProperty('last-modified'));
}
module.exports = {
lzma_compress_to_base64: lzma_compress_to_base64,
checkNoCache: checkNoCache,
checkCache: checkCache
};

View File

@@ -0,0 +1,11 @@
{"version":"1.0.1",
"layers":[{
"type":"cartodb",
"options":{
"sql":"select 1 as id, ST_Transform(ST_SetSRID(ST_MakePoint(x/1000,x/2000),4326),3857) as the_geom_webmercator FROM generate_series(-170000,170000) x",
"cartocss":"#style{ marker-width: 12;}",
"cartocss_version":"2.1.1",
"Interactivity":"id"
}
}]
}

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"
}
}]
}

View File

@@ -38,7 +38,8 @@ if ( ! username ) usage(me, 1);
console.log("Using environment " + ENV);
global.environment = require('../config/environments/' + ENV);
var serverOptions = require('../lib/cartodb/server_options'); // _after_ setting global.environment
// _after_ setting global.environment
var serverOptions = require('../lib/cartodb/server_options')();
var client;
var dbname;