mirror of
https://github.com/SunRed/haste-server.git
synced 2024-11-23 17:50:19 +01:00
Replaced all callbacks with promises in document stores
This commit is contained in:
parent
c165781b18
commit
7de68695fe
4 changed files with 130 additions and 146 deletions
|
@ -15,34 +15,30 @@ DocumentHandler.defaultKeyLength = 10;
|
||||||
|
|
||||||
// Handle retrieving a document
|
// Handle retrieving a document
|
||||||
DocumentHandler.prototype.handleGet = async function(key, res, skipExpire){
|
DocumentHandler.prototype.handleGet = async function(key, res, skipExpire){
|
||||||
await this.store.get(key, function(ret){
|
const data = await this.store.get(key, skipExpire);
|
||||||
if (ret){
|
|
||||||
winston.verbose('retrieved document', { key: key });
|
//when data is null it means there was either no data or an error
|
||||||
res.writeHead(200, { 'content-type': 'application/json' });
|
if (!data){
|
||||||
res.end(JSON.stringify({ data: ret, key: key }));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
winston.warn('document not found', { key: key });
|
winston.warn('document not found', { key: key });
|
||||||
res.writeHead(404, { 'content-type': 'application/json' });
|
res.status(404).json({ message: 'Document not found.' });
|
||||||
res.end(JSON.stringify({ message: 'Document not found.' }));
|
return;
|
||||||
}
|
}
|
||||||
}, skipExpire);
|
winston.verbose('retrieved document', { key: key });
|
||||||
|
res.status(200).json({ data: data, key: key });
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle retrieving the raw version of a document
|
// Handle retrieving the raw version of a document
|
||||||
DocumentHandler.prototype.handleGetRaw = async function(key, res, skipExpire){
|
DocumentHandler.prototype.handleGetRaw = async function(key, res, skipExpire){
|
||||||
await this.store.get(key, function(ret){
|
const data = await this.store.get(key, skipExpire);
|
||||||
if (ret){
|
|
||||||
winston.verbose('retrieved raw document', { key: key });
|
if (!data){
|
||||||
res.writeHead(200, { 'content-type': 'text/plain; charset=UTF-8' });
|
|
||||||
res.end(ret);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
winston.warn('raw document not found', { key: key });
|
winston.warn('raw document not found', { key: key });
|
||||||
res.writeHead(404, { 'content-type': 'application/json' });
|
res.status(404).json({ message: 'Document not found.' });
|
||||||
res.end(JSON.stringify({ message: 'Document not found.' }));
|
return;
|
||||||
}
|
}
|
||||||
}, skipExpire);
|
winston.verbose('retrieved raw document', { key: key });
|
||||||
|
res.status(200).end(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle adding a new Document
|
// Handle adding a new Document
|
||||||
|
@ -51,40 +47,12 @@ DocumentHandler.prototype.handlePost = function (req, res){
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
// What to do when done
|
//parse a form to grab the data
|
||||||
let onSuccess = async function (){
|
|
||||||
// Check length
|
|
||||||
if (_this.maxLength && buffer.length > _this.maxLength){
|
|
||||||
cancelled = true;
|
|
||||||
winston.warn('document >maxLength', { maxLength: _this.maxLength });
|
|
||||||
res.writeHead(400, { 'content-type': 'application/json' });
|
|
||||||
res.end(
|
|
||||||
JSON.stringify({ message: 'Document exceeds maximum length.' })
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// And then save if we should
|
|
||||||
await _this.chooseKey(async function (key){
|
|
||||||
await _this.store.set(key, buffer, function (resp){
|
|
||||||
if (resp){
|
|
||||||
winston.verbose('added document', { key: key });
|
|
||||||
res.writeHead(200, { 'content-type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ key: key }));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
winston.verbose('error adding document');
|
|
||||||
res.writeHead(500, { 'content-type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ message: 'Error adding document.' }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we should, parse a form to grab the data
|
|
||||||
let ct = req.headers['content-type'];
|
let ct = req.headers['content-type'];
|
||||||
if (ct && ct.split(';')[0] == 'multipart/form-data'){
|
if (ct && ct.split(';')[0] == 'multipart/form-data'){
|
||||||
|
winston.debug('okayge');
|
||||||
let busboy = new Busboy({ headers: req.headers });
|
let busboy = new Busboy({ headers: req.headers });
|
||||||
busboy.on('field', function (fieldname, val){
|
busboy.on('field', (fieldname, val) => {
|
||||||
if (fieldname == 'data'){
|
if (fieldname == 'data'){
|
||||||
buffer = val;
|
buffer = val;
|
||||||
}
|
}
|
||||||
|
@ -93,35 +61,50 @@ DocumentHandler.prototype.handlePost = function (req, res){
|
||||||
await onSuccess();
|
await onSuccess();
|
||||||
});
|
});
|
||||||
req.pipe(busboy);
|
req.pipe(busboy);
|
||||||
// Otherwise, use our own and just grab flat data from POST body
|
//otherwise, grab flat data from POST body
|
||||||
} else {
|
}
|
||||||
req.on('data', function (data){
|
else {
|
||||||
|
req.on('data', data => {
|
||||||
buffer += data.toString();
|
buffer += data.toString();
|
||||||
});
|
});
|
||||||
req.on('end', async function (){
|
req.on('end', async () => {
|
||||||
if (cancelled){ return; }
|
if (cancelled) return;
|
||||||
await onSuccess();
|
await onSuccess();
|
||||||
});
|
});
|
||||||
req.on('error', function (error){
|
req.on('error', error => {
|
||||||
winston.error('connection error: ' + error.message);
|
winston.error(`busboy connection error: ${error.message}`);
|
||||||
res.writeHead(500, { 'content-type': 'application/json' });
|
res.status(500).json({ message: 'Internal server error occured while adding document.' });
|
||||||
res.end(JSON.stringify({ message: 'Connection error.' }));
|
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let onSuccess = async function (){
|
||||||
|
//check length
|
||||||
|
if (_this.maxLength && buffer.length > _this.maxLength){
|
||||||
|
cancelled = true;
|
||||||
|
winston.warn('document >maxLength', { maxLength: _this.maxLength });
|
||||||
|
res.status(413).json({ message: 'Document exceeds maximum length.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//and save
|
||||||
|
const key = await _this.chooseKey();
|
||||||
|
const success = await _this.store.set(key, buffer);
|
||||||
|
if (!success){
|
||||||
|
winston.verbose('error adding document');
|
||||||
|
res.status(500).json({ message: 'Internal server error occured while adding document.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
winston.verbose('added document', { key: key });
|
||||||
|
res.status(200).json({ key: key });
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep choosing keys until one isn't taken
|
//keep choosing keys until one isn't taken
|
||||||
DocumentHandler.prototype.chooseKey = async function(callback){
|
DocumentHandler.prototype.chooseKey = async function(){
|
||||||
let key = this.acceptableKey();
|
let key = this.acceptableKey();
|
||||||
let _this = this;
|
let data = await this.store.get(key, true); //don't bump expirations on key searching
|
||||||
await this.store.get(key, function(ret){
|
if (data) return this.chooseKey();
|
||||||
if (ret){
|
return key;
|
||||||
_this.chooseKey(callback);
|
|
||||||
} else {
|
|
||||||
callback(key);
|
|
||||||
}
|
|
||||||
}, true); // Don't bump expirations when key searching
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DocumentHandler.prototype.acceptableKey = function(){
|
DocumentHandler.prototype.acceptableKey = function(){
|
||||||
|
|
|
@ -10,7 +10,7 @@ class FileDocumentStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
//save data in a file, key as md5 - since we don't know what we can passed here
|
//save data in a file, key as md5 - since we don't know what we can passed here
|
||||||
async set(key, data, callback, skipExpire){
|
async set(key, data, skipExpire){
|
||||||
const _this = this;
|
const _this = this;
|
||||||
const filePath = this.getPath(key);
|
const filePath = this.getPath(key);
|
||||||
|
|
||||||
|
@ -23,35 +23,35 @@ class FileDocumentStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
winston.silly('set key', { type: 'file', filename: filePath });
|
winston.silly('set key', { type: 'file', filename: filePath });
|
||||||
await fs.promises.writeFile(filePath, data, {mode: '600'})
|
return await fs.promises.writeFile(filePath, data, {mode: '600'})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
callback(true);
|
|
||||||
if (_this.expire && !skipExpire){
|
if (_this.expire && !skipExpire){
|
||||||
winston.warn('file store cannot set expirations on keys', { file: filePath });
|
winston.warn('file store doesn\'t support expiration', { file: filePath });
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
winston.error('error while writing document to file', { file: filePath, error: err });
|
winston.error('error while writing document to file', { file: filePath, error: err });
|
||||||
callback(false);
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//get data from a file
|
//get data from a file
|
||||||
async get(key, callback, skipExpire){
|
async get(key, skipExpire){
|
||||||
const _this = this;
|
const _this = this;
|
||||||
const filePath = this.getPath(key);
|
const filePath = this.getPath(key);
|
||||||
|
|
||||||
winston.silly('get key', { type: 'file', filename: filePath });
|
winston.silly('get key', { type: 'file', filename: filePath });
|
||||||
await fs.promises.readFile(filePath, {encoding: 'utf8'})
|
return await fs.promises.readFile(filePath, {encoding: 'utf8'})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
callback(data);
|
|
||||||
if (_this.expire && !skipExpire){
|
if (_this.expire && !skipExpire){
|
||||||
winston.warn('file store cannot set expirations on keys', { file: filePath });
|
winston.warn('file store cannot set expirations on keys', { file: filePath });
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
winston.debug('error while reading document', { file: filePath, error: err });
|
winston.debug('error while reading document', { file: filePath, error: err });
|
||||||
return callback(false);
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const mongodb = require('mongodb');
|
const mongodb = require('mongodb');
|
||||||
|
|
||||||
const MongoDocumentStore = function (config){
|
class MongoDocumentStore {
|
||||||
|
constructor(config){
|
||||||
this.expire = config.expire;
|
this.expire = config.expire;
|
||||||
this.MongoClient = new mongodb.MongoClient(config.connectionUri, config.clientOptions);
|
this.MongoClient = new mongodb.MongoClient(config.connectionUri, config.clientOptions);
|
||||||
};
|
}
|
||||||
|
|
||||||
MongoDocumentStore.prototype.set = async function (key, data, callback, skipExpire){
|
async set(key, data, callback, skipExpire){
|
||||||
winston.silly(`mongo set ${key}`);
|
winston.silly(`mongo set ${key}`);
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const that = this;
|
const that = this;
|
||||||
|
|
||||||
await this.safeConnect(async ( {error} = {} ) => {
|
if ((await this.safeConnect()).error) return null;
|
||||||
if (error) return callback(false);
|
|
||||||
|
|
||||||
await this.MongoClient.db().collection('entries').updateOne(
|
return await this.MongoClient.db().collection('entries').updateOne(
|
||||||
{
|
{
|
||||||
'entry_id': key,
|
'entry_id': key,
|
||||||
$or: [
|
$or: [
|
||||||
|
@ -34,22 +34,19 @@ MongoDocumentStore.prototype.set = async function (key, data, callback, skipExpi
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((err, result) => {
|
.then((err, result) => {
|
||||||
return callback(true);
|
return true;
|
||||||
})
|
})
|
||||||
.catch((err, result) => {
|
.catch((err, result) => {
|
||||||
winston.error('error updating mongodb document', { error: err });
|
winston.error('error updating mongodb document', { error: err });
|
||||||
return callback(false);
|
return false;
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
};
|
async get(key, skipExpire){
|
||||||
|
|
||||||
MongoDocumentStore.prototype.get = async function (key, callback, skipExpire){
|
|
||||||
winston.silly(`mongo get ${key}`);
|
winston.silly(`mongo get ${key}`);
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const that = this;
|
const that = this;
|
||||||
|
|
||||||
await this.safeConnect(async ( {error} = {} ) => {
|
if ((await this.safeConnect()).error) return null;
|
||||||
if (error) return callback(false);
|
|
||||||
|
|
||||||
let document = await this.MongoClient.db().collection('entries').findOne({
|
let document = await this.MongoClient.db().collection('entries').findOne({
|
||||||
'entry_id': key,
|
'entry_id': key,
|
||||||
|
@ -57,14 +54,11 @@ MongoDocumentStore.prototype.get = async function (key, callback, skipExpire){
|
||||||
{ expiration: -1 },
|
{ expiration: -1 },
|
||||||
{ expiration: { $gt: now } }
|
{ expiration: { $gt: now } }
|
||||||
]
|
]
|
||||||
})
|
}).catch(err => {
|
||||||
.catch(err => {
|
|
||||||
winston.error('error finding mongodb document', { error: err });
|
winston.error('error finding mongodb document', { error: err });
|
||||||
return callback(false);
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
callback(document ? document.value : false);
|
|
||||||
|
|
||||||
if (document && document.expiration != -1 && that.expire && !skipExpire) {
|
if (document && document.expiration != -1 && that.expire && !skipExpire) {
|
||||||
await this.MongoClient.db().collection('entries').updateOne(
|
await this.MongoClient.db().collection('entries').updateOne(
|
||||||
{ 'entry_id': key },
|
{ 'entry_id': key },
|
||||||
|
@ -74,22 +68,25 @@ MongoDocumentStore.prototype.get = async function (key, callback, skipExpire){
|
||||||
});
|
});
|
||||||
winston.silly('extended expiry of mongodb document', { key: key, timestamp: that.expire + now });
|
winston.silly('extended expiry of mongodb document', { key: key, timestamp: that.expire + now });
|
||||||
}
|
}
|
||||||
});
|
return document ? document.value : null;
|
||||||
};
|
}
|
||||||
|
safeConnect(){
|
||||||
MongoDocumentStore.prototype.safeConnect = function(cb){
|
|
||||||
//don't try connecting again if already connected
|
//don't try connecting again if already connected
|
||||||
//https://jira.mongodb.org/browse/NODE-1868
|
//https://jira.mongodb.org/browse/NODE-1868
|
||||||
if (this.MongoClient.isConnected()) return cb({error: null});
|
if (this.MongoClient.isConnected()) return { error: null };
|
||||||
this.MongoClient.connect()
|
return this.MongoClient.connect()
|
||||||
.then(client => {
|
.then(client => {
|
||||||
winston.debug('connected to mongodb', { success: true });
|
winston.debug('connected to mongodb', { success: true });
|
||||||
cb({error: null});
|
return { error: null };
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
winston.error('error connecting to mongodb', { error: err });
|
winston.error('error connecting to mongodb', { error: err });
|
||||||
cb({error: err});
|
return { error: err };
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = MongoDocumentStore;
|
module.exports = MongoDocumentStore;
|
|
@ -18,30 +18,34 @@ class RedisDocumentStore {
|
||||||
winston.info('initialized redis client');
|
winston.info('initialized redis client');
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(key, data, callback, skipExpire){
|
async set(key, data, skipExpire){
|
||||||
await this.client.set(key, data).catch(err => {
|
return await this.client.set(key, data)
|
||||||
winston.error('failed to call redisClient.set', {error: err});
|
.then(() => {
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
if (!skipExpire) this.setExpiration(key);
|
if (!skipExpire) this.setExpiration(key);
|
||||||
callback(true);
|
return true;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
winston.error('failed to set redis document', {error: err});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(key, callback, skipExpire){
|
async get(key, skipExpire){
|
||||||
let data = await this.client.get(key).catch(err => {
|
return await this.client.get(key)
|
||||||
winston.error('failed to get document from redis', {key: key, error: err});
|
.then(data => {
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
if (!skipExpire) this.setExpiration(key);
|
if (!skipExpire) this.setExpiration(key);
|
||||||
callback(data);
|
return data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
winston.error('failed to get redis document', {key: key, error: err});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExpiration(key){
|
async setExpiration(key){
|
||||||
if (!this.expire) return;
|
if (!this.expire) return;
|
||||||
await this.client.expire(key, this.expire).catch(err => {
|
await this.client.expire(key, this.expire).catch(err => {
|
||||||
winston.warn('failed to set expiry on key', {key: key, error: err});
|
winston.warn('failed to set redis key expiry', {key: key, error: err});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue