Skip to content

Commit

Permalink
cache
Browse files Browse the repository at this point in the history
  • Loading branch information
refractalize committed Jun 23, 2017
1 parent b096e27 commit 9cd9989
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
*.sw?
/test/cache/
61 changes: 61 additions & 0 deletions middleware/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var debug = require('debug')('httpism:cache')
var fileStore = require('./fileStore')
var urlUtils = require('url')

function createStore (options) {
var url = typeof options === 'object' && options.hasOwnProperty('url') ? options.url : undefined
var parsedUrl = urlUtils.parse(url)
var protocol = parsedUrl.protocol || 'file'

var storeConstructor = storeTypes[protocol]

if (!storeConstructor) {
throw new Error('no such store for url: ' + url)
}

return storeConstructor(options)
}

module.exports = function (options) {
var store = createStore(options)
var isResponseCachable = typeof options === 'object' &&
options.hasOwnProperty('isResponseCachable')
? options.isResponseCachable
: function (response) {
return response.statusCode >= 200 && response.statusCode < 400
}

var httpismCache = function (req, next) {
var url = req.url

return store.responseExists(url).then(function (exists) {
if (exists) {
debug('hit', url)
return store.readResponse(url)
} else {
debug('miss', url)
return next().then(function (response) {
if (isResponseCachable(response)) {
return store.writeResponse(url, response)
} else {
return response
}
})
}
})
}

httpismCache.httpismMiddleware = {
name: 'cache',
before: ['debugLog', 'http']
}

httpismCache.middleware = 'cache'
httpismCache.before = ['debugLog', 'http']

return httpismCache
}

var storeTypes = {
file: fileStore
}
63 changes: 63 additions & 0 deletions middleware/fileStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
var fs = require('fs-promise')
var PassThrough = require('stream').PassThrough
var urlUtils = require('url')

function writeStreamToFile (filename, stream) {
return new Promise(function (resolve, reject) {
var file = fs.createWriteStream(filename)
stream.on('error', reject)
stream.on('end', resolve)
stream.pipe(file)
})
}

module.exports = function (options) {
var url = typeof options === 'object' && options.hasOwnProperty('url') ? options.url : undefined
var parsedUrl = urlUtils.parse(url)
var path = parsedUrl.path

return {
filename: function (url) {
return path + '/' + encodeURIComponent(url)
},

responseExists: function (url) {
return fs.exists(this.filename(url))
},

writeResponse: function (url, response) {
var filename = this.filename(url)
var body = response.body
delete response.body

var fileStream = body.pipe(new PassThrough())
var responseStream = body.pipe(new PassThrough())

var responseJson = JSON.stringify(response, null, 2)

return fs
.mkdirs(path)
.then(function () {
return fs.writeFile(filename + '.json', responseJson)
})
.then(function () {
writeStreamToFile(filename, fileStream).catch(function (e) {
console.error((e && e.stack) || e)
})

response.body = responseStream
return response
})
},

readResponse: function (url) {
var filename = this.filename(url)

return fs.readFile(filename + '.json', 'utf-8').then(function (contents) {
var response = JSON.parse(contents)
response.body = fs.createReadStream(filename)
return response
})
}
}
}
88 changes: 88 additions & 0 deletions test/httpismSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var multiparty = require('multiparty')
var obfuscateUrlPassword = require('../obfuscateUrlPassword')
var urlTemplate = require('url-template')
var pathUtils = require('path')
var cache = require('../middleware/cache')

describe('httpism', function () {
var server
Expand Down Expand Up @@ -1632,4 +1633,91 @@ describe('httpism', function () {
})
})
})

describe('cache', function () {
var version
var cachePath

beforeEach(function () {
version = 1
cachePath = pathUtils.join(__dirname, 'cache')

app.get('/', function (req, res) {
res.set('x-version', version)
res.send({version: version})
})

app.get('/binary', function (req, res) {
res.set('x-version', version)
res.send(Buffer.from([1, 3, 3, 7, version]))
})

return clearCache()
})

function clearCache () {
return fs.remove(cachePath)
}

it('caches responses', function () {
var http = httpism.client(cache({url: cachePath}), {response: true})
return http.get(baseurl).then(function (response) {
expect(response.headers['x-version']).to.eql('1')
expect(response.body.version).to.equal(1)
}).then(function () {
version++
return http.get(baseurl)
}).then(function (response) {
expect(response.headers['x-version']).to.eql('1')
expect(response.body.version).to.equal(1)
}).then(function () {
return clearCache()
}).then(function () {
return http.get(baseurl)
}).then(function (response) {
expect(response.headers['x-version']).to.eql('2')
expect(response.body.version).to.equal(2)
})
})

function streamToBuffer (stream) {
return new Promise(function (resolve, reject) {
var buffers = []
stream.on('data', function (buffer) {
buffers.push(buffer)
})
stream.on('error', reject)
stream.on('end', function () {
resolve(Buffer.concat(buffers))
})
})
}

it('caches binary responses', function () {
var http = httpism.client(cache({url: cachePath}), {response: true})
return http.get(baseurl + '/binary').then(function (response) {
expect(response.headers['x-version']).to.eql('1')
return streamToBuffer(response.body)
}).then(function (buffer) {
expect(Array.from(buffer.values())).to.eql([1, 3, 3, 7, 1])
}).then(function () {
version++
return http.get(baseurl + '/binary')
}).then(function (response) {
expect(response.headers['x-version']).to.eql('1')
return streamToBuffer(response.body)
}).then(function (buffer) {
expect(Array.from(buffer.values())).to.eql([1, 3, 3, 7, 1])
}).then(function () {
return clearCache()
}).then(function () {
return http.get(baseurl + '/binary')
}).then(function (response) {
expect(response.headers['x-version']).to.eql('2')
return streamToBuffer(response.body)
}).then(function (buffer) {
expect(Array.from(buffer.values())).to.eql([1, 3, 3, 7, 2])
})
})
})
})

0 comments on commit 9cd9989

Please sign in to comment.