Using classes for the new files and structure in the html5 server

This commit is contained in:
Leonardo Crauss Daronco 2014-04-23 16:18:25 -04:00
parent 0b40c1a10d
commit 068204a78f
6 changed files with 302 additions and 321 deletions

View File

@ -1,27 +1,21 @@
# Module dependencies
express = require("express")
RedisStore = require("connect-redis")(express)
redis = require("redis")
express = require("express")
redis = require("redis")
RedisStore = require("connect-redis")(express)
config = require("./config")
Logger = require("./lib/logger")
MainRouter = require("./routes/main_router")
Modules = require("./lib/modules")
RedisAction = require("./lib/redis_action")
RedisPublisher = require("./lib/redis_publisher")
RedisWebsocketBridge = require("./lib/redis_websocket_bridge")
Utils = require("./lib/utils")
config = require("./config")
ClientProxy = require("./lib/clientproxy")
Controller = require("./lib/controller")
Logger = require("./lib/logger")
MainRouter = require("./routes/main_router")
MessageBus = require("./lib/messagebus")
Modules = require("./lib/modules")
RedisPubSub = require("./lib/redispubsub")
Utils = require("./lib/utils")
# Module to store the modules registered in the application
config.modules = modules = new Modules()
config.modules.register "RedisAction", new RedisAction()
config.modules.register "RedisPublisher", new RedisPublisher()
# @todo This is only as a module because this app still changes data on redis, but it shouldn't.
# When this is fixed, redisStore can probably become an internal variable in RedisAction.
config.modules.register "RedisStore", redis.createClient()
# The application, exported in this module
app = config.modules.register "App", express.createServer()
module.exports = app
@ -60,31 +54,19 @@ app.configure "production", ->
app.helpers
h_environment: app.settings.env
# # Socket.IO
# io = require("socket.io")
# # reduce logging
# io.set('log level', 1)
# Router
config.modules.register "MainRouter", new MainRouter()
# Socket.IO
io = require("socket.io").listen(app)
io.configure ->
# Application modules
config.modules.register "RedisPubSub", new RedisPubSub()
config.modules.register "MessageBus", new MessageBus()
config.modules.register "Controller", new Controller()
# Authorize a session before it given access to connect to SocketIO
io.set "authorization", (handshakeData, callback) ->
redisAction = config.modules.get("RedisAction")
sessionID = Utils.getCookieVar(handshakeData.headers.cookie, "sessionid")
meetingID = Utils.getCookieVar(handshakeData.headers.cookie, "meetingid")
redisAction.isValidSession meetingID, sessionID, (err, isValid) ->
unless isValid
Logger.error "Invalid sessionID/meetingID"
callback(null, false) # failed authorization
else
redisAction.getUserProperties meetingID, sessionID, (err, properties) ->
handshakeData.sessionID = sessionID
handshakeData.username = properties.username
handshakeData.meetingID = properties.meetingID
callback(null, true) # good authorization
# Bridge used to interact between redis and socket clients
config.modules.register "RedisWebsocketBridge", new RedisWebsocketBridge(io)
# reduce logging
io.set('log level', 1);
clientProxy = new ClientProxy()
config.modules.register "ClientProxy", clientProxy
clientProxy.listen(app)

View File

@ -1,69 +1,59 @@
socketio = require('socket.io')
controller = require './controller'
config = require '../config'
log = require './bbblogger'
MESSAGE = "message"
io = null
moduleDeps = ["Controller"]
exports.listen = (app) ->
io = socketio.listen(app)
io.sockets.on('connection', (socket) ->
console.log("Client has connected.")
socket.on('message', (jsonMsg) ->
log.debug("Received message #{jsonMsg}")
handleMessage(socket, jsonMsg)
)
socket.on('disconnect', () ->
handleClientDisconnected socket
)
)
module.exports = class ClientProxy
handleClientDisconnected = (socket) ->
if (socket.userId?)
log.info("User [#{socket.userId}] has disconnected.")
constructor: ->
config.modules.wait moduleDeps, =>
@controller = config.modules.get("Controller")
handleMessage = (socket, message) ->
if message.header.name?
handleValidMessage(socket, message)
else
log.error({message: message}, "Invalid message.")
listen: (app) ->
io = socketio.listen(app)
io.set('log level', 1)
io.sockets.on 'connection', (socket) =>
log.debug({client: socket}, "Client has connected.")
socket.on 'message', (jsonMsg) =>
log.debug("Received message #{jsonMsg}")
@_handleMessage(socket, jsonMsg)
socket.on 'disconnect', =>
@_handleClientDisconnected socket
handleValidMessage = (socket, message) ->
switch message.header.name
when 'authenticateMessage'
handleLoginMessage socket, message
_handleClientDisconnected: (socket) ->
if socket.userId?
log.info("User [#{socket.userId}] has disconnected.")
_handleMessage: (socket, message) ->
if message.header?.name?
@_handleValidMessage(socket, message)
else
log.error({message: message}, 'Unknown message name.')
log.error({message: message}, "Invalid message.")
handleLoginMessage = (socket, data) ->
controller.processLoginMessage(data, (err, result) ->
if (err)
message = {name: "authenticationReply", error: err}
sendMessageToClient socket, message
# Disconnect this socket as it failed authentication.
socket.disconnect()
else
# Assign the userId to this socket. This way we can
# locate this socket using the userId.
socket.userId = result.userId
message = {name: "authenticationReply", data: result}
sendMessageToClient socket, message
)
_handleValidMessage: (socket, message) ->
switch message.header.name
when 'authenticateMessage'
@_handleLoginMessage socket, message
else
log.error({message: message}, 'Unknown message name.')
_handleLoginMessage: (socket, data) ->
@controller.processLoginMessage data, (err, result) ->
if err?
message = {name: "authenticationReply", error: err}
sendMessageToClient socket, message
# Disconnect this socket as it failed authentication.
socket.disconnect()
else
# Assign the userId to this socket. This way we can
# locate this socket using the userId.
socket.userId = result.userId
message = {name: "authenticationReply", data: result}
sendMessageToClient socket, message
sendMessageToClient = (socket, message) ->
socket.emit(MESSAGE, JSON.stringify(message))

View File

@ -1,23 +1,33 @@
bus = require './messagebus'
log = require './bbblogger'
messageReceiver = null
config = require '../config'
exports.registerMessageReceiver = (callback) ->
messageReceiver = callback
moduleDeps = ["MessageBus"]
exports.processLoginMessage = (data, callback) ->
bus.sendMessage(data, (err, result) ->
if (err)
errLog = {reason: err, data: data}
log.error({error: errLog}, 'Authentication Failure')
callback(err, null)
else
log.info("SUCCESS: #{result}")
if result.error?
log.info({error: result.error}, 'Authentication Failure')
callback(result.error, null)
module.exports = class Controller
constructor: ->
config.modules.wait moduleDeps, =>
@messageBus = config.modules.get("MessageBus")
# @clientProxy = config.modules.get("ClientProxy")
# registerMessageReceiver: (callback) ->
# messageReceiver = callback
processLoginMessage: (data, callback) ->
@messageBus.sendMessage data, (err, result) ->
if err?
errLog = {reason: err, data: data}
log.error({error: errLog}, 'Authentication Failure')
callback(err, null)
else
log.info({response: result.data}, 'Authentication Success')
callback(null, result.data)
)
log.info("SUCCESS: #{result}")
if result.error?
log.info({error: result.error}, 'Authentication Failure')
callback(result.error, null)
else
log.info({response: result.data}, 'Authentication Success')
callback(null, result.data)
# processEndMessage: (data, callback) ->
# @clientProxy.endMeeting()

View File

@ -1,32 +1,37 @@
postal = require('postal')
redisrpc = require './redispubsub'
crypto = require 'crypto'
exports.receiveMessage = (callback) ->
postal.subscribe({
channel: "receiveChannel"
topic: "broadcast",
callback: (msg, envelope) ->
callback( msg )
})
config = require '../config'
moduleDeps = ["RedisPubSub"]
exports.sendMessage = (data, callback) ->
replyTo = {
channel: 'model.data',
topic: 'get.' + crypto.randomBytes(16).toString('hex')
};
module.exports = class MessageBus
postal.subscribe({
channel: replyTo.channel,
topic: replyTo.topic,
callback: (msg, envelope) ->
callback( msg.err, msg.data )
}).once()
constructor: ->
config.modules.wait moduleDeps, =>
@pubSub = config.modules.get("RedisPubSub")
postal.publish({
channel: 'publishChannel',
topic: 'broadcast',
replyTo: replyTo,
data: data
})
receiveMessages: (callback) ->
postal.subscribe
channel: "receiveChannel"
topic: "broadcast"
callback: (msg, envelope) ->
callback( msg )
sendAndWaitForReply: (data, callback) ->
replyTo =
channel: 'replyChannel'
topic: 'get.' + crypto.randomBytes(16).toString('hex')
postal.subscribe(
channel: replyTo.channel
topic: replyTo.topic
callback: (msg, envelope) ->
callback( msg.err, msg.data )
).once()
postal.publish
channel: 'publishChannel'
topic: 'broadcast'
replyTo: replyTo
data: data

View File

@ -2,96 +2,91 @@ redis = require 'redis'
crypto = require 'crypto'
postal = require 'postal'
log = require './bbblogger'
# default timeout to wait for response
TIMEOUT = 5000;
TIMEOUT = 5000
pubClient = redis.createClient()
subClient = redis.createClient()
module.exports = class RedisPubSub
# hash to store requests waiting for response
pendingRequests = {};
constructor: ->
@pubClient = redis.createClient()
@subClient = redis.createClient()
initialize = () ->
postal.subscribe({
channel: 'publishChannel',
topic: 'broadcast',
callback: ( msg, envelope ) ->
if (envelope.replyTo?)
sendAndWaitForReply(msg, envelope)
# hash to store requests waiting for response
@pendingRequests = {}
postal.subscribe
channel: 'publishChannel'
topic: 'broadcast'
callback: (msg, envelope) ->
if envelope.replyTo?
sendAndWaitForReply(msg, envelope)
else
sendMessage(msg, envelope)
@subClient.on "subscribe", (channel, count) ->
log.info("Subscribed to #{channel}")
@subClient.on "message", (channel, jsonMsg) ->
log.debug("Received message on [channel] = #{channel} [message] = #{jsonMsg}")
message = JSON.parse(jsonMsg)
if message.header?.correlationId?
correlationId = message.header.correlationId
# retrieve the request entry
entry = @pendingRequests[correlationId]
# make sure we don't timeout by clearing it
clearTimeout(entry.timeout)
# delete the entry from hash
delete @pendingRequests[correlationId]
response = {}
response.data = message.payload
postal.publish
channel: entry.replyTo.channel
topic: entry.replyTo.topic
data: response
else
sendMessage(msg, envelope)
})
sendToController message
sendAndWaitForReply = (message, envelope) ->
# generate a unique correlation id for this call
correlationId = crypto.randomBytes(16).toString('hex');
# create a timeout for what should happen if we don't get a response
timeoutId = setTimeout( (correlationId) ->
response = {}
#if this ever gets called we didn't get a response in a
#timely fashion
error = {code: "503", message: "Waiting for reply timeout.", description: "Waiting for reply timeout."}
response.err = error
postal.publish({
channel: envelope.replyTo.channel,
topic: envelope.replyTo.topic,
data: response
})
# delete the entry from hash
delete pendingRequests[correlationId];
, TIMEOUT, correlationId)
log.info("RPC: Subscribing message on channel [responseChannel]")
@subClient.subscribe("responseChannel")
# create a request entry to store in a hash
entry = {
replyTo: envelope.replyTo,
timeout: timeoutId #the id for the timeout so we can clear it
};
# put the entry in the hash so we can match the response later
pendingRequests[correlationId] = entry;
console.log("Publishing #{message}")
sendAndWaitForReply: (message, envelope) ->
# generate a unique correlation id for this call
correlationId = crypto.randomBytes(16).toString('hex')
message.header.correlationId = correlationId
pubClient.publish("bigbluebuttonAppChannel", JSON.stringify(message))
# create a timeout for what should happen if we don't get a response
timeoutId = setTimeout( (correlationId) =>
response = {}
# if this ever gets called we didn't get a response in a timely fashion
response.err =
code: "503"
message: "Waiting for reply timeout."
description: "Waiting for reply timeout."
postal.publish
channel: envelope.replyTo.channel
topic: envelope.replyTo.topic
data: response
# delete the entry from hash
delete @pendingRequests[correlationId]
, TIMEOUT, correlationId)
subClient.on("subscribe", (channel, count) ->
console.log("Subscribed to #{channel}")
)
# create a request entry to store in a hash
entry =
replyTo: envelope.replyTo
timeout: timeoutId #the id for the timeout so we can clear it
subClient.on("message", (channel, jsonMsg) ->
# put the entry in the hash so we can match the response later
@pendingRequests[correlationId] = entry
console.log("Publishing #{message}")
console.log("Received message on [channel] = #{channel} [message] = #{jsonMsg}")
message = JSON.parse(jsonMsg)
message.header.correlationId = correlationId
if (message.header.correlationId?)
correlationId = message.header.correlationId
#retreive the request entry
entry = pendingRequests[correlationId];
#make sure we don't timeout by clearing it
clearTimeout(entry.timeout);
#delete the entry from hash
delete pendingRequests[correlationId];
response = {}
response.data = message.payload
postal.publish({
channel: entry.replyTo.channel,
topic: entry.replyTo.topic,
data: response
})
else
sendToController message
)
@pubClient.publish("bigbluebuttonAppChannel", JSON.stringify(message))
sendToController = (message) ->
postal.publish({
postal.publish
channel: "receiveChannel"
topic: "broadcast",
topic: "broadcast"
data: message
})
initialize()
console.log("RPC: Subscribing message on channel [responseChannel]")
subClient.subscribe("responseChannel")

View File

@ -3,9 +3,8 @@ sanitizer = require("sanitizer")
util = require("util")
config = require("../config")
RedisKeys = require("../lib/redis_keys")
moduleDeps = ["App", "RedisAction", "RedisStore"]
moduleDeps = ["App"]
# The main router that registers the routes that can be accessed by the client.
module.exports = class MainRouter
@ -13,16 +12,16 @@ module.exports = class MainRouter
constructor: () ->
config.modules.wait moduleDeps, =>
@app = config.modules.get("App")
@redisAction = config.modules.get("RedisAction")
@redisStore = config.modules.get("RedisStore")
# @redisAction = config.modules.get("RedisAction")
# @redisStore = config.modules.get("RedisStore")
@_registerRoutes()
_registerRoutes: () ->
@app.get "/", @_index
@app.get "/auth", @_getAuth
@app.post "/auth", @_postAuth
@app.post "/logout", @_requiresLogin, @_logout
@app.get "/meetings", @_meetings
# @app.get "/auth", @_getAuth
# @app.post "/auth", @_postAuth
# @app.post "/logout", @_requiresLogin, @_logout
# @app.get "/meetings", @_meetings
# When requesting the homepage a potential meetingID and sessionID are extracted
# from the user's cookie. If they match with a user that is in the database under
@ -34,108 +33,108 @@ module.exports = class MainRouter
#
# @internal
_index: (req, res) =>
@redisAction.getMeetings (err, meetings) ->
res.render "index",
title: config.appName
meetings: meetings
# @redisAction.getMeetings (err, meetings) ->
res.render "index",
title: config.appName
meetings: []
# Upon submitting their login details from the index page via a POST request, a meeting
# will be created and joined. If an error occurs, which usually results in using an
# invalid username or meetingID, the user receives an error response. Both success and
# error responses are in json only.
#
# This method is registered as a route on express.
#
# @internal
_postAuth: (req, res) =>
user = req.body
username = user.username = sanitizer.escape(user.username)
meetingID = user.meetingID = sanitizer.escape(user.meetingID)
sessionID = req.sessionID
# # Upon submitting their login details from the index page via a POST request, a meeting
# # will be created and joined. If an error occurs, which usually results in using an
# # invalid username or meetingID, the user receives an error response. Both success and
# # error responses are in json only.
# #
# # This method is registered as a route on express.
# #
# # @internal
# _postAuth: (req, res) =>
# user = req.body
# username = user.username = sanitizer.escape(user.username)
# meetingID = user.meetingID = sanitizer.escape(user.meetingID)
# sessionID = req.sessionID
validParameters = @_validateLoginParameters username, meetingID
# validParameters = @_validateLoginParameters username, meetingID
if validParameters
@redisAction.makeMeeting meetingID, sessionID, username, (result) ->
user.loginAccepted = result
# save the ids so socketio can get the username and meeting
if result
res.cookie "sessionid", sessionID
res.cookie "meetingid", meetingID
res.contentType "json"
res.send(user)
else
user.loginAccepted = false
res.send(user)
# if validParameters
# @redisAction.makeMeeting meetingID, sessionID, username, (result) ->
# user.loginAccepted = result
# # save the ids so socketio can get the username and meeting
# if result
# res.cookie "sessionid", sessionID
# res.cookie "meetingid", meetingID
# res.contentType "json"
# res.send(user)
# else
# user.loginAccepted = false
# res.send(user)
# Returns a json informing if there's an authenticated user or not. The meetingID and
# sessionID are extracted from the user's cookie. If they match with a user that is
# in the database, the user is accepted and his information is included in the response.
# If they don't match, the user is not accepted.
#
# This method is registered as a route on express.
#
# @internal
_getAuth: (req, res) =>
@redisAction.isValidSession req.cookies["meetingid"], req.cookies["sessionid"], (err, valid) ->
res.contentType "json"
user = {}
unless valid
user.loginAccepted = false
res.send user
else
user.loginAccepted = true
user.meetingID = req.cookies.meetingid
# user.username = ?? // TODO
res.send user
# # Returns a json informing if there's an authenticated user or not. The meetingID and
# # sessionID are extracted from the user's cookie. If they match with a user that is
# # in the database, the user is accepted and his information is included in the response.
# # If they don't match, the user is not accepted.
# #
# # This method is registered as a route on express.
# #
# # @internal
# _getAuth: (req, res) =>
# @redisAction.isValidSession req.cookies["meetingid"], req.cookies["sessionid"], (err, valid) ->
# res.contentType "json"
# user = {}
# unless valid
# user.loginAccepted = false
# res.send user
# else
# user.loginAccepted = true
# user.meetingID = req.cookies.meetingid
# # user.username = ?? // TODO
# res.send user
# When a user logs out, their session is destroyed and their cookies are cleared.
# @param {Object} req Request object from the client
# @param {Object} res Response object to the client
#
# This method is registered as a route on express.
#
# @internal
_logout: (req, res) =>
req.session.destroy() # end the session
res.cookie "sessionid", null # clear the cookie from the client
res.cookie "meetingid", null
res.redirect "/"
# # When a user logs out, their session is destroyed and their cookies are cleared.
# # @param {Object} req Request object from the client
# # @param {Object} res Response object to the client
# #
# # This method is registered as a route on express.
# #
# # @internal
# _logout: (req, res) =>
# req.session.destroy() # end the session
# res.cookie "sessionid", null # clear the cookie from the client
# res.cookie "meetingid", null
# res.redirect "/"
# @param {Object} req Request object from the client
# @param {Object} res Response object to the client
#
# This method is registered as a route on express.
#
# @internal
_meetings: (req, res) =>
@redisAction.getMeetings (err, results) ->
res.contentType "json"
res.send JSON.stringify(results)
# # @param {Object} req Request object from the client
# # @param {Object} res Response object to the client
# #
# # This method is registered as a route on express.
# #
# # @internal
# _meetings: (req, res) =>
# @redisAction.getMeetings (err, results) ->
# res.contentType "json"
# res.send JSON.stringify(results)
# If a page requires authentication to view, this function is used to verify that there
# is a user logged in.
# @param {Object} req Request object from client
# @param {Object} res Response object to client
# @param {Function} next To be run as a callback if valid
#
# This method is registered as a route on express.
#
# @internal
_requiresLogin: (req, res, next) =>
# check that they have a cookie with valid session id
@redisAction.isValidSession req.cookies["meetingid"], req.cookies["sessionid"], (err, isValid) ->
if isValid
next()
else
res.redirect "/"
# # If a page requires authentication to view, this function is used to verify that there
# # is a user logged in.
# # @param {Object} req Request object from client
# # @param {Object} res Response object to client
# # @param {Function} next To be run as a callback if valid
# #
# # This method is registered as a route on express.
# #
# # @internal
# _requiresLogin: (req, res, next) =>
# # check that they have a cookie with valid session id
# @redisAction.isValidSession req.cookies["meetingid"], req.cookies["sessionid"], (err, isValid) ->
# if isValid
# next()
# else
# res.redirect "/"
# Checks whether the parameters passed by the user to login are correct
# @param username [string] the username passed by the user
# @param meetingID [string] the meetingID passed by the user
# @return [boolean] whether the parameters are correct or not
# @internal
_validateLoginParameters: (username, meetingID) ->
username? and meetingID? and
username.length <= config.maxUsernameLength and
meetingID.split(" ").length is 1
# # Checks whether the parameters passed by the user to login are correct
# # @param username [string] the username passed by the user
# # @param meetingID [string] the meetingID passed by the user
# # @return [boolean] whether the parameters are correct or not
# # @internal
# _validateLoginParameters: (username, meetingID) ->
# username? and meetingID? and
# username.length <= config.maxUsernameLength and
# meetingID.split(" ").length is 1