diff --git a/lib/cartodb/middleware/vector-error.js b/lib/cartodb/middleware/vector-error.js index 75e93b0a..8268ec1f 100644 --- a/lib/cartodb/middleware/vector-error.js +++ b/lib/cartodb/middleware/vector-error.js @@ -5,7 +5,7 @@ module.exports = function vectorError() { return function vectorErrorMiddleware(err, req, res, next) { if(req.params.format === 'mvt') { - if (isTimeoutError(err)) { + if (isTimeoutError(err) || isRateLimitError(err)) { res.set('Content-Type', 'application/x-protobuf'); return res.status(429).send(timeoutErrorVectorTile); } @@ -27,3 +27,7 @@ function isDatasourceTimeoutError (err) { function isTimeoutError (err) { return isRenderTimeoutError(err) || isDatasourceTimeoutError(err); } + +function isRateLimitError (err) { + return err.type === 'limit' && err.subtype === 'rate-limit'; +} diff --git a/test/acceptance/rate-limit.test.js b/test/acceptance/rate-limit.test.js index 5a21ad0f..0813023c 100644 --- a/test/acceptance/rate-limit.test.js +++ b/test/acceptance/rate-limit.test.js @@ -15,6 +15,7 @@ let redisClient; let testClient; let keysToDelete = ['user:localhost:mapviews:global']; const user = 'localhost'; +let layergroupid; const query = ` SELECT @@ -68,13 +69,13 @@ const createMapConfig = ({ }); -function setLimit(count, period, burst) { +function setLimit(count, period, burst, endpoint = RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS) { redisClient.SELECT(8, err => { if (err) { return; } - const key = `limits:rate:store:${user}:maps:${RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS}`; + const key = `limits:rate:store:${user}:maps:${endpoint}`; redisClient.rpush(key, burst); redisClient.rpush(key, count); redisClient.rpush(key, period); @@ -270,3 +271,85 @@ describe('rate limit middleware', function () { ); }); }); + +describe('rate limit and vector tiles', function () { + + before(function(done) { + global.environment.enabledFeatures.rateLimitsEnabled = true; + global.environment.enabledFeatures.rateLimitsByEndpoint.tile = true; + + redisClient = redis.createClient(global.environment.redis.port); + const count = 1; + const period = 1; + const burst = 0; + setLimit(count, period, burst, RATE_LIMIT_ENDPOINTS_GROUPS.TILE); + + testClient = new TestClient(createMapConfig(), 1234); + testClient.getLayergroup({status: 200}, (err, res) => { + assert.ifError(err); + + layergroupid = res.layergroupid; + + done(); + }); + }); + + after(function() { + global.environment.enabledFeatures.rateLimitsEnabled = false; + global.environment.enabledFeatures.rateLimitsByEndpoint.tile = false; + }); + + afterEach(function(done) { + keysToDelete.forEach( key => { + redisClient.del(key); + }); + + redisClient.SELECT(0, () => { + redisClient.del('user:localhost:mapviews:global'); + + redisClient.SELECT(5, () => { + redisClient.del('user:localhost:mapviews:global'); + done(); + }); + }); + }); + + it('mvt rate limited', function (done) { + const tileParams = (status, limit, remaining, reset, retry, contentType) => { + let headers = { + "Content-Type": contentType, + "Carto-Rate-Limit-Limit": limit, + "Carto-Rate-Limit-Remaining": remaining, + "Carto-Rate-Limit-Reset": reset + }; + + if (retry) { + headers['Retry-After'] = retry; + } + + return { + layergroupid: layergroupid, + format: 'mvt', + response: {status, headers} + }; + }; + + testClient.getTile(0, 0, 0, tileParams(204, '1', '0', '1'), (err) => { + assert.ifError(err); + + testClient.getTile(0, 0, 0, tileParams(429, '1', '0', '0', '1', 'application/x-protobuf'), (err, res, tile) => { + assert.ifError(err); + + var tileJSON = tile.toJSON(); + assert.equal(Array.isArray(tileJSON), true); + assert.equal(tileJSON.length, 2); + assert.equal(tileJSON[0].name, 'errorTileSquareLayer'); + assert.equal(tileJSON[1].name, 'errorTileStripesLayer'); + + done(); + }); + + }); + + }); +});