1
0
Fork 0
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:
zneix 2020-09-02 16:16:54 +02:00
parent c165781b18
commit 7de68695fe
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: 911916E0523B22F6
4 changed files with 130 additions and 146 deletions

View file

@ -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(){

View file

@ -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;
}); });
} }

View file

@ -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;

View file

@ -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});
}); });
} }
} }