trabajo-afectivo/script/websocket-server.rb

322 lines
9.5 KiB
Ruby
Raw Normal View History

2013-03-10 23:14:31 +00:00
#!/usr/bin/env ruby
2013-06-13 07:01:06 +00:00
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
2013-03-10 23:14:31 +00:00
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'
2013-03-10 23:14:31 +00:00
require 'daemons'
2012-07-23 22:22:23 +00:00
# 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,
2013-03-10 23:14:31 +00:00
:v => false,
:d => false,
2012-08-02 09:17:22 +00:00
:k => '/path/to/server.key',
:c => '/path/to/server.crt',
2013-01-15 22:33:51 +00:00
:i => Dir.pwd.to_s + '/tmp/pids/websocket.pid'
2012-07-23 22:22:23 +00:00
}
2013-03-10 23:14:31 +00:00
if ARGV[0] != 'start' && ARGV[0] != 'stop'
puts "Usage: websocket-server.rb start|stop [options]"
exit;
end
2012-08-02 09:30:30 +00:00
tls_options = {}
2012-07-23 22:22:23 +00:00
OptionParser.new do |opts|
2013-03-10 23:14:31 +00:00
opts.banner = "Usage: websocket-server.rb start|stop [options]"
2012-07-23 22:22:23 +00:00
2013-03-10 23:14:31 +00:00
opts.on("-d", "--daemon", "start as daemon") do |d|
@options[:d] = d
end
2013-03-10 23:14:31 +00:00
opts.on("-v", "--verbose", "enable debug messages") do |d|
@options[:v] = 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
2013-01-15 22:46:35 +00:00
opts.on("-i", "--pid [OPT]", "pid, default is tmp/pids/websocket.pid") do |i|
@options[:i] = i
end
2012-08-02 09:17:22 +00:00
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!
2013-01-15 22:46:35 +00:00
puts "Starting websocket server on #{ @options[:b] }:#{ @options[:p] } (secure:#{ @options[:s].to_s },pid:#{@options[:i].to_s})"
2012-08-02 09:17:22 +00:00
#puts options.inspect
2012-07-23 22:22:23 +00:00
2013-03-10 23:14:31 +00:00
if ARGV[0] == 'stop'
# read pid
pid =File.open( @options[:i].to_s ).read
pid.gsub!(/\r|\n/, "")
# kill
Process.kill( 9, pid.to_i )
exit
end
if ARGV[0] == 'start' && @options[:d]
Daemons.daemonize
# create pid file
$daemon_pid = File.new( @options[:i].to_s,"w" )
$daemon_pid.sync = true
$daemon_pid.puts(Process.pid.to_s)
$daemon_pid.close
end
2013-01-15 22:33:51 +00:00
2012-07-23 22:22:23 +00:00
@clients = {}
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
# 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
2013-05-10 09:33:17 +00:00
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']
Session.spool_create(msg)
2012-11-02 16:10:22 +00:00
end
# get spool messages and send them to new client connection
2012-11-02 16:10:22 +00:00
if data['action'] == 'spool'
log 'notice', "request spool data", client_id
2012-11-04 10:24:03 +00:00
spool = Session.spool_list( data['timestamp'], @clients[client_id][:session]['id'] )
spool.each { |item|
# create new msg to push to client
msg = JSON.generate( item[:message] )
2012-11-04 10:24:03 +00:00
if item[:type] == 'direct'
log 'notice', "send spool to (user_id=#{ @clients[client_id][:session]['id'] })", client_id
@clients[client_id][:websocket].send( "[#{ msg }]" )
else
log 'notice', "send spool", client_id
@clients[client_id][:websocket].send( "[#{ msg }]" )
2012-11-02 16:10:22 +00:00
end
}
# send spool:sent event to client
log 'notice', "send spool:sent event", client_id
2013-06-17 13:08:51 +00:00
@clients[client_id][:websocket].send( '[{"event":"spool:sent","data":{"timestamp":' + Time.now.to_i.to_s + '}}]' )
2012-11-02 16:10:22 +00:00
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
2013-06-13 07:01:06 +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
2013-06-13 07:01:06 +00:00
# broadcast
2012-10-28 23:47:38 +00:00
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['data']['recipient']
if data['data']['recipient'].class != Hash
log 'error', "recipient attribute isn't a hash '#{ data['data']['recipient'].inspect }'"
2013-05-10 09:33:17 +00:00
else
if !data['data']['recipient'].has_key?('user_id')
log 'error', "need recipient.user_id attribute '#{ data['data']['recipient'].inspect }'"
2013-05-10 09:33:17 +00:00
else
if data['data']['recipient']['user_id'].class != Array
log 'error', "recipient.user_id attribute isn't an array '#{ data['data']['recipient']['user_id'].inspect }'"
else
data['data']['recipient']['user_id'].each { |user_id|
2013-05-10 09:33:17 +00:00
if local_client[:user][:id].to_i == user_id.to_i
2013-06-11 06:18:18 +00:00
log 'notice', "send broadcast from (#{client_id.to_s}) to (user_id=#{user_id})", local_client_id
2013-05-10 09:33:17 +00:00
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
end
}
end
2012-11-04 10:24:03 +00:00
end
2013-05-10 09:33:17 +00:00
end
2013-06-13 07:01:06 +00:00
# broadcast every client
2012-11-04 10:24:03 +00:00
else
2013-06-11 06:18:18 +00:00
log 'notice', "send broadcast from (#{client_id.to_s})", 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
2013-06-11 06:18:18 +00:00
else
log 'notice', "do not send broadcast to it self", client_id
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 unused connections
EventMachine.add_timer(0.5) {
check_unused_connections
}
# 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]
2013-06-13 07:01:06 +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 = '-' )
2013-03-10 23:14:31 +00:00
if !@options[:v]
return if level == 'debug'
end
2012-08-03 22:46:05 +00:00
puts "#{Time.now}:client(#{ client_id }) #{ data }"
2013-06-13 07:01:06 +00:00
# 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
}