trabajo-afectivo/script/websocket-server.rb

276 lines
7.9 KiB
Ruby
Raw Normal View History

2012-07-23 22:22:23 +00:00
$LOAD_PATH << './lib'
require 'rubygems'
require 'eventmachine'
require 'em-websocket'
require 'json'
require 'fileutils'
2012-11-22 14:40:13 +00:00
require 'session'
2012-07-23 22:22:23 +00:00
require 'optparse'
# Look for -o with argument, and -I and -D boolean arguments
@options = {
2012-07-23 22:22:23 +00:00
:p => 6042,
:b => '0.0.0.0',
2012-08-02 09:17:22 +00:00
:s => false,
:d => false,
2012-08-02 09:17:22 +00:00
:k => '/path/to/server.key',
:c => '/path/to/server.crt',
2012-07-23 22:22:23 +00:00
}
2012-08-02 09:30:30 +00:00
tls_options = {}
2012-07-23 22:22:23 +00:00
OptionParser.new do |opts|
opts.banner = "Usage: websocket-server.rb [options]"
opts.on("-d", "--debug", "enable debug messages") do |d|
@options[:d] = d
end
2012-07-23 22:22:23 +00:00
opts.on("-p", "--port [OPT]", "port of websocket server") do |p|
@options[:p] = p
2012-07-23 22:22:23 +00:00
end
opts.on("-b", "--bind [OPT]", "bind address") do |b|
@options[:b] = b
2012-07-23 22:22:23 +00:00
end
2012-08-02 09:17:22 +00:00
opts.on("-s", "--secure", "enable secure connections") do |s|
@options[:s] = s
2012-08-02 09:17:22 +00:00
end
opts.on("-k", "--private-key [OPT]", "/path/to/server.key for secure connections") do |k|
2012-08-02 09:30:30 +00:00
tls_options[:private_key_file] = k
2012-08-02 09:17:22 +00:00
end
opts.on("-c", "--certificate [OPT]", "/path/to/server.crt for secure connections") do |c|
2012-08-02 09:30:30 +00:00
tls_options[:cert_chain_file] = c
2012-08-02 09:17:22 +00:00
end
2012-07-23 22:22:23 +00:00
end.parse!
puts "Starting websocket server on #{ @options[:b] }:#{ @options[:p] } (secure:#{ @options[:s].to_s })"
2012-08-02 09:17:22 +00:00
#puts options.inspect
2012-07-23 22:22:23 +00:00
@clients = {}
2012-11-02 16:10:22 +00:00
@spool = []
2012-07-23 22:22:23 +00:00
EventMachine.run {
EventMachine::WebSocket.start( :host => @options[:b], :port => @options[:p], :secure => @options[:s], :tls_options => tls_options ) do |ws|
2012-07-23 22:22:23 +00:00
2012-12-24 13:53:50 +00:00
# check unused connections
check_unused_connections
2012-07-23 22:22:23 +00:00
# register client connection
ws.onopen {
client_id = ws.object_id
log 'notice', 'Client connected.', client_id
2012-07-23 22:22:23 +00:00
if !@clients.include? client_id
@clients[client_id] = {
2012-08-07 05:33:47 +00:00
:websocket => ws,
:last_ping => Time.new,
:error_count => 0,
}
2012-07-23 22:22:23 +00:00
end
}
# unregister client connection
ws.onclose {
client_id = ws.object_id
log 'notice', 'Client disconnected.', client_id
2012-08-04 13:35:55 +00:00
# removed from current client list
2012-07-23 22:22:23 +00:00
if @clients.include? client_id
@clients.delete client_id
end
2012-08-04 13:35:55 +00:00
2012-07-23 22:22:23 +00:00
Session.destory( client_id )
}
# manage messages
ws.onmessage { |msg|
client_id = ws.object_id
log 'debug', "received message: #{ msg } ", client_id
begin
data = JSON.parse(msg)
rescue => e
log 'error', "can't parse message: #{ msg }, #{ e.inspect}", client_id
next
end
2012-07-23 22:22:23 +00:00
2012-08-07 05:33:47 +00:00
# check if connection already exists
next if !@clients[client_id]
2012-11-02 16:10:22 +00:00
# spool messages for new connects
if data['spool']
meta = {
2012-11-04 10:24:03 +00:00
:msg => msg,
:msg_object => data,
:timestamp => Time.now.to_i,
2012-11-02 16:10:22 +00:00
}
@spool.push meta
end
# get spool messages
if data['action'] == 'spool'
@spool.each { |message|
2012-11-04 10:24:03 +00:00
# only send not already now messages
2012-11-02 16:10:22 +00:00
if !data['timestamp'] || data['timestamp'] < message[:timestamp]
2012-11-04 10:24:03 +00:00
# spool to recipient list
if message[:msg_object]['recipient'] && message[:msg_object]['recipient']['user_id']
message[:msg_object]['recipient']['user_id'].each { |user_id|
if @clients[client_id][:session]['id'] == user_id
log 'notice', "send spool to (user_id=#{user_id})", client_id
@clients[client_id][:websocket].send( "[#{ message[:msg] }]" )
end
}
# spool every client
else
log 'notice', "send spool", client_id
@clients[client_id][:websocket].send( "[#{ message[:msg] }]" )
end
2012-11-02 16:10:22 +00:00
end
}
end
2012-07-23 22:22:23 +00:00
# get session
if data['action'] == 'login'
@clients[client_id][:session] = data['session']
2012-11-26 05:04:44 +00:00
Session.create( client_id, data['session'], { :type => 'websocket' } )
2012-08-04 13:35:55 +00:00
# remember ping, send pong back
2012-08-04 13:35:55 +00:00
elsif data['action'] == 'ping'
@clients[client_id][:last_ping] = Time.now
@clients[client_id][:websocket].send( '[{"action":"pong"}]' )
2012-10-28 23:47:38 +00:00
# broadcast
elsif data['action'] == 'broadcast'
2012-11-04 10:24:03 +00:00
# list all current clients
client_list = Session.list
client_list.each {|local_client_id, local_client|
2012-12-14 12:16:04 +00:00
if local_client_id.to_s != client_id.to_s
2012-11-04 10:24:03 +00:00
# broadcast to recipient list
if data['recipient'] && data['recipient']['user_id']
data['recipient']['user_id'].each { |user_id|
if local_client[:user][:id] == user_id
2012-11-04 10:24:03 +00:00
log 'notice', "send broadcast to (user_id=#{user_id})", local_client_id
if local_client[:meta][:type] == 'websocket' && @clients[ local_client_id ]
@clients[ local_client_id ][:websocket].send( "[#{msg}]" )
else
Session.send( local_client_id, data )
end
2012-11-04 10:24:03 +00:00
end
}
2012-11-04 10:24:03 +00:00
# broadcast every client
else
log 'notice', "send broadcast", local_client_id
if local_client[:meta][:type] == 'websocket' && @clients[ local_client_id ]
@clients[ local_client_id ][:websocket].send( "[#{msg}]" )
else
Session.send( local_client_id, data )
end
2012-11-04 10:24:03 +00:00
end
2012-10-28 23:47:38 +00:00
end
}
2012-08-04 13:35:55 +00:00
end
2012-07-23 22:22:23 +00:00
}
end
# check open unused connections, kick all connection without activitie in the last 2 minutes
EventMachine.add_periodic_timer(120) {
2012-12-24 13:53:50 +00:00
check_unused_connections
}
EventMachine.add_periodic_timer(20) {
2012-11-26 05:04:44 +00:00
# websocket
log 'notice', "Status: websocket clients: #{ @clients.size }"
@clients.each { |client_id, client|
log 'notice', 'working...', client_id
}
2012-11-26 05:04:44 +00:00
# ajax
client_list = Session.list
clients = 0
client_list.each {|client_id, client|
next if client[:meta][:type] == 'websocket'
clients = clients + 1
}
log 'notice', "Status: ajax clients: #{ clients }"
client_list.each {|client_id, client|
next if client[:meta][:type] == 'websocket'
log 'notice', 'working...', client_id
}
}
2012-11-02 16:10:22 +00:00
EventMachine.add_periodic_timer(0.4) {
next if @clients.size == 0
2012-11-26 05:04:44 +00:00
log 'debug', "checking for data to send..."
2012-07-23 22:22:23 +00:00
@clients.each { |client_id, client|
2012-08-07 05:33:47 +00:00
next if client[:disconnect]
log 'debug', 'checking for data...', client_id
2012-07-23 22:22:23 +00:00
begin
queue = Session.queue( client_id )
if queue && queue[0]
2012-08-03 22:46:05 +00:00
# log "send " + queue.inspect, client_id
2012-11-04 10:24:03 +00:00
log 'notice', "send data to client", client_id
2012-07-23 22:22:23 +00:00
client[:websocket].send( queue.to_json )
end
2012-08-03 22:46:05 +00:00
rescue => e
log 'error', 'problem:' + e.inspect, client_id
# disconnect client
2012-08-07 05:33:47 +00:00
client[:error_count] += 1
if client[:error_count] > 100
if @clients.include? client_id
@clients.delete client_id
end
end
2012-07-23 22:22:23 +00:00
end
}
}
2012-11-02 16:10:22 +00:00
2012-12-24 13:53:50 +00:00
def check_unused_connections
log 'notice', "check unused idle connections..."
idle_time_in_min = 4
# web sockets
@clients.each { |client_id, client|
if ( client[:last_ping] + ( 60 * idle_time_in_min ) ) < Time.now
log 'notice', "closing idle websocket connection", client_id
# remember to not use this connection anymore
client[:disconnect] = true
# try to close regular
client[:websocket].close_websocket
# delete sesstion from client list
sleep 1
@clients.delete(client_id)
end
}
# ajax
clients = Session.list
clients.each { |client_id, client|
next if client[:meta][:type] == 'websocket'
if ( client[:meta][:last_ping].to_i + ( 60 * idle_time_in_min ) ) < Time.now.to_i
log 'notice', "closing idle ajax connection", client_id
Session.destory( client_id )
end
}
end
def log( level, data, client_id = '-' )
if !@options[:d]
return if level == 'debug'
end
2012-08-03 22:46:05 +00:00
puts "#{Time.now}:client(#{ client_id }) #{ data }"
# puts "#{Time.now}:#{ level }:client(#{ client_id }) #{ data }"
2012-08-03 22:46:05 +00:00
end
2012-07-23 22:22:23 +00:00
}