From 6fcd19ba737f1f5614a56c6925adb882dea43b8d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Jul 2017 13:26:25 +0200 Subject: Move to promises Closes https://github.com/Chocobozzz/PeerTube/issues/74 --- server/helpers/core-utils.ts | 89 +++++++++++++++++++++++++++++- server/helpers/database-utils.ts | 69 ++++++++---------------- server/helpers/peertube-crypto.ts | 111 +++++++++++++++++--------------------- server/helpers/requests.ts | 81 +++++++++++++++------------- server/helpers/utils.ts | 24 +++------ 5 files changed, 209 insertions(+), 165 deletions(-) (limited to 'server/helpers') diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 32b89b6dd..1e92049f1 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts @@ -4,6 +4,20 @@ */ import { join } from 'path' +import { pseudoRandomBytes } from 'crypto' +import { + readdir, + readFile, + rename, + unlink, + writeFile, + access +} from 'fs' +import * as mkdirp from 'mkdirp' +import * as bcrypt from 'bcrypt' +import * as createTorrent from 'create-torrent' +import * as openssl from 'openssl-wrapper' +import * as Promise from 'bluebird' function isTestInstance () { return process.env.NODE_ENV === 'test' @@ -14,9 +28,82 @@ function root () { return join(__dirname, '..', '..', '..') } +function promisify0 (func: (cb: (err: any, result: A) => void) => void): () => Promise { + return function promisified (): Promise { + return new Promise((resolve: (arg: A) => void, reject: (err: any) => void) => { + func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ]) + }) + } +} + +// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2 +function promisify1 (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise { + return function promisified (arg: T): Promise { + return new Promise((resolve: (arg: A) => void, reject: (err: any) => void) => { + func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ]) + }) + } +} + +function promisify1WithVoid (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise { + return function promisified (arg: T): Promise { + return new Promise((resolve: () => void, reject: (err: any) => void) => { + func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ]) + }) + } +} + +function promisify2 (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise { + return function promisified (arg1: T, arg2: U): Promise { + return new Promise((resolve: (arg: A) => void, reject: (err: any) => void) => { + func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ]) + }) + } +} + +function promisify2WithVoid (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise { + return function promisified (arg1: T, arg2: U): Promise { + return new Promise((resolve: () => void, reject: (err: any) => void) => { + func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ]) + }) + } +} + +const readFilePromise = promisify2(readFile) +const readFileBufferPromise = promisify1(readFile) +const unlinkPromise = promisify1WithVoid(unlink) +const renamePromise = promisify2WithVoid(rename) +const writeFilePromise = promisify2(writeFile) +const readdirPromise = promisify1(readdir) +const mkdirpPromise = promisify1(mkdirp) +const pseudoRandomBytesPromise = promisify1(pseudoRandomBytes) +const accessPromise = promisify1WithVoid(access) +const opensslExecPromise = promisify2WithVoid(openssl.exec) +const bcryptComparePromise = promisify2(bcrypt.compare) +const bcryptGenSaltPromise = promisify1(bcrypt.genSalt) +const bcryptHashPromise = promisify2(bcrypt.hash) +const createTorrentPromise = promisify2(createTorrent) + // --------------------------------------------------------------------------- export { isTestInstance, - root + root, + + promisify0, + promisify1, + readdirPromise, + readFilePromise, + readFileBufferPromise, + unlinkPromise, + renamePromise, + writeFilePromise, + mkdirpPromise, + pseudoRandomBytesPromise, + accessPromise, + opensslExecPromise, + bcryptComparePromise, + bcryptGenSaltPromise, + bcryptHashPromise, + createTorrentPromise } diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index f8ee9a454..f761cfb89 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts @@ -1,70 +1,45 @@ -import * as Sequelize from 'sequelize' // TODO: import from ES6 when retry typing file will include errorFilter function import * as retry from 'async/retry' +import * as Promise from 'bluebird' -import { database as db } from '../initializers/database' import { logger } from './logger' -function commitTransaction (t: Sequelize.Transaction, callback: (err: Error) => void) { - return t.commit().asCallback(callback) -} - -function rollbackTransaction (err: Error, t: Sequelize.Transaction, callback: (err: Error) => void) { - // Try to rollback transaction - if (t) { - // Do not catch err, report the original one - t.rollback().asCallback(function () { - return callback(err) - }) - } else { - return callback(err) - } -} - type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } -function retryTransactionWrapper (functionToRetry: Function, options: RetryTransactionWrapperOptions, finalCallback: Function) { +function retryTransactionWrapper (functionToRetry: (... args) => Promise, options: RetryTransactionWrapperOptions) { const args = options.arguments ? options.arguments : [] - transactionRetryer( + return transactionRetryer( function (callback) { - return functionToRetry.apply(this, args.concat([ callback ])) - }, - function (err) { - if (err) { - logger.error(options.errorMessage, { error: err }) - } - - // Do not return the error, continue the process - return finalCallback(null) + functionToRetry.apply(this, args) + .then(result => callback(null, result)) + .catch(err => callback(err)) } ) + .catch(err => { + // Do not throw the error, continue the process + logger.error(options.errorMessage, { error: err }) + }) } -function transactionRetryer (func: Function, callback: (err: Error) => void) { - retry({ - times: 5, - - errorFilter: function (err) { - const willRetry = (err.name === 'SequelizeDatabaseError') - logger.debug('Maybe retrying the transaction function.', { willRetry }) - return willRetry - } - }, func, callback) -} +function transactionRetryer (func: Function) { + return new Promise((res, rej) => { + retry({ + times: 5, -function startSerializableTransaction (callback: (err: Error, t: Sequelize.Transaction) => void) { - db.sequelize.transaction(/* { isolationLevel: 'SERIALIZABLE' } */).asCallback(function (err, t) { - // We force to return only two parameters - return callback(err, t) + errorFilter: function (err) { + const willRetry = (err.name === 'SequelizeDatabaseError') + logger.debug('Maybe retrying the transaction function.', { willRetry }) + return willRetry + } + }, func, function (err) { + err ? rej(err) : res() + }) }) } // --------------------------------------------------------------------------- export { - commitTransaction, retryTransactionWrapper, - rollbackTransaction, - startSerializableTransaction, transactionRetryer } diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 0ac875127..8e8001cd6 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,7 +1,5 @@ import * as crypto from 'crypto' -import * as bcrypt from 'bcrypt' import * as fs from 'fs' -import * as openssl from 'openssl-wrapper' import { join } from 'path' import { @@ -12,6 +10,14 @@ import { BCRYPT_SALT_SIZE, PUBLIC_CERT_NAME } from '../initializers' +import { + readFilePromise, + bcryptComparePromise, + bcryptGenSaltPromise, + bcryptHashPromise, + accessPromise, + opensslExecPromise +} from './core-utils' import { logger } from './logger' function checkSignature (publicKey: string, data: string, hexSignature: string) { @@ -60,46 +66,32 @@ function sign (data: string|Object) { return signature } -function comparePassword (plainPassword: string, hashPassword: string, callback: (err: Error, match?: boolean) => void) { - bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { - if (err) return callback(err) - - return callback(null, isPasswordMatch) - }) +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) } -function createCertsIfNotExist (callback: (err: Error) => void) { - certsExist(function (err, exist) { - if (err) return callback(err) - +function createCertsIfNotExist () { + return certsExist().then(exist => { if (exist === true) { - return callback(null) + return undefined } - createCerts(function (err) { - return callback(err) - }) + return createCerts() }) } -function cryptPassword (password: string, callback: (err: Error, hash?: string) => void) { - bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) { - if (err) return callback(err) - - bcrypt.hash(password, salt, function (err, hash) { - return callback(err, hash) - }) - }) +function cryptPassword (password: string) { + return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt)) } -function getMyPrivateCert (callback: (err: Error, privateCert: string) => void) { +function getMyPrivateCert () { const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - fs.readFile(certPath, 'utf8', callback) + return readFilePromise(certPath, 'utf8') } -function getMyPublicCert (callback: (err: Error, publicCert: string) => void) { +function getMyPublicCert () { const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) - fs.readFile(certPath, 'utf8', callback) + return readFilePromise(certPath, 'utf8') } // --------------------------------------------------------------------------- @@ -116,23 +108,21 @@ export { // --------------------------------------------------------------------------- -function certsExist (callback: (err: Error, certsExist: boolean) => void) { +function certsExist () { const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - fs.access(certPath, function (err) { - // If there is an error the certificates do not exist - const exists = !err - return callback(null, exists) - }) -} -function createCerts (callback: (err: Error) => void) { - certsExist(function (err, exist) { - if (err) return callback(err) + // If there is an error the certificates do not exist + return accessPromise(certPath) + .then(() => true) + .catch(() => false) +} +function createCerts () { + return certsExist().then(exist => { if (exist === true) { const errorMessage = 'Certs already exist.' logger.warning(errorMessage) - return callback(new Error(errorMessage)) + throw new Error(errorMessage) } logger.info('Generating a RSA key...') @@ -142,30 +132,27 @@ function createCerts (callback: (err: Error) => void) { 'out': privateCertPath, '2048': false } - openssl.exec('genrsa', genRsaOptions, function (err) { - if (err) { - logger.error('Cannot create private key on this pod.') - return callback(err) - } - - logger.info('RSA key generated.') - logger.info('Managing public key...') - - const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') - const rsaOptions = { - 'in': privateCertPath, - 'pubout': true, - 'out': publicCertPath - } - openssl.exec('rsa', rsaOptions, function (err) { - if (err) { - logger.error('Cannot create public key on this pod.') - return callback(err) + return opensslExecPromise('genrsa', genRsaOptions) + .then(() => { + logger.info('RSA key generated.') + logger.info('Managing public key...') + + const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') + const rsaOptions = { + 'in': privateCertPath, + 'pubout': true, + 'out': publicCertPath } - - logger.info('Public key managed.') - return callback(null) + return opensslExecPromise('rsa', rsaOptions) + .then(() => logger.info('Public key managed.')) + .catch(err => { + logger.error('Cannot create public key on this pod.') + throw err + }) + }) + .catch(err => { + logger.error('Cannot create private key on this pod.') + throw err }) - }) }) } diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index b40fc8e39..b31074373 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts @@ -1,5 +1,6 @@ import * as replay from 'request-replay' import * as request from 'request' +import * as Promise from 'bluebird' import { RETRY_REQUESTS, @@ -14,16 +15,18 @@ type MakeRetryRequestParams = { method: 'GET'|'POST', json: Object } -function makeRetryRequest (params: MakeRetryRequestParams, callback: request.RequestCallback) { - replay( - request(params, callback), - { - retries: RETRY_REQUESTS, - factor: 3, - maxTimeout: Infinity, - errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] - } - ) +function makeRetryRequest (params: MakeRetryRequestParams) { + return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { + replay( + request(params, (err, response, body) => err ? rej(err) : res({ response, body })), + { + retries: RETRY_REQUESTS, + factor: 3, + maxTimeout: Infinity, + errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] + } + ) + }) } type MakeSecureRequestParams = { @@ -33,41 +36,43 @@ type MakeSecureRequestParams = { sign: boolean data?: Object } -function makeSecureRequest (params: MakeSecureRequestParams, callback: request.RequestCallback) { - const requestParams = { - url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, - json: {} - } +function makeSecureRequest (params: MakeSecureRequestParams) { + return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { + const requestParams = { + url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, + json: {} + } - if (params.method !== 'POST') { - return callback(new Error('Cannot make a secure request with a non POST method.'), null, null) - } + if (params.method !== 'POST') { + return rej(new Error('Cannot make a secure request with a non POST method.')) + } - // Add signature if it is specified in the params - if (params.sign === true) { - const host = CONFIG.WEBSERVER.HOST + // Add signature if it is specified in the params + if (params.sign === true) { + const host = CONFIG.WEBSERVER.HOST - let dataToSign - if (params.data) { - dataToSign = params.data - } else { - // We do not have data to sign so we just take our host - // It is not ideal but the connection should be in HTTPS - dataToSign = host - } + let dataToSign + if (params.data) { + dataToSign = params.data + } else { + // We do not have data to sign so we just take our host + // It is not ideal but the connection should be in HTTPS + dataToSign = host + } - requestParams.json['signature'] = { - host, // Which host we pretend to be - signature: sign(dataToSign) + requestParams.json['signature'] = { + host, // Which host we pretend to be + signature: sign(dataToSign) + } } - } - // If there are data informations - if (params.data) { - requestParams.json['data'] = params.data - } + // If there are data informations + if (params.data) { + requestParams.json['data'] = params.data + } - request.post(requestParams, callback) + request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body })) + }) } // --------------------------------------------------------------------------- diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index bbf135fa1..e99a48393 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -1,25 +1,14 @@ import * as express from 'express' -import { pseudoRandomBytes } from 'crypto' - -import { logger } from './logger' +import { pseudoRandomBytesPromise } from './core-utils' +import { ResultList } from '../../shared' function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { res.type('json').status(400).end() } -function generateRandomString (size: number, callback: (err: Error, randomString?: string) => void) { - pseudoRandomBytes(size, function (err, raw) { - if (err) return callback(err) - - callback(null, raw.toString('hex')) - }) -} - -function createEmptyCallback () { - return function (err) { - if (err) logger.error('Error in empty callback.', { error: err }) - } +function generateRandomString (size: number) { + return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex')) } interface FormatableToJSON { @@ -33,17 +22,18 @@ function getFormatedObjects (objects: T[], object formatedObjects.push(object.toFormatedJSON()) }) - return { + const res: ResultList = { total: objectsTotal, data: formatedObjects } + + return res } // --------------------------------------------------------------------------- export { badRequest, - createEmptyCallback, generateRandomString, getFormatedObjects } -- cgit v1.2.3