diff --git a/dadi/lib/cache/index.js b/dadi/lib/cache/index.js index cb7925ab..bdbc8f1d 100755 --- a/dadi/lib/cache/index.js +++ b/dadi/lib/cache/index.js @@ -1,4 +1,5 @@ const crypto = require('crypto') +const etag = require('etag') const lengthStream = require('length-stream') const path = require('path') const pathToRegexp = require('path-to-regexp') @@ -95,12 +96,24 @@ Cache.prototype.init = function () { return cache .getMetadata(cacheKey) .then(metadata => { + res.statusCode = 200 res.setHeader('X-Cache-Lookup', 'HIT') let compressed = false - if (metadata && metadata.compression === 'gzip') { - compressed = true + if (metadata) { + if (metadata.compression === 'gzip') { + compressed = true + } + + if (metadata.etag) { + res.setHeader('ETag', metadata.etag) + + if (req.headers['if-none-match'] === metadata.etag) { + res.statusCode = 304 + return res.end() + } + } } if (noCache) { @@ -170,12 +183,15 @@ Cache.prototype.init = function () { _end.apply(res, arguments) // If response is not 200 don't cache. - if (res.statusCode !== 200) return + if (res.statusCode !== 200) { + return + } // Cache the content. cache.set(cacheKey, data, { metadata: { - compression: !acceptEncoding ? 'none' : acceptEncoding + compression: !acceptEncoding ? 'none' : acceptEncoding, + etag: etag(data) } }).then(() => { diff --git a/dadi/lib/help.js b/dadi/lib/help.js index 7173bcdb..be46388b 100755 --- a/dadi/lib/help.js +++ b/dadi/lib/help.js @@ -2,6 +2,7 @@ const cache = require('./cache') const config = require('./../../config') const crypto = require('crypto') const ERROR_CODES = require('./../../error-codes') +const etag = require('etag') const formatError = require('@dadi/format-error') const log = require('@dadi/logger') const stackTrace = require('stack-trace') @@ -82,12 +83,6 @@ module.exports.sendBackJSON = function (successCode, res, next) { let resBody = body ? JSON.stringify(body) : null - // log response if it's already been sent - if (res.finished) { - log.info({res: res}, 'Response already sent. Attempting to send results: ' + resBody) - return - } - if (originalRequest && module.exports.shouldCompress(originalRequest)) { res.setHeader('Content-Encoding', 'gzip') @@ -105,6 +100,17 @@ module.exports.sendBackJSON = function (successCode, res, next) { return Promise.resolve(resBody).then(resBody => { res.setHeader('Content-Type', 'application/json') + + if (resBody) { + let etagResult = etag(resBody) + res.setHeader('ETag', etagResult) + + if (originalRequest && originalRequest.headers['if-none-match'] === etagResult) { + res.statusCode = 304 + return res.end() + } + } + res.statusCode = statusCode res.end(resBody) }) diff --git a/dadi/lib/index.js b/dadi/lib/index.js index 46ae18db..f7170edd 100755 --- a/dadi/lib/index.js +++ b/dadi/lib/index.js @@ -6,7 +6,7 @@ var chokidar = require('chokidar') var cluster = require('cluster') var colors = require('colors') // eslint-disable-line var debug = require('debug')('api:server') -var parsecomments = require('parse-comments') +var ParseComments = require('parse-comments') var fs = require('fs') var mkdirp = require('mkdirp') var path = require('path') @@ -632,7 +632,7 @@ Server.prototype.addEndpointResource = function (options) { this.addComponent({ aclKey, - docs: parsecomments(content), + docs: new ParseComments().parse(content), component, filepath, route @@ -688,7 +688,7 @@ Server.prototype.addHook = function (options) { var opts = { route: 'hook:' + name, component: filepath, - docs: parsecomments(content), + docs: new ParseComments().parse(content), filepath: filepath } diff --git a/package.json b/package.json index 1c25937c..9d88106b 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "debug": "3.1.0", "deep-clone": "^3.0.2", "deepmerge": "^2.1.0", + "etag": "^1.8.1", "fs-extra": "^3.0.1", "imagesize": "^1.0.0", "js-promise-queue": "^1.1.0", @@ -52,7 +53,7 @@ "moment": "2.19.3", "natural": "^0.6.1", "object-path": "^0.11.4", - "parse-comments": "0.4.3", + "parse-comments": "^1.0.0", "path-to-regexp": "~1.7.0", "recovery": "^0.2.6", "require-directory": "^2.1.1", @@ -65,7 +66,7 @@ "vary": "^1.1.2" }, "devDependencies": { - "@commitlint/cli": "~4.1.1", + "@commitlint/cli": "^7.5.2", "@commitlint/config-angular": "~3.1.1", "aws-sdk-mock": "1.6.1", "coveralls": "^3.0.1", @@ -83,7 +84,7 @@ "should": "4.0.4", "sinon": "2.3.2", "snazzy": "7.0.0", - "snyk": "^1.104.1", + "snyk": "^1.147.3", "standard": "8.6.0", "supertest": "^3.1.0", "uuid": "^3.3.2" diff --git a/test/acceptance/app.js b/test/acceptance/app.js index 9cd39dba..3a68d7de 100755 --- a/test/acceptance/app.js +++ b/test/acceptance/app.js @@ -402,7 +402,7 @@ describe('Application', function () { docs.should.exist docs.should.be.Array - docs[0].lead.should.eql('Adds two numbers together.') + docs[0].description.should.eql('Adds two numbers together.') done() }) diff --git a/test/acceptance/rest-endpoints/collections-api/get.js b/test/acceptance/rest-endpoints/collections-api/get.js index 6b08e1a4..e1561668 100644 --- a/test/acceptance/rest-endpoints/collections-api/get.js +++ b/test/acceptance/rest-endpoints/collections-api/get.js @@ -1229,5 +1229,32 @@ describe('Collections API – GET', function () { }) }) }) + + it('should respond with 304 if etag matches If-None-Match header', function (done) { + help.createDoc(bearerToken, function (err, doc1) { + if (err) return done(err) + help.createDoc(bearerToken, function (err, doc2) { + if (err) return done(err) + + var client = request(connectionString) + client + .get('/vtest/testdb/test-schema') + .set('Authorization', 'Bearer ' + bearerToken) + .expect(200) + .expect('content-type', 'application/json') + .end((err, res) => { + if (err) return done(err) + + let etag = res.headers['etag'] + + client + .get('/vtest/testdb/test-schema') + .set('Authorization', 'Bearer ' + bearerToken) + .set('If-None-Match', etag) + .expect(304, done) + }) + }) + }) + }) }) }) diff --git a/test/unit/helpTest.js b/test/unit/helpTest.js index f1d9771c..e861303d 100644 --- a/test/unit/helpTest.js +++ b/test/unit/helpTest.js @@ -44,7 +44,7 @@ describe('Help', function (done) { it('should send an error with the formatted message corresponding to the API error code with the status code provided', done => { let res = { end: function end (resBody) { - this.setHeader.callCount.should.eql(2) + this.setHeader.callCount.should.eql(3) this.setHeader.args[0][0].should.eql('Content-Length') this.setHeader.args[0][1].should.be.Number this.setHeader.args[1][0].should.eql('Content-Type') @@ -68,7 +68,7 @@ describe('Help', function (done) { it('should send an error with the formatted message corresponding to the API error code with the status code 500 if one is not provided', done => { let res = { end: function end (resBody) { - this.setHeader.callCount.should.eql(2) + this.setHeader.callCount.should.eql(3) this.setHeader.args[0][0].should.eql('Content-Length') this.setHeader.args[0][1].should.be.Number this.setHeader.args[1][0].should.eql('Content-Type')