Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
7 changed files
with
295 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ language: node_js | |
node_js: | ||
- "8" | ||
- "9" | ||
- "10" | ||
- "node" | ||
|
||
services: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,196 +1,159 @@ | ||
'use strict'; | ||
|
||
// Load modules | ||
|
||
const Redis = require('ioredis'); | ||
const Hoek = require('hoek'); | ||
|
||
|
||
// Declare internals | ||
|
||
const internals = {}; | ||
|
||
|
||
internals.defaults = { | ||
const defaults = { | ||
host: '127.0.0.1', | ||
port: 6379 | ||
}; | ||
|
||
exports = module.exports = class Connection { | ||
|
||
exports = module.exports = internals.Connection = function (options) { | ||
|
||
Hoek.assert(this.constructor === internals.Connection, 'Redis cache client must be instantiated using new'); | ||
|
||
this.settings = Object.assign({}, internals.defaults, options); | ||
this.client = null; | ||
return this; | ||
}; | ||
constructor(options) { | ||
|
||
Hoek.assert(this instanceof Connection, 'Redis cache client must be instantiated using new'); | ||
|
||
// Async | ||
internals.Connection.prototype.start = function () { | ||
this.client = null; | ||
this.settings = Object.assign({}, defaults, options); | ||
|
||
if (this.settings.client) { | ||
this.client = this.settings.client; | ||
return this; | ||
} | ||
|
||
if (this.client) { | ||
return Promise.resolve(); | ||
} | ||
async start() { | ||
|
||
// Return a promise that is resolved when everything is ready | ||
return new Promise((resolve, reject) => { | ||
if (this.settings.client) { | ||
this.client = this.settings.client; | ||
} | ||
|
||
let client; | ||
if (this.client) { | ||
return; | ||
} | ||
|
||
const options = { | ||
password: this.settings.password, | ||
const options = Object.assign({}, this.settings, { | ||
db: this.settings.database || this.settings.db, | ||
tls: this.settings.tls | ||
}; | ||
name: this.settings.sentinelName, | ||
tls: this.settings.tls, | ||
lazyConnect: true | ||
}); | ||
|
||
if (this.settings.sentinels && this.settings.sentinels.length) { | ||
options.sentinels = this.settings.sentinels; | ||
options.name = this.settings.sentinelName; | ||
client = Redis.createClient(options); | ||
} | ||
else if (this.settings.url) { | ||
client = Redis.createClient(this.settings.url, options); | ||
} | ||
else if (this.settings.socket) { | ||
client = Redis.createClient(this.settings.socket, options); | ||
} | ||
else { | ||
client = Redis.createClient(this.settings.port, this.settings.host, options); | ||
} | ||
const client = new Redis(options); | ||
|
||
// Listen to errors | ||
client.on('error', (err) => { | ||
client.on('error', () => { | ||
|
||
if (!this.client) { // Failed to connect | ||
client.end(false); | ||
return reject(err); | ||
if (!this.client) { // Failed to connect | ||
client.disconnect(); | ||
} | ||
}); | ||
|
||
// Wait for connection | ||
client.once('ready', () => { | ||
|
||
this.client = client; | ||
return resolve(); | ||
}); | ||
}); | ||
}; | ||
|
||
await client.connect(); | ||
this.client = client; | ||
} | ||
|
||
internals.Connection.prototype.stop = async function () { | ||
async stop() { | ||
|
||
try { | ||
if (this.client && !this.settings.client) { | ||
this.client.removeAllListeners(); | ||
await this.client.quit(); | ||
try { | ||
if (this.client && !this.settings.client) { | ||
this.client.removeAllListeners(); | ||
await this.client.disconnect(); | ||
} | ||
} | ||
finally { | ||
this.client = null; | ||
} | ||
} | ||
finally { | ||
this.client = null; | ||
} | ||
}; | ||
|
||
|
||
internals.Connection.prototype.isReady = function () { | ||
isReady() { | ||
|
||
return !!this.client && this.client.status === 'ready'; | ||
}; | ||
return !!this.client && this.client.status === 'ready'; | ||
} | ||
|
||
validateSegmentName(name) { | ||
|
||
internals.Connection.prototype.validateSegmentName = function (name) { | ||
if (!name) { | ||
return new Error('Empty string'); | ||
} | ||
|
||
if (!name) { | ||
return new Error('Empty string'); | ||
} | ||
if (name.indexOf('\0') !== -1) { | ||
return new Error('Includes null character'); | ||
} | ||
|
||
if (name.indexOf('\0') !== -1) { | ||
return new Error('Includes null character'); | ||
return null; | ||
} | ||
|
||
return null; | ||
}; | ||
async get(key) { | ||
|
||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
} | ||
|
||
internals.Connection.prototype.get = async function (key) { | ||
const result = await this.client.get(this.generateKey(key)); | ||
|
||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
} | ||
if (!result) { | ||
return null; | ||
} | ||
|
||
const result = await this.client.get(this.generateKey(key)); | ||
let envelope = null; | ||
|
||
if (!result) { | ||
return null; | ||
} | ||
try { | ||
envelope = JSON.parse(result); | ||
} | ||
catch (ignoreErr) { } // Handled by validation below | ||
|
||
let envelope = null; | ||
try { | ||
envelope = JSON.parse(result); | ||
} | ||
catch (err) { } // Handled by validation below | ||
if (!envelope) { | ||
throw Error('Bad envelope content'); | ||
} | ||
|
||
if (!envelope) { | ||
throw Error('Bad envelope content'); | ||
} | ||
if (!envelope.stored || !envelope.hasOwnProperty('item')) { | ||
throw Error('Incorrect envelope structure'); | ||
} | ||
|
||
if (!envelope.stored || !envelope.hasOwnProperty('item')) { | ||
throw Error('Incorrect envelope structure'); | ||
return envelope; | ||
} | ||
|
||
return envelope; | ||
}; | ||
|
||
|
||
internals.Connection.prototype.set = async function (key, value, ttl) { | ||
async set(key, value, ttl) { | ||
|
||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
} | ||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
} | ||
|
||
const envelope = { | ||
item: value, | ||
stored: Date.now(), | ||
ttl | ||
}; | ||
const envelope = { | ||
item: value, | ||
stored: Date.now(), | ||
ttl | ||
}; | ||
|
||
const cacheKey = this.generateKey(key); | ||
const cacheKey = this.generateKey(key); | ||
const stringifiedEnvelope = JSON.stringify(envelope); | ||
|
||
const stringifiedEnvelope = JSON.stringify(envelope); | ||
await this.client.set(cacheKey, stringifiedEnvelope); | ||
|
||
await this.client.set(cacheKey, stringifiedEnvelope); | ||
const ttlSec = Math.max(1, Math.floor(ttl / 1000)); | ||
|
||
const ttlSec = Math.max(1, Math.floor(ttl / 1000)); | ||
// Use 'pexpire' with ttl in Redis 2.6.0 | ||
return this.client.expire(cacheKey, ttlSec); | ||
}; | ||
// Use 'pexpire' with ttl in Redis 2.6.0 | ||
return this.client.expire(cacheKey, ttlSec); | ||
} | ||
|
||
async drop(key) { | ||
|
||
// Async | ||
internals.Connection.prototype.drop = function (key) { | ||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
} | ||
|
||
if (!this.client) { | ||
throw Error('Connection not started'); | ||
return await this.client.del(this.generateKey(key)); | ||
} | ||
return this.client.del(this.generateKey(key)); | ||
}; | ||
|
||
|
||
internals.Connection.prototype.generateKey = function (key) { | ||
generateKey({ id, segment }) { | ||
|
||
const parts = []; | ||
const parts = []; | ||
|
||
if (this.settings.partition) { | ||
parts.push(encodeURIComponent(this.settings.partition)); | ||
} | ||
if (this.settings.partition) { | ||
parts.push(encodeURIComponent(this.settings.partition)); | ||
} | ||
|
||
parts.push(encodeURIComponent(key.segment)); | ||
parts.push(encodeURIComponent(key.id)); | ||
parts.push(encodeURIComponent(segment)); | ||
parts.push(encodeURIComponent(id)); | ||
|
||
return parts.join(':'); | ||
return parts.join(':'); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.