diff --git a/app.js b/app.js index 018893ad..3f80ca02 100755 --- a/app.js +++ b/app.js @@ -89,11 +89,11 @@ var server = cartodbWindshaft(serverOptions); // 1 is good if you have max 16 filedescriptors server.maxConnections = global.environment.maxConnections || 128; -server.listen(serverOptions.bind.port, serverOptions.bind.host); +var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host); var version = require("./package").version; -server.on('listening', function() { +listener.on('listening', function() { console.log( "Windshaft tileserver %s started on %s:%s PID=%d (%s)", version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT diff --git a/lib/cartodb/cache/surrogate_keys_cache.js b/lib/cartodb/cache/surrogate_keys_cache.js index 237ac906..907e6345 100644 --- a/lib/cartodb/cache/surrogate_keys_cache.js +++ b/lib/cartodb/cache/surrogate_keys_cache.js @@ -17,8 +17,8 @@ module.exports = SurrogateKeysCache; */ SurrogateKeysCache.prototype.tag = function(response, cacheObject) { var newKey = cacheObject.key(); - response.header('Surrogate-Key', appendSurrogateKey( - response.header('Surrogate-Key'), + response.set('Surrogate-Key', appendSurrogateKey( + response.get('Surrogate-Key'), Array.isArray(newKey) ? cacheObject.key().join(' ') : newKey )); diff --git a/lib/cartodb/controllers/base.js b/lib/cartodb/controllers/base.js index ac7a2e6a..419bdec6 100644 --- a/lib/cartodb/controllers/base.js +++ b/lib/cartodb/controllers/base.js @@ -150,23 +150,33 @@ BaseController.prototype.req2params = function(req, callback){ }; // jshint maxcomplexity:6 - -BaseController.prototype.send = function(req, res, args) { - if (global.environment && global.environment.api_hostname) { - res.header('X-Served-By-Host', global.environment.api_hostname); - } - - if (req.params && req.params.dbhost) { - res.header('X-Served-By-DB-Host', req.params.dbhost); +// jshint maxcomplexity:9 +BaseController.prototype.send = function(req, res, body, status, headers) { + if (req.params.dbhost) { + res.set('X-Served-By-DB-Host', req.params.dbhost); } if (req.profiler) { - res.header('X-Tiler-Profiler', req.profiler.toJSONString()); + res.set('X-Tiler-Profiler', req.profiler.toJSONString()); } - res.send.apply(res, args); + if (headers) { + res.set(headers); + } - if (req.profiler ) { + res.status(status); + + if (!Buffer.isBuffer(body) && typeof body === 'object') { + if (req.query && req.query.callback) { + res.jsonp(body); + } else { + res.json(body); + } + } else { + res.send(body); + } + + if (req.profiler) { try { // May throw due to dns, see // See http://github.com/CartoDB/Windshaft/issues/166 @@ -176,6 +186,7 @@ BaseController.prototype.send = function(req, res, args) { } } }; +// jshint maxcomplexity:6 BaseController.prototype.sendError = function(req, res, err, label) { label = label || 'UNKNOWN'; @@ -191,7 +202,7 @@ BaseController.prototype.sendError = function(req, res, err, label) { var errorResponseBody = { errors: [errorMessage(err)] }; - this.send(req, res, [errorResponseBody, statusCode]); + this.send(req, res, errorResponseBody, statusCode); }; function errorMessage(err) { diff --git a/lib/cartodb/controllers/layergroup.js b/lib/cartodb/controllers/layergroup.js index 59e7a98b..a1f13999 100644 --- a/lib/cartodb/controllers/layergroup.js +++ b/lib/cartodb/controllers/layergroup.js @@ -77,7 +77,7 @@ LayergroupController.prototype.attributes = function(req, res) { if (err) { self.sendError(req, res, err, 'GET ATTRIBUTES'); } else { - self.sendResponse(req, res, [tile, 200]); + self.sendResponse(req, res, tile, 200); } } ); @@ -159,7 +159,7 @@ LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, t global.statsClient.increment('windshaft.tiles.error'); global.statsClient.increment('windshaft.tiles.' + formatStat + '.error'); } else { - this.sendResponse(req, res, [tile, headers, 200]); + this.sendResponse(req, res, tile, 200, headers); global.statsClient.increment('windshaft.tiles.success'); global.statsClient.increment('windshaft.tiles.' + formatStat + '.success'); } @@ -211,17 +211,17 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo if (err) { self.sendError(req, res, err, 'STATIC_MAP'); } else { - res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format); - self.sendResponse(req, res, [image, 200]); + res.set('Content-Type', headers['Content-Type'] || 'image/' + format); + self.sendResponse(req, res, image, 200); } } ); }; -LayergroupController.prototype.sendResponse = function(req, res, args) { +LayergroupController.prototype.sendResponse = function(req, res, body, status, headers) { var self = this; - res.header('Cache-Control', 'public,max-age=31536000'); + res.set('Cache-Control', 'public,max-age=31536000'); // Set Last-Modified header var lastUpdated; @@ -231,7 +231,7 @@ LayergroupController.prototype.sendResponse = function(req, res, args) { } else { lastUpdated = new Date(); } - res.header('Last-Modified', lastUpdated.toUTCString()); + res.set('Last-Modified', lastUpdated.toUTCString()); var dbName = req.params.dbname; step( @@ -245,10 +245,10 @@ LayergroupController.prototype.sendResponse = function(req, res, args) { } if (!!affectedTables) { var tablesCacheEntry = new TablesCacheEntry(dbName, affectedTables); - res.header('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); + res.set('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); self.surrogateKeysCache.tag(res, tablesCacheEntry); } - self.send(req, res, args); + self.send(req, res, body, status, headers); } ); diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index ffa776a7..00d6d317 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -166,8 +166,8 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { if (err) { self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP'); } else { - res.header('X-Layergroup-Id', layergroup.layergroupid); - self.send(req, res, [layergroup, 200]); + res.set('X-Layergroup-Id', layergroup.layergroupid); + self.send(req, res, layergroup, 200); } } ); @@ -223,10 +223,10 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8); layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid; - res.header('X-Layergroup-Id', layergroup.layergroupid); + res.set('X-Layergroup-Id', layergroup.layergroupid); self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName())); - self.send(req, res, [layergroup, 200]); + self.send(req, res, layergroup, 200); } } ); @@ -309,9 +309,9 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la if (req.method === 'GET') { var tableCacheEntry = new TablesCacheEntry(dbName, result.affectedTables); var ttl = global.environment.varnish.layergroupTtl || 86400; - res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); - res.header('Last-Modified', (new Date()).toUTCString()); - res.header('X-Cache-Channel', tableCacheEntry.getCacheChannel()); + res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); + res.set('Last-Modified', (new Date()).toUTCString()); + res.set('X-Cache-Channel', tableCacheEntry.getCacheChannel()); if (result.affectedTables && result.affectedTables.length > 0) { self.surrogateKeysCache.tag(res, tableCacheEntry); } diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index d921b956..bc6f7654 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -35,8 +35,8 @@ NamedMapsController.prototype.register = function(app) { NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) { this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName())); - res.header('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png'); - res.header('Cache-Control', 'public,max-age=7200,must-revalidate'); + res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png'); + res.set('Cache-Control', 'public,max-age=7200,must-revalidate'); var self = this; @@ -52,7 +52,7 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header } if (!result || !!result.affectedTables) { // we increase cache control as we can invalidate it - res.header('Cache-Control', 'public,max-age=31536000'); + res.set('Cache-Control', 'public,max-age=31536000'); var lastModifiedDate; if (Number.isFinite(result.lastUpdatedTime)) { @@ -60,15 +60,15 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header } else { lastModifiedDate = new Date(); } - res.header('Last-Modified', lastModifiedDate.toUTCString()); + res.set('Last-Modified', lastModifiedDate.toUTCString()); var tablesCacheEntry = new TablesCacheEntry(dbName, result.affectedTables); - res.header('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); + res.set('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); if (result.affectedTables.length > 0) { self.surrogateKeysCache.tag(res, tablesCacheEntry); } } - self.send(req, res, [resource, 200]); + self.send(req, res, resource, 200); } ); }; diff --git a/lib/cartodb/controllers/named_maps_admin.js b/lib/cartodb/controllers/named_maps_admin.js index c393b1c3..bd7643d0 100644 --- a/lib/cartodb/controllers/named_maps_admin.js +++ b/lib/cartodb/controllers/named_maps_admin.js @@ -29,7 +29,7 @@ NamedMapsAdminController.prototype.register = function(app) { app.post(app.base_url_templated, cors(), this.create.bind(this)); app.put(app.base_url_templated + '/:template_id', cors(), this.update.bind(this)); app.get(app.base_url_templated + '/:template_id', cors(), this.retrieve.bind(this)); - app.del(app.base_url_templated + '/:template_id', cors(), this.destroy.bind(this)); + app.delete(app.base_url_templated + '/:template_id', cors(), this.destroy.bind(this)); app.get(app.base_url_templated, cors(), this.list.bind(this)); app.options(app.base_url_templated + '/:template_id', cors('Content-Type')); }; @@ -144,9 +144,9 @@ NamedMapsAdminController.prototype.destroy = function(req, res) { }, function prepareResponse(err/*, tpl_val*/){ assert.ifError(err); - return { status: 'ok' }; + return ''; }, - finishFn(self, req, res, 'DELETE TEMPLATE', ['', 204]) + finishFn(self, req, res, 'DELETE TEMPLATE', 204) ); }; @@ -176,12 +176,12 @@ NamedMapsAdminController.prototype.list = function(req, res) { ); }; -function finishFn(controller, req, res, description, okResponse) { +function finishFn(controller, req, res, description, status) { return function finish(err, response){ if (err) { controller.sendError(req, res, err, description); } else { - controller.send(req, res, okResponse || [response, 200]); + controller.send(req, res, response, status || 200); } }; } diff --git a/lib/cartodb/controllers/server_info.js b/lib/cartodb/controllers/server_info.js index a6bc040e..7f61d51f 100644 --- a/lib/cartodb/controllers/server_info.js +++ b/lib/cartodb/controllers/server_info.js @@ -27,11 +27,11 @@ ServerInfoController.prototype.register = function(app) { }; ServerInfoController.prototype.welcome = function(req, res) { - res.send(WELCOME_MSG, 200); + res.status(200).send(WELCOME_MSG); }; ServerInfoController.prototype.version = function(req, res) { - res.send(versions, 200); + res.status(200).send(versions); }; ServerInfoController.prototype.health = function(req, res) { @@ -47,10 +47,10 @@ ServerInfoController.prototype.health = function(req, res) { if (err) { response.err = err.message; } - res.send(response, ok ? 200 : 503); + res.status(ok ? 200 : 503).send(response); }); } else { - res.send({enabled: false, ok: true}, 200); + res.status(200).send({enabled: false, ok: true}); } }; diff --git a/lib/cartodb/middleware/cors.js b/lib/cartodb/middleware/cors.js index f34e4d42..227bb477 100644 --- a/lib/cartodb/middleware/cors.js +++ b/lib/cartodb/middleware/cors.js @@ -4,8 +4,8 @@ module.exports = function cors(extraHeaders) { if(extraHeaders) { baseHeaders += ", " + extraHeaders; } - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", baseHeaders); + res.set("Access-Control-Allow-Origin", "*"); + res.set("Access-Control-Allow-Headers", baseHeaders); next(); }; -}; \ No newline at end of file +}; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index 8f1e3de8..2a9c67ae 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -1,4 +1,5 @@ var express = require('express'); +var bodyParser = require('body-parser'); var RedisPool = require('redis-mpool'); var cartodbRedis = require('cartodb-redis'); var _ = require('underscore'); @@ -241,10 +242,12 @@ function bootstrap(opts) { app = express.createServer(opts.https); } else { // fall back to http by default - app = express.createServer(); + app = express(); } app.enable('jsonp callback'); - app.use(express.bodyParser()); + app.disable('x-powered-by'); + app.disable('etag'); + app.use(bodyParser.json()); app.use(function bootstrap$prepareRequestResponse(req, res, next) { req.context = req.context || {}; @@ -253,7 +256,9 @@ function bootstrap(opts) { profile: opts.useProfiler }); - res.removeHeader('x-powered-by'); + if (global.environment && global.environment.api_hostname) { + res.set('X-Served-By-Host', global.environment.api_hostname); + } next(); }); @@ -262,7 +267,7 @@ function bootstrap(opts) { app.use(function(err, req, res, next) { if (err) { if (err.name === 'SyntaxError') { - res.send({ errors: [err.name + ': ' + err.message] }, 400); + res.status(400).json({ errors: [err.name + ': ' + err.message] }); } else { next(err); } @@ -277,7 +282,7 @@ function bootstrap(opts) { } function setupLogger(app, opts) { - if (opts.log_format) { + if (global.log4js && opts.log_format) { var loggerOpts = { // Allowing for unbuffered logging is mainly // used to avoid hanging during unit testing. @@ -288,11 +293,7 @@ function setupLogger(app, opts) { // optional log format format: opts.log_format }; - if (global.log4js) { - app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level: 'info'}))); - } else { - app.use(express.logger(loggerOpts)); - } + app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level: 'info'}))); } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 601085b1..af1ead97 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,6 +2,103 @@ "name": "windshaft-cartodb", "version": "2.12.1", "dependencies": { + "body-parser": { + "version": "1.14.0", + "from": "body-parser@~1.14.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.0.tgz", + "dependencies": { + "bytes": { + "version": "2.1.0", + "from": "bytes@2.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.1.0.tgz" + }, + "content-type": { + "version": "1.0.1", + "from": "content-type@~1.0.1", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" + }, + "depd": { + "version": "1.1.0", + "from": "depd@~1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz" + }, + "http-errors": { + "version": "1.3.1", + "from": "http-errors@~1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "dependencies": { + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "statuses": { + "version": "1.2.1", + "from": "statuses@1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" + } + } + }, + "iconv-lite": { + "version": "0.4.11", + "from": "iconv-lite@0.4.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz" + }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@~2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + } + }, + "qs": { + "version": "5.1.0", + "from": "qs@5.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz" + }, + "raw-body": { + "version": "2.1.3", + "from": "raw-body@~2.1.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.3.tgz", + "dependencies": { + "unpipe": { + "version": "1.0.0", + "from": "unpipe@1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + } + }, + "type-is": { + "version": "1.6.8", + "from": "type-is@~1.6.8", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.8.tgz", + "dependencies": { + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "mime-types": { + "version": "2.1.6", + "from": "mime-types@~2.1.6", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.6.tgz", + "dependencies": { + "mime-db": { + "version": "1.18.0", + "from": "mime-db@~1.18.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.18.0.tgz" + } + } + } + } + } + } + }, "cartodb-psql": { "version": "0.4.0", "from": "cartodb-psql@~0.4.0", @@ -84,36 +181,227 @@ "resolved": "https://registry.npmjs.org/dot/-/dot-1.0.3.tgz" }, "express": { - "version": "2.5.11", - "from": "express@~2.5.11", - "resolved": "https://registry.npmjs.org/express/-/express-2.5.11.tgz", + "version": "4.13.3", + "from": "express@~4.13.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz", "dependencies": { - "connect": { - "version": "1.9.2", - "from": "connect@1.x", - "resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz", + "accepts": { + "version": "1.2.13", + "from": "accepts@~1.2.12", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.2.13.tgz", "dependencies": { - "formidable": { - "version": "1.0.17", - "from": "formidable@1.0.x", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz" + "mime-types": { + "version": "2.1.6", + "from": "mime-types@~2.1.6", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.6.tgz", + "dependencies": { + "mime-db": { + "version": "1.18.0", + "from": "mime-db@~1.18.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.18.0.tgz" + } + } + }, + "negotiator": { + "version": "0.5.3", + "from": "negotiator@0.5.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz" } } }, - "mime": { - "version": "1.2.4", - "from": "mime@1.2.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.4.tgz" + "array-flatten": { + "version": "1.1.1", + "from": "array-flatten@1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + }, + "content-disposition": { + "version": "0.5.0", + "from": "content-disposition@0.5.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz" + }, + "content-type": { + "version": "1.0.1", + "from": "content-type@~1.0.1", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" + }, + "cookie": { + "version": "0.1.3", + "from": "cookie@0.1.3", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz" + }, + "cookie-signature": { + "version": "1.0.6", + "from": "cookie-signature@1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + }, + "depd": { + "version": "1.0.1", + "from": "depd@~1.0.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.0.1.tgz" + }, + "escape-html": { + "version": "1.0.2", + "from": "escape-html@1.0.2", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.2.tgz" + }, + "etag": { + "version": "1.7.0", + "from": "etag@~1.7.0", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "finalhandler": { + "version": "0.4.0", + "from": "finalhandler@0.4.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.0.tgz", + "dependencies": { + "unpipe": { + "version": "1.0.0", + "from": "unpipe@~1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + } + } + }, + "fresh": { + "version": "0.3.0", + "from": "fresh@0.3.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz" + }, + "merge-descriptors": { + "version": "1.0.0", + "from": "merge-descriptors@1.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.0.tgz" + }, + "methods": { + "version": "1.1.1", + "from": "methods@~1.1.1", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.1.tgz" + }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@~2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "dependencies": { + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + } + } + }, + "parseurl": { + "version": "1.3.0", + "from": "parseurl@~1.3.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz" + }, + "path-to-regexp": { + "version": "0.1.7", + "from": "path-to-regexp@0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + }, + "proxy-addr": { + "version": "1.0.8", + "from": "proxy-addr@~1.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.8.tgz", + "dependencies": { + "forwarded": { + "version": "0.1.0", + "from": "forwarded@~0.1.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz" + }, + "ipaddr.js": { + "version": "1.0.1", + "from": "ipaddr.js@1.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.1.tgz" + } + } }, "qs": { - "version": "0.4.2", - "from": "qs@0.4.x", - "resolved": "https://registry.npmjs.org/qs/-/qs-0.4.2.tgz" + "version": "4.0.0", + "from": "qs@4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz" }, - "mkdirp": { - "version": "0.3.0", - "from": "mkdirp@0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" + "range-parser": { + "version": "1.0.2", + "from": "range-parser@~1.0.2", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz" + }, + "send": { + "version": "0.13.0", + "from": "send@0.13.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.13.0.tgz", + "dependencies": { + "destroy": { + "version": "1.0.3", + "from": "destroy@1.0.3", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz" + }, + "http-errors": { + "version": "1.3.1", + "from": "http-errors@~1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "dependencies": { + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "mime": { + "version": "1.3.4", + "from": "mime@1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "statuses": { + "version": "1.2.1", + "from": "statuses@~1.2.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" + } + } + }, + "serve-static": { + "version": "1.10.0", + "from": "serve-static@~1.10.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.0.tgz" + }, + "type-is": { + "version": "1.6.8", + "from": "type-is@~1.6.6", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.8.tgz", + "dependencies": { + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "mime-types": { + "version": "2.1.6", + "from": "mime-types@~2.1.6", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.6.tgz", + "dependencies": { + "mime-db": { + "version": "1.18.0", + "from": "mime-db@~1.18.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.18.0.tgz" + } + } + } + } + }, + "utils-merge": { + "version": "1.0.0", + "from": "utils-merge@1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" + }, + "vary": { + "version": "1.0.1", + "from": "vary@~1.0.1", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.0.1.tgz" } } }, @@ -306,7 +594,7 @@ }, "mime-types": { "version": "2.1.6", - "from": "mime-types@~2.1.2", + "from": "mime-types@~2.1.6", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.6.tgz", "dependencies": { "mime-db": { diff --git a/package.json b/package.json index 6302368e..16672df3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "Sandro Santilli " ], "dependencies": { - "express": "~2.5.11", + "express": "~4.13.3", + "body-parser": "~1.14.0", "debug": "~2.2.0", "step-profiler": "~0.2.1", "node-statsd": "~0.0.7", diff --git a/test/acceptance/cache/surrogate_keys_invalidation.js b/test/acceptance/cache/surrogate_keys_invalidation.js index 57412633..96e83c79 100644 --- a/test/acceptance/cache/surrogate_keys_invalidation.js +++ b/test/acceptance/cache/surrogate_keys_invalidation.js @@ -1,4 +1,4 @@ -require(__dirname + '/../../support/test_helper'); +require('../../support/test_helper'); var assert = require('../../support/assert'); var redis = require('redis'); diff --git a/test/acceptance/multilayer_server.js b/test/acceptance/multilayer_server.js index b8b5db2a..80d432be 100644 --- a/test/acceptance/multilayer_server.js +++ b/test/acceptance/multilayer_server.js @@ -22,7 +22,7 @@ describe('tests from old api translated to multilayer', function() { // 'map_style|null|publicuser|my_table', redisClient.keys("map_style|*", function(err, matches) { redisClient.del(matches, function() { - done(); + testHelper.deleteRedisKeys({ 'user:cartodb250user:mapviews:global': 5 }, done); }); }); }); diff --git a/test/acceptance/ported/attributes.js b/test/acceptance/ported/attributes.js index da6bfcba..ddccb814 100644 --- a/test/acceptance/ported/attributes.js +++ b/test/acceptance/ported/attributes.js @@ -235,7 +235,10 @@ describe('attributes', function() { assert.ifError(err); // jsonp errors should be returned with HTTP status 200 assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - assert.equal(res.body, 'test({"errors":["Layer 0 has no exposed attributes"]});'); + assert.equal( + res.body, + '/**/ typeof test === \'function\' && test({"errors":["Layer 0 has no exposed attributes"]});' + ); return null; }, function do_get_attr_1(err) diff --git a/test/acceptance/ported/external_resources.js b/test/acceptance/ported/external_resources.js index 0b365ef8..157d9e35 100644 --- a/test/acceptance/ported/external_resources.js +++ b/test/acceptance/ported/external_resources.js @@ -1,4 +1,4 @@ -require('../../support/test_helper'); +var testHelper = require('../../support/test_helper'); var assert = require('../../support/assert'); @@ -7,6 +7,8 @@ var PortedServerOptions = require('./support/ported_server_options'); var http = require('http'); var testClient = require('./support/test_client'); +var nock = require('nock'); + var BaseController = require('../../../lib/cartodb/controllers/base'); function rmdir_recursive_sync(dirname) { @@ -33,6 +35,8 @@ describe('external resources', function() { var req2paramsFn; before(function(done) { + nock.enableNetConnect('127.0.0.1'); + req2paramsFn = BaseController.prototype.req2params; BaseController.prototype.req2params = PortedServerOptions.req2params; // Start a server to test external resources @@ -58,8 +62,13 @@ describe('external resources', function() { rmdir_recursive_sync(global.environment.millstone.cache_basedir); - // Close the resources server - res_serv.close(done); + testHelper.deleteRedisKeys({ + 'user:localhost:mapviews:global': 5 + }, function() { + // Close the resources server + res_serv.close(done); + }); + }); function imageCompareFn(fixture, done) { @@ -73,7 +82,7 @@ describe('external resources', function() { it("basic external resource", function(done) { - var circleStyle = "#test_table_3 { marker-file: url('http://localhost:" + res_serv_port + + var circleStyle = "#test_table_3 { marker-file: url('http://127.0.0.1:" + res_serv_port + "/circle.svg'); marker-transform:'scale(0.2)'; }"; testClient.getTile(testClient.defaultTableMapConfig('test_table_3', circleStyle), 13, 4011, 3088, @@ -82,7 +91,7 @@ describe('external resources', function() { it("different external resource", function(done) { - var squareStyle = "#test_table_3 { marker-file: url('http://localhost:" + res_serv_port + + var squareStyle = "#test_table_3 { marker-file: url('http://127.0.0.1:" + res_serv_port + "/square.svg'); marker-transform:'scale(0.2)'; }"; testClient.getTile(testClient.defaultTableMapConfig('test_table_3', squareStyle), 13, 4011, 3088, @@ -96,7 +105,7 @@ describe('external resources', function() { serverOptions: PortedServerOptions }; - var externalResourceStyle = "#test_table_3{marker-file: url('http://localhost:" + res_serv_port + + var externalResourceStyle = "#test_table_3{marker-file: url('http://127.0.0.1:" + res_serv_port + "/square.svg'); marker-transform:'scale(0.2)'; }"; var externalResourceMapConfig = testClient.defaultTableMapConfig('test_table_3', externalResourceStyle); @@ -122,7 +131,7 @@ describe('external resources', function() { }); it("referencing unexistant external resources returns an error", function(done) { - var url = "http://localhost:" + res_serv_port + "/notfound.png"; + var url = "http://127.0.0.1:" + res_serv_port + "/notfound.png"; var style = "#test_table_3{marker-file: url('" + url + "'); marker-transform:'scale(0.2)'; }"; var mapConfig = testClient.defaultTableMapConfig('test_table_3', style); diff --git a/test/acceptance/ported/multilayer_error_cases.js b/test/acceptance/ported/multilayer_error_cases.js index 9d2f55f5..2434900d 100644 --- a/test/acceptance/ported/multilayer_error_cases.js +++ b/test/acceptance/ported/multilayer_error_cases.js @@ -56,7 +56,10 @@ describe('multilayer error cases', function() { headers: {'Content-Type': 'application/json' } }, {}, function(res) { assert.equal(res.statusCode, 200); - assert.equal(res.body, 'test({"errors":["Missing layers array from layergroup config"]});'); + assert.equal( + res.body, + '/**/ typeof test === \'function\' && test({"errors":["Missing layers array from layergroup config"]});' + ); done(); }); }); @@ -340,7 +343,10 @@ describe('multilayer error cases', function() { } }; testClient.createLayergroup(bogusSqlMapConfig, options, function(err, res) { - assert.ok(/^test\(/.test(res.body), "Body start expected callback name: " + res.body); + assert.ok( + /^\/\*\*\/ typeof test === 'function' && test\(/.test(res.body), + "Body start expected callback name: " + res.body + ); assert.ok(/syntax error/.test(res.body), "Unexpected error: " + res.body); done(); }); @@ -356,7 +362,10 @@ describe('multilayer error cases', function() { } }; testClient.createLayergroup(noGeomMapConfig, options, function(err, res) { - assert.ok(/^test\(/.test(res.body), "Body start expected callback name: " + res.body); + assert.ok( + /^\/\*\*\/ typeof test === 'function' && test\(/.test(res.body), + "Body start expected callback name: " + res.body + ); assert.ok(/column.*does not exist/.test(res.body), "Unexpected error: " + res.body); done(); }); diff --git a/test/acceptance/ported/regressions.js b/test/acceptance/ported/regressions.js index fa6330af..8a1423e4 100644 --- a/test/acceptance/ported/regressions.js +++ b/test/acceptance/ported/regressions.js @@ -1,10 +1,8 @@ require('../../support/test_helper'); var assert = require('../../support/assert'); -var _ = require('underscore'); var fs = require('fs'); var http = require('http'); -var cartodbServer = require('../../../lib/cartodb/server'); var ServerOptions = require('./support/ported_server_options'); var testClient = require('./support/test_client'); @@ -102,37 +100,4 @@ describe('regressions', function() { done(); }); }); - - // See https://github.com/CartoDB/Windshaft/issues/167 - it("#167 does not die on unexistent statsd host", function(done) { - var CustomOptions = _.clone(ServerOptions); - CustomOptions.statsd = _.clone(CustomOptions.statsd); - CustomOptions.statsd.host = 'whoami.vizzuality.com'; - CustomOptions.statsd.cacheDns = false; - var server = cartodbServer(CustomOptions); - server.req2params = CustomOptions.req2params; - server.setMaxListeners(0); - - var errors = []; - // We need multiple requests to make sure - // statsd_client eventually tries to send - // stats _and_ DNS lookup is given enough - // time (an interval is used later for that) - var numreq = 10; - var pending = numreq; - var completed = function(err) { - if ( err ) { - errors.push(err); - } - if ( ! --pending ) { - setTimeout(function() { - done(errors.length ? new Error(errors.join(',')) : null); - }, 10); - } - }; - var mapConfig = testClient.defaultTableMapConfig('test_table'); - for (var i=0; i 25) { + return callback(null, new Error('Tried too many ports')); } - if (server.__deferred) { - server.__deferred.forEach(function(args){ - assert.response.apply(assert, args); - }); - server.__deferred = null; - } - } - - // Check that the server is ready or defer - if (!server.fd) { - server.__deferred = server.__deferred || []; - server.listen(server.__port = port++, '127.0.0.1', check); - } else if (!server.__port) { - server.__deferred = server.__deferred || []; - process.nextTick(check); - } - - // The socket was created but is not yet listening, so keep deferring - if (!server.__listening) { - server.__deferred.push(arguments); - return; - } - - // Callback as third or fourth arg - var callback = typeof res === 'function' ? res : (typeof msg === 'function' ? msg : function(){}); - - // Default messate to test title - if (typeof msg === 'function') { - msg = null; - } - msg = msg || assert.testTitle; - msg += '. '; - - // Pending responses - server.__pending = server.__pending || 0; - server.__pending++; - - // Create client - if (!server.fd) { - server.listen(server.__port = port++, '127.0.0.1', issue); - } else { - issue(); - } - - // jshint maxcomplexity:8 - function issue(){ - - // Issue request - var timer, - method = req.method || 'GET', - status = res.status || res.statusCode, - data = req.data || req.body, - requestTimeout = req.timeout || 0, - encoding = req.encoding || 'utf8'; - - var request = http.request({ - host: '127.0.0.1', - port: server.__port, - path: req.url, - method: method, - headers: req.headers + listener = server.listen(port, host); + listener.on('error', function() { + port++; + listeningAttempts++; + listen(); }); + listener.on('listening', onServerListening); + } - var check = function() { - if (--server.__pending === 0) { - server.close(); - server.__listening = false; - } + listen(); + + // jshint maxcomplexity:9 + function onServerListening() { + var status = res.status || res.statusCode; + var requestParams = { + url: 'http://' + host + ':' + port + req.url, + method: req.method || 'GET', + headers: req.headers || {}, + timeout: req.timeout || 0, + encoding: req.encoding || 'utf8' }; - // Timeout - if (requestTimeout) { - timer = setTimeout(function(){ - check(); - delete req.timeout; - assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.'); - }, requestTimeout); + if (req.body || req.data) { + requestParams.body = req.body || req.data; } - if (data) { - request.write(data); - } - - request.on('response', function(response){ - response.body = ''; - response.setEncoding(encoding); - response.on('data', function(chunk){ response.body += chunk; }); - response.on('end', function(){ - if (timer) { - clearTimeout(timer); - } - - check(); + request(requestParams, function assert$response$requestHandler(error, response, body) { + listener.close(function() { + response.body = response.body || body; // Assert response body - if (res.body !== undefined) { + if (res.body) { var eql = res.body instanceof RegExp ? res.body.test(response.body) : res.body === response.body; assert.ok( eql, - msg + colorize('[red]{Invalid response body.}\n' + - ' Expected: [green]{' + res.body + '}\n' + - ' Got: [red]{' + response.body + '}') + colorize('[red]{Invalid response body.}\n' + + ' Expected: [green]{' + res.body + '}\n' + + ' Got: [red]{' + response.body + '}') ); } // Assert response status if (typeof status === 'number') { - assert.equal( - response.statusCode, - status, - msg + colorize('[red]{Invalid response status code.}\n' + - ' Expected: [green]{' + status + '}\n' + - ' Got: [red]{' + response.statusCode + '}\n' + - ' Body: ' + response.body) + assert.equal(response.statusCode, status, + colorize('[red]{Invalid response status code.}\n' + + ' Expected: [green]{' + status + '}\n' + + ' Got: [red]{' + response.statusCode + '}\n' + + ' Body: ' + response.body) ); } @@ -227,11 +158,10 @@ assert.response = function(server, req, res, msg){ actual = response.headers[name.toLowerCase()], expected = res.headers[name], headerEql = expected instanceof RegExp ? expected.test(actual) : expected === actual; - assert.ok( - headerEql, - msg + colorize('Invalid response header [bold]{' + name + '}.\n' + - ' Expected: [green]{' + expected + '}\n' + - ' Got: [red]{' + actual + '}') + assert.ok(headerEql, + colorize('Invalid response header [bold]{' + name + '}.\n' + + ' Expected: [green]{' + expected + '}\n' + + ' Got: [red]{' + actual + '}') ); } } @@ -241,12 +171,12 @@ assert.response = function(server, req, res, msg){ }); }); - request.end(); } }; +// jshint maxcomplexity:6 // @param tolerance number of tolerated grid cell differences -// jshint maxcomplexity:8 +// jshint maxcomplexity:9 assert.utfgridEqualsFile = function(buffer, file_b, tolerance, callback) { fs.writeFileSync('/tmp/grid.json', buffer, 'binary'); // <-- to debug/update var expected_json = JSON.parse(fs.readFileSync(file_b, 'utf8')); @@ -265,7 +195,7 @@ assert.utfgridEqualsFile = function(buffer, file_b, tolerance, callback) { }; try { - var obtained_json = JSON.parse(buffer); + var obtained_json = Object.prototype.toString() === buffer.toString() ? buffer : JSON.parse(buffer); // compare grid var obtained_grid = obtained_json.grid; diff --git a/test/support/test_helper.js b/test/support/test_helper.js index 00351eee..02380e3f 100644 --- a/test/support/test_helper.js +++ b/test/support/test_helper.js @@ -10,6 +10,9 @@ var LZMA = require('lzma').LZMA; var lzmaWorker = new LZMA(); +var redis = require('redis'); +var nock = require('nock'); + // set environment specific variables global.environment = require(__dirname + '/../../config/environments/test'); global.environment.name = 'test'; @@ -56,23 +59,74 @@ function checkSurrogateKey(res, expectedKey) { assert.equal(res.headers['surrogate-key'], expectedKey); } -//var _ = require('underscore'); -//var redis = require('redis'); -// global afterEach to capture tests that leave keys in redis -//afterEach(function(done) { -// var redisClient = redis.createClient(global.environment.redis.port); -// // Check that we start with an empty redis db -// redisClient.keys("*", function(err, keys) { -// if ( err ) { -// return done(err); -// } -// assert.equal(keys.length, 0, "test left objects in redis:\n" + keys.join("\n")); -// redisClient.flushall(done); -// }); -//}); +//global after to capture test suites that leave keys in redis +after(function(done) { + + // restoring nock globally after each suite + nock.cleanAll(); + nock.enableNetConnect(); + + var expectedKeys = { + 'rails:test_windshaft_cartodb_user_1_db:test_table_private_1': true, + 'rails:test_windshaft_cartodb_user_1_db:my_table': true, + 'rails:users:localhost:map_key': true, + 'rails:users:cartodb250user': true, + 'rails:users:localhost': true + }; + var databasesTasks = { 0: 'users', 5: 'meta'}; + + var keysFound = []; + function taskDone(err, db, keys) { + if (err) { + return done(err); + } + + delete databasesTasks[db]; + keys.forEach(function(k) { + if (!expectedKeys[k]) { + keysFound.push(k); + } + }); + + if (Object.keys(databasesTasks).length === 0) { + //assert.equal(keysFound.length, 0, 'Unexpected keys found in redis: ' + keysFound.join(', ')); + done(); + } + } + + Object.keys(databasesTasks).forEach(function(db) { + var redisClient = redis.createClient(global.environment.redis.port); + redisClient.select(db, function() { + // Check that we start with an empty redis db + redisClient.keys("*", function(err, keys) { + return taskDone(err, db, keys); + }); + }); + }); +}); + +function deleteRedisKeys(keysToDelete, callback) { + + function taskDone(k) { + delete keysToDelete[k]; + if (Object.keys(keysToDelete).length === 0) { + callback(); + } + } + + Object.keys(keysToDelete).forEach(function(k) { + var redisClient = redis.createClient(global.environment.redis.port); + redisClient.select(keysToDelete[k], function() { + redisClient.del(k, function() { + taskDone(k); + }); + }); + }); +} module.exports = { + deleteRedisKeys: deleteRedisKeys, lzma_compress_to_base64: lzma_compress_to_base64, checkNoCache: checkNoCache, checkSurrogateKey: checkSurrogateKey, diff --git a/test/unit/cartodb/cache/model/named_maps_entry.test.js b/test/unit/cartodb/cache/model/named_maps_entry.test.js index bd11b5ac..6d1b21fb 100644 --- a/test/unit/cartodb/cache/model/named_maps_entry.test.js +++ b/test/unit/cartodb/cache/model/named_maps_entry.test.js @@ -1,3 +1,5 @@ +require('../../../../support/test_helper'); + var assert = require('assert'); var _ = require('underscore'); var NamedMapsCacheEntry = require('../../../../../lib/cartodb/cache/model/named_maps_entry'); diff --git a/test/unit/cartodb/ported/tile_stats.test.js b/test/unit/cartodb/ported/tile_stats.test.js index 318c7194..2a872eb3 100644 --- a/test/unit/cartodb/ported/tile_stats.test.js +++ b/test/unit/cartodb/ported/tile_stats.test.js @@ -36,6 +36,9 @@ describe('tile stats', function() { } }; var resMock = { + status: function() { return this; }, + json: function() {}, + jsonp: function() {}, send: function() {} }; layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, resMock, null, null); @@ -61,6 +64,9 @@ describe('tile stats', function() { } }; var resMock = { + status: function() { return this; }, + json: function() {}, + jsonp: function() {}, send: function() {} }; diff --git a/test/unit/cartodb/ported/windshaft_server.test.js b/test/unit/cartodb/ported/windshaft_server.test.js index c7acdceb..52f90d0d 100644 --- a/test/unit/cartodb/ported/windshaft_server.test.js +++ b/test/unit/cartodb/ported/windshaft_server.test.js @@ -19,9 +19,9 @@ describe('windshaft', function() { it('can spawn a new server on the global listen port', function(done){ var ws = cartodbServer(serverOptions); - ws.listen(global.environment.windshaft_port, function() { + var server = ws.listen(global.environment.windshaft_port, function() { assert.ok(ws); - ws.close(done); /* allow proper tear down */ + server.close(done); /* allow proper tear down */ }); }); diff --git a/test/unit/cartodb/template_maps.test.js b/test/unit/cartodb/template_maps.test.js index 472d57b8..6cf76705 100644 --- a/test/unit/cartodb/template_maps.test.js +++ b/test/unit/cartodb/template_maps.test.js @@ -1,4 +1,4 @@ -require('../../support/test_helper'); +var testHelper = require('../../support/test_helper'); var assert = require('assert'); var RedisPool = require('redis-mpool'); @@ -11,6 +11,13 @@ describe('template_maps', function() { // configure redis pool instance to use in tests var redis_pool = new RedisPool(global.environment.redis); + after(function(done) { + testHelper.deleteRedisKeys({ + 'map_tpl|me': 0, + 'map_tpl|you': 0 + }, done); + }); + var wadusLayer = { options: { sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',