From 2d6658717618495d5e4fd9f9fa794442ea727cc8 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 22 Sep 2016 14:28:34 -0400 Subject: [PATCH 1/2] adds CLI for parse-live-query-server, adds ability to start parse-server with live-query server --- bin/parse-live-query-server | 3 + .../definitions/parse-live-query-server.js | 48 +++++++ .../parse-server.js} | 82 +++++------- src/cli/parse-live-query-server.js | 15 +++ src/cli/parse-server.js | 118 +++++++++--------- src/cli/utils/parsers.js | 59 +++++++++ src/cli/utils/runner.js | 37 ++++++ src/logger.js | 15 ++- 8 files changed, 266 insertions(+), 111 deletions(-) create mode 100755 bin/parse-live-query-server create mode 100644 src/cli/definitions/parse-live-query-server.js rename src/cli/{cli-definitions.js => definitions/parse-server.js} (86%) create mode 100644 src/cli/parse-live-query-server.js create mode 100644 src/cli/utils/parsers.js create mode 100644 src/cli/utils/runner.js diff --git a/bin/parse-live-query-server b/bin/parse-live-query-server new file mode 100755 index 0000000000..8f22879d85 --- /dev/null +++ b/bin/parse-live-query-server @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require("../lib/cli/parse-live-query-server"); diff --git a/src/cli/definitions/parse-live-query-server.js b/src/cli/definitions/parse-live-query-server.js new file mode 100644 index 0000000000..846eec4282 --- /dev/null +++ b/src/cli/definitions/parse-live-query-server.js @@ -0,0 +1,48 @@ +import { + numberParser, + numberOrBoolParser, + objectParser, + arrayParser, + moduleOrObjectParser, + booleanParser, + nullParser +} from '../utils/parsers'; + + +export default { + "appId": { + required: true, + help: "Required. This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId." + }, + "masterKey": { + required: true, + help: "Required. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey." + }, + "serverURL": { + required: true, + help: "Required. This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL." + }, + "redisURL": { + help: "Optional. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey." + }, + "keyPairs": { + help: "Optional. A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details." + }, + "websocketTimeout": { + help: "Optional. Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).", + action: numberParser("websocketTimeout") + }, + "cacheTimeout": { + help: "Optional. Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).", + action: numberParser("cacheTimeout") + }, + "logLevel": { + help: "Optional. This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO.", + }, + "port": { + env: "PORT", + help: "The port to run the ParseServer. defaults to 1337.", + default: 1337, + action: numberParser("port") + }, +}; diff --git a/src/cli/cli-definitions.js b/src/cli/definitions/parse-server.js similarity index 86% rename from src/cli/cli-definitions.js rename to src/cli/definitions/parse-server.js index 300a40c5b6..679f3963d0 100644 --- a/src/cli/cli-definitions.js +++ b/src/cli/definitions/parse-server.js @@ -1,52 +1,13 @@ -function numberParser(key) { - return function(opt) { - opt = parseInt(opt); - if (!Number.isInteger(opt)) { - throw new Error(`The ${key} is invalid`); - } - return opt; - } -} +import { + numberParser, + numberOrBoolParser, + objectParser, + arrayParser, + moduleOrObjectParser, + booleanParser, + nullParser +} from '../utils/parsers'; -function numberOrBoolParser(key) { - return function(opt) { - if (typeof opt === 'boolean') { - return opt; - } - return numberParser(key)(opt); - } -} - -function objectParser(opt) { - if (typeof opt == 'object') { - return opt; - } - return JSON.parse(opt) -} - -function moduleOrObjectParser(opt) { - if (typeof opt == 'object') { - return opt; - } - try { - return JSON.parse(opt); - } catch(e) {} - return opt; -} - -function booleanParser(opt) { - if (opt == true || opt == "true" || opt == "1") { - return true; - } - return false; -} - -function nullParser(opt) { - if (opt == 'null') { - return null; - } - return opt; -} export default { "appId": { @@ -128,9 +89,7 @@ export default { env: "PARSE_SERVER_FACEBOOK_APP_IDS", help: "Comma separated list for your facebook app Ids", type: "list", - action: function(opt) { - return opt.split(",") - } + action: arrayParser }, "enableAnonymousUsers": { env: "PARSE_SERVER_ENABLE_ANON_USERS", @@ -239,5 +198,24 @@ export default { "cluster": { help: "Run with cluster, optionally set the number of processes default to os.cpus().length", action: numberOrBoolParser("cluster") - } + }, + "liveQuery.classNames": { + help: "parse-server's LiveQuery configuration object", + action: objectParser + }, + "liveQuery.classNames": { + help: "parse-server's LiveQuery classNames", + action: arrayParser + }, + "liveQuery.redisURL": { + help: "parse-server's LiveQuery redisURL", + }, + "startLiveQueryServer": { + help: "Starts the liveQuery server", + action: booleanParser + }, + "liveQueryServerOptions": { + help: "Live query server configuration options (will start the liveQuery server)", + action: objectParser + }, }; diff --git a/src/cli/parse-live-query-server.js b/src/cli/parse-live-query-server.js new file mode 100644 index 0000000000..6550de3068 --- /dev/null +++ b/src/cli/parse-live-query-server.js @@ -0,0 +1,15 @@ +import definitions from './definitions/parse-live-query-server'; +import runner from './utils/runner'; +import { ParseServer } from '../index'; +import express from 'express'; + +runner({ + definitions, + start: function(program, options, logOptions) { + logOptions(); + var app = express(); + var httpServer = require('http').createServer(app); + httpServer.listen(options.port); + ParseServer.createLiveQueryServer(httpServer, options); + } +}) diff --git a/src/cli/parse-server.js b/src/cli/parse-server.js index a09445f434..facbc91061 100755 --- a/src/cli/parse-server.js +++ b/src/cli/parse-server.js @@ -1,18 +1,12 @@ import path from 'path'; import express from 'express'; import { ParseServer } from '../index'; -import definitions from './cli-definitions'; -import program from './utils/commander'; -import { mergeWithOptions } from './utils/commander'; +import definitions from './definitions/parse-server'; import cluster from 'cluster'; import os from 'os'; +import runner from './utils/runner'; -program.loadDefinitions(definitions); - -program - .usage('[options] '); - -program.on('--help', function(){ +const help = function(){ console.log(' Get Started guide:'); console.log(''); console.log(' Please have a look at the get started guide!') @@ -32,33 +26,7 @@ program.on('--help', function(){ console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); console.log(' $ parse-server -- --appId APP_ID --masterKey MASTER_KEY --serverURL serverURL'); console.log(''); -}); - -program.parse(process.argv, process.env); - -let options = program.getOptions(); - -if (!options.serverURL) { - options.serverURL = `http://localhost:${options.port}${options.mountPath}`; -} - -if (!options.appId || !options.masterKey || !options.serverURL) { - program.outputHelp(); - console.error(""); - console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m'); - console.error(""); - process.exit(1); -} - -function logStartupOptions(options) { - for (let key in options) { - let value = options[key]; - if (key == "masterKey") { - value = "***REDACTED***"; - } - console.log(`${key}: ${value}`); - } -} +}; function startServer(options, callback) { const app = express(); @@ -66,7 +34,9 @@ function startServer(options, callback) { app.use(options.mountPath, api); var server = app.listen(options.port, callback); - + if (options.startLiveQueryServer || options.liveQueryServerOptions) { + ParseServer.createLiveQueryServer(server, options.liveQueryServerOptions); + } var handleShutdown = function() { console.log('Termination signal received. Shutting down.'); server.close(function () { @@ -77,27 +47,59 @@ function startServer(options, callback) { process.on('SIGINT', handleShutdown); } -if (options.cluster) { - const numCPUs = typeof options.cluster === 'number' ? options.cluster : os.cpus().length; - if (cluster.isMaster) { - logStartupOptions(options); - for(var i = 0; i < numCPUs; i++) { - cluster.fork(); + +runner({ + definitions, + help, + usage: '[options] ', + start: function(program, options, logOptions) { + if (!options.serverURL) { + options.serverURL = `http://localhost:${options.port}${options.mountPath}`; + } + + if (!options.appId || !options.masterKey || !options.serverURL) { + program.outputHelp(); + console.error(""); + console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m'); + console.error(""); + process.exit(1); + } + + if (options["liveQuery.classNames"]) { + options.liveQuery = options.liveQuery || {}; + options.liveQuery.classNames = options["liveQuery.classNames"]; + delete options["liveQuery.classNames"]; + } + if (options["liveQuery.redisURL"]) { + options.liveQuery = options.liveQuery || {}; + options.liveQuery.redisURL = options["liveQuery.redisURL"]; + delete options["liveQuery.redisURL"]; + } + + if (options.cluster) { + const numCPUs = typeof options.cluster === 'number' ? options.cluster : os.cpus().length; + if (cluster.isMaster) { + for(var i = 0; i < numCPUs; i++) { + cluster.fork(); + } + cluster.on('exit', (worker, code, signal) => { + console.log(`worker ${worker.process.pid} died... Restarting`); + cluster.fork(); + }); + } else { + startServer(options, () => { + console.log('['+process.pid+'] parse-server running on '+options.serverURL); + }); + } + } else { + startServer(options, () => { + logOptions(); + console.log(''); + console.log('['+process.pid+'] parse-server running on '+options.serverURL); + }); } - cluster.on('exit', (worker, code, signal) => { - console.log(`worker ${worker.process.pid} died... Restarting`); - cluster.fork(); - }); - } else { - startServer(options, () => { - console.log('['+process.pid+'] parse-server running on '+options.serverURL); - }); } -} else { - startServer(options, () => { - logStartupOptions(options); - console.log(''); - console.log('['+process.pid+'] parse-server running on '+options.serverURL); - }); -} +}) + + diff --git a/src/cli/utils/parsers.js b/src/cli/utils/parsers.js new file mode 100644 index 0000000000..7d47de5286 --- /dev/null +++ b/src/cli/utils/parsers.js @@ -0,0 +1,59 @@ +export function numberParser(key) { + return function(opt) { + opt = parseInt(opt); + if (!Number.isInteger(opt)) { + throw new Error(`The ${key} is invalid`); + } + return opt; + } +} + +export function numberOrBoolParser(key) { + return function(opt) { + if (typeof opt === 'boolean') { + return opt; + } + return numberParser(key)(opt); + } +} + +export function objectParser(opt) { + if (typeof opt == 'object') { + return opt; + } + return JSON.parse(opt) +} + +export function arrayParser(opt) { + if (Array.isArray(opt)) { + return opt; + } else if (typeof opt === 'string') { + return opt.split(','); + } else { + throw new Error(`${opt} should be a comma separated string or an array`); + } +} + +export function moduleOrObjectParser(opt) { + if (typeof opt == 'object') { + return opt; + } + try { + return JSON.parse(opt); + } catch(e) {} + return opt; +} + +export function booleanParser(opt) { + if (opt == true || opt == "true" || opt == "1") { + return true; + } + return false; +} + +export function nullParser(opt) { + if (opt == 'null') { + return null; + } + return opt; +} diff --git a/src/cli/utils/runner.js b/src/cli/utils/runner.js new file mode 100644 index 0000000000..d4a362ec79 --- /dev/null +++ b/src/cli/utils/runner.js @@ -0,0 +1,37 @@ + +import program from './commander'; +import { mergeWithOptions } from './commander'; + +function logStartupOptions(options) { + for (let key in options) { + let value = options[key]; + if (key == "masterKey") { + value = "***REDACTED***"; + } + if (typeof value === 'object') { + value = JSON.stringify(value); + } + console.log(`${key}: ${value}`); + } +} + +export default function({ + definitions, + help, + usage, + start +}) { + program.loadDefinitions(definitions); + if (usage) { + program.usage(usage); + } + if (help) { + program.on('--help', help); + } + program.parse(process.argv, process.env); + + let options = program.getOptions(); + start(program, options, function() { + logStartupOptions(options); + }); +} \ No newline at end of file diff --git a/src/logger.js b/src/logger.js index 7c82574333..bc4d49bcf2 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,5 +1,18 @@ 'use strict'; -let logger; +import defaults from './defaults'; +import { WinstonLoggerAdapter } from './Adapters/Logger/WinstonLoggerAdapter'; +import { LoggerController } from './Controllers/LoggerController'; + +function defaultLogger() { + let adapter = new WinstonLoggerAdapter({ + logsFolder: defaults.logsFolder, + jsonLogs: defaults.jsonLogs, + verbose: defaults.verbose, + silent: defaults.silent }); + return new LoggerController(adapter); +} + +let logger = defaultLogger(); export function setLogger(aLogger) { logger = aLogger; From cd282477a6b6bd18871467514f3eef08977f0421 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Thu, 22 Sep 2016 16:56:13 -0400 Subject: [PATCH 2/2] Don't crash when the message is badly formatted --- src/LiveQuery/ParseLiveQueryServer.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 6f737f6d58..d3594ba731 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -62,7 +62,13 @@ class ParseLiveQueryServer { // to the subscribers and the handler will be called. this.subscriber.on('message', (channel, messageStr) => { logger.verbose('Subscribe messsage %j', messageStr); - let message = JSON.parse(messageStr); + let message; + try { + message = JSON.parse(messageStr); + } catch(e) { + logger.error('unable to parse message', messageStr, e); + return; + } this._inflateParseObject(message); if (channel === 'afterSave') { this._onAfterSave(message); @@ -229,7 +235,12 @@ class ParseLiveQueryServer { _onConnect(parseWebsocket: any): void { parseWebsocket.on('message', (request) => { if (typeof request === 'string') { - request = JSON.parse(request); + try { + request = JSON.parse(request); + } catch(e) { + logger.error('unable to parse request', request, e); + return; + } } logger.verbose('Request: %j', request);