socky-server 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ module Socky
2
+ class Connection
3
+ # finders module - extends Socky::Connection
4
+ module Finders
5
+
6
+ # Return list of all connections
7
+ def find_all
8
+ Socky::Connection.connections
9
+ end
10
+
11
+ # Return filtered list of connections
12
+ # @param [Hash] opts the options for filters.
13
+ # @option opts [Hash] :to ({}) return only listed clients/channels. keys supported: clients, channels
14
+ # @option opts [Hash] :except ({}) return all clients/channels except listed. keys supported: clients, channels
15
+ # @return [Array] list of connections
16
+ # @example return all connections
17
+ # Socky::Connection.find
18
+ # @example return no connections
19
+ # # empty array as param means "no channels"
20
+ # # nil is handles as "ignore param" so all clients/channels will be executed
21
+ # Socky::Connection.find(:to => { :clients => [] })
22
+ # Socky::Connection.find(:to => { :channels => [] })
23
+ # @example return connections of users "first" and "second" from channels "some_channel"
24
+ # Socky::Connection.find(:to => { :clients => ["first","second"], :channels => "some_channel" })
25
+ # @example return all connections from channel "some_channel" except of ones belonging to "first"
26
+ # Socky::Connection.find(:to => { :channels => "some_channel" }, :except => { :clients => "first" })
27
+ def find(opts = {})
28
+ to = symbolize_keys(opts[:to]) || {}
29
+ exclude = symbolize_keys(opts[:except]) || {}
30
+
31
+ connections = find_all
32
+ connections = filter_by_clients(connections, to[:clients], exclude[:clients])
33
+ connections = filter_by_channels(connections, to[:channels], exclude[:channels])
34
+
35
+ connections
36
+ end
37
+
38
+ private
39
+
40
+ def filter_by_clients(connections, included_clients = nil, excluded_clients = nil)
41
+ # Empty table means "no users" - nil means "all users"
42
+ return [] if (included_clients.is_a?(Array) && included_clients.empty?)
43
+
44
+ included_clients = Array(included_clients)
45
+ excluded_clients = Array(excluded_clients)
46
+
47
+ connections.find_all do |connection|
48
+ connection if (included_clients.empty? || included_clients.include?(connection.client)) && !excluded_clients.include?(connection.client)
49
+ end
50
+ end
51
+
52
+ def filter_by_channels(connections, included_channels = nil, excluded_channels = nil)
53
+ # Empty table means "no channels" - nil means "all channels"
54
+ return [] if (included_channels.is_a?(Array) && included_channels.empty?)
55
+
56
+ included_channels = Array(included_channels)
57
+ excluded_channels = Array(excluded_channels)
58
+
59
+ connections.find_all do |connection|
60
+ connection if connection.channels.any? do |channel|
61
+ (included_channels.empty? || included_channels.include?(channel) ) && !excluded_channels.include?(channel)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+
3
+ module Socky
4
+ # every message from admin is stored as instance of Message
5
+ # and then processed by #process method
6
+ class Message
7
+ include Socky::Misc
8
+
9
+ class InvalidJSON < Socky::SockyError; end #:nodoc:
10
+ class UnauthorisedQuery < Socky::SockyError; end #:nodoc:
11
+ class InvalidQuery < Socky::SockyError; end #:nodoc:
12
+
13
+ # message params like command type or message content
14
+ attr_reader :params
15
+ # message sender(admin) required when some data are returned
16
+ attr_reader :creator
17
+
18
+ class << self
19
+ # create new message and process it
20
+ # @see #process
21
+ # @param [Connection] connection creator of message
22
+ # @param [String] message message content
23
+ def process(connection, message)
24
+ message = new(connection, message)
25
+ message.process
26
+ rescue SockyError => error
27
+ error connection.name, error
28
+ connection.send_message(error.message)
29
+ end
30
+ end
31
+
32
+ # initialize new message
33
+ # @param [Connection] creator creator of message
34
+ # @param [String] message valid json containing hash of params
35
+ # @raise [InvalidJSON] if message is invalid json or don't evaluate to hash
36
+ def initialize(creator, message)
37
+ @params = symbolize_keys(JSON.parse(message)) rescue raise(InvalidJSON, "invalid request")
38
+ @creator = creator
39
+ end
40
+
41
+ # process message - check command('broadcast' or 'query')
42
+ # and send message to correct connections
43
+ # 'broadcast' command require 'body' of message and allows 'to' and 'except' hashes for filters
44
+ # 'query' command require 'type' of query - currently only 'show_connections' is supported
45
+ # @see Socky::Connection::Finders.find filtering options
46
+ # @raise [InvalidQuery, 'unknown command'] when 'command' param is invalid
47
+ # @raise [InvalidQuery, 'unknown query type'] when 'command' is 'queru' but no 'type' is provided
48
+ def process
49
+ debug [self.name, "processing", params.inspect]
50
+
51
+ case params.delete(:command).to_s
52
+ when "broadcast" then broadcast
53
+ when "query" then query
54
+ else raise(InvalidQuery, "unknown command")
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def broadcast
61
+ connections = Socky::Connection.find(params)
62
+ send_message(params[:body], connections)
63
+ end
64
+
65
+ def query
66
+ case params[:type].to_s
67
+ when "show_connections" then query_show_connections
68
+ else raise(InvalidQuery, "unknown query type")
69
+ end
70
+ end
71
+
72
+ def query_show_connections
73
+ respond Socky::Connection.find_all
74
+ end
75
+
76
+ def respond(message)
77
+ creator.send_message(message)
78
+ end
79
+
80
+ def send_message(message, connections)
81
+ connections.each{|connection| connection.send_message message}
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,74 @@
1
+ module Socky
2
+ # common methods for all other classes
3
+ module Misc
4
+
5
+ # extend including class by itself
6
+ def self.included(base)
7
+ base.extend Socky::Misc
8
+ end
9
+
10
+ # return server-wide options
11
+ # @see Socky.options
12
+ def options
13
+ Socky.options
14
+ end
15
+
16
+ # write server-wide options
17
+ def options=(ob)
18
+ Socky.options = ob
19
+ end
20
+
21
+ # return name of current object
22
+ # @example when included in connection
23
+ # @connection.name #=> "Connection(2149785820)"
24
+ def name
25
+ "#{self.class.to_s.split("::").last}(#{self.object_id})"
26
+ end
27
+
28
+ # return log path
29
+ def log_path
30
+ Socky.log_path
31
+ end
32
+
33
+ # return pid path
34
+ def pid_path
35
+ Socky.pid_path
36
+ end
37
+
38
+ # return config path
39
+ def config_path
40
+ Socky.config_path
41
+ end
42
+
43
+ # log message at info level
44
+ # @param [Array] args data for logging
45
+ def info(args)
46
+ Socky.logger.info args.join(" ")
47
+ end
48
+
49
+ # log message at debug level
50
+ # @param [Array] args data for logging
51
+ def debug(args)
52
+ Socky.logger.debug args.join(" ")
53
+ end
54
+
55
+ # log message at error level
56
+ # @param [String] name object name with raised error
57
+ # @param [Error] error error instance that was raised
58
+ def error(name, error)
59
+ debug [name, "raised:", error.class, error.message]
60
+ end
61
+
62
+ # convert keys of hash to symbol
63
+ # @param [Hash] hash hash to symbolize
64
+ # @return [Hash] with symbolized keys
65
+ # @return [Object] if hash isn't instance of Hash
66
+ def symbolize_keys(hash)
67
+ return hash unless hash.is_a?(Hash)
68
+ hash.inject({}) do |options, (key, value)|
69
+ options[(key.to_sym if key.respond_to?(:to_sym)) || key] = value
70
+ options
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,27 @@
1
+ require 'em-http'
2
+
3
+ module Socky
4
+ # this class provide unobtrusive http request methods
5
+ class NetRequest
6
+ include Socky::Misc
7
+
8
+ class << self
9
+
10
+ # send unobtrusive http POST request to gived address and return status of request as block response
11
+ # @param [String] url address to send request in format 'http://address[:port]/[path]'
12
+ # @param [Hash] params params for request(will be attached in post message)
13
+ # @yield [Boolean] called after request is finished - if response status is 200 then it's true, else false
14
+ def post(url, params = {}, &block)
15
+ http = EventMachine::HttpRequest.new(url).post :body => params, :timeout => options[:timeout] || 3
16
+ http.errback { yield false }
17
+ http.callback { yield http.response_header.status == 200 }
18
+ true
19
+ rescue => error
20
+ error "Bad request", error
21
+ false
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'socky/options/config'
2
+ require 'socky/options/parser'
3
+
4
+ module Socky
5
+ # options parser - reads options from STDIN and config file and set Socky.options
6
+ class Options
7
+ include Socky::Misc
8
+
9
+ class << self
10
+ # prepare server-wide options from config and parser
11
+ # @param [Array] argv arguments that will be provided to parser
12
+ # @see default_options default options
13
+ # @see Config.read merged with default options
14
+ # @see Parser.parse merges with default options after config
15
+ def prepare(argv)
16
+ self.options = default_options
17
+
18
+ parsed_options = Parser.parse(argv)
19
+ config_options = Config.read(parsed_options[:config_path] || config_path, :kill => parsed_options[:kill])
20
+
21
+ self.options.merge!(config_options)
22
+ self.options.merge!(parsed_options)
23
+ end
24
+
25
+ # default options for server
26
+ def default_options
27
+ {
28
+ :config_path => config_path,
29
+ :port => 8080,
30
+ :debug => false,
31
+ :deep_debug => false,
32
+ :secure => false,
33
+ :log_path => log_path
34
+ }
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ module Socky
5
+ class Options
6
+ # config parser class - used by Socky::Options
7
+ class Config
8
+
9
+ class NoConfigFile < Socky::SockyError; end #:nodoc:
10
+ class InvalidConfig < Socky::SockyError; end #:nodoc:
11
+ class AlreadyExists < Socky::SockyError; end #:nodoc:
12
+ class ConfigUnavailable < Socky::SockyError; end #:nodoc:
13
+ class SuccessfullyCreated < Socky::SockyError; end #:nodoc:
14
+
15
+ class << self
16
+ # read config file or exits if file don't exists or is invalid
17
+ # @param [String] path path to valid yaml file
18
+ # @param [Hash] args args to rescue eventual problems
19
+ # @option args [Any] kill (nil) if not nil then empty hash will be returned if config file isn't found
20
+ # @return [Hash] parsed config options
21
+ # @raise [NoConfigFile] if file doesn't exists
22
+ # @raise [InvalidConfig] if file isn't valid yaml
23
+ def read(path, args = {})
24
+ raise(NoConfigFile, "You must generate a config file (socky -g filename.yml)") unless File.exists?(path)
25
+ result = YAML::load(ERB.new(IO.read(path)).result)
26
+ raise(InvalidConfig, "Provided config file is invalid.") unless result.is_a?(Hash)
27
+ result
28
+ rescue SockyError => error
29
+ if args[:kill]
30
+ return {}
31
+ else
32
+ puts error.message
33
+ exit
34
+ end
35
+ end
36
+
37
+ # generate default config file
38
+ # @see DEFAULT_CONFIG_FILE
39
+ # @param [String] path path to file that will be created
40
+ # @raise [AlreadyExists] if file exists(you must delete it manually)
41
+ # @raise [ConfigUnavailable] if file cannot be created(wrong privilages?)
42
+ # @raise [SuccessfullyCreated] if file is successfully created
43
+ def generate(path)
44
+ raise(AlreadyExists, "Config file already exists. You must remove it before generating a new one.") if File.exists?(path)
45
+ File.open(path, 'w+') do |file|
46
+ file.write DEFAULT_CONFIG_FILE
47
+ end rescue raise(ConfigUnavailable, "Config file is unavailable - please choose another.")
48
+ raise(SuccessfullyCreated, "Config file generated at #{path}")
49
+ rescue SockyError => error
50
+ puts error.message
51
+ exit
52
+ end
53
+
54
+ # default config file content
55
+ DEFAULT_CONFIG_FILE= <<-EOF
56
+ :port: 8080
57
+ :debug: false
58
+
59
+ # :subscribe_url: http://localhost:3000/socky/subscribe
60
+ # :unsubscribe_url: http://localhost:3000/socky/unsubscribe
61
+
62
+ :secret: my_secret_key
63
+
64
+ :secure: false
65
+
66
+ # :timeout: 3
67
+
68
+ # :log_path: /var/log/socky.log
69
+ # :pid_path: /var/run/socky.pid
70
+
71
+ # :tls_options:
72
+ # :private_key_file: /private/key
73
+ # :cert_chain_file: /ssl/certificate
74
+ EOF
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,93 @@
1
+ require 'optparse'
2
+
3
+ module Socky
4
+ class Options
5
+ # STDIN options parser - used by Socky::Options
6
+ class Parser
7
+
8
+ class << self
9
+ # parse options(usually from STDIN)
10
+ # see source code for available options
11
+ # @param [Array] argv options for parser
12
+ # @return [Hash] parsed options from array
13
+ def parse(argv)
14
+ result = {}
15
+ opts = OptionParser.new do |opts|
16
+ opts.summary_width = 25
17
+ opts.banner = "Usage: socky [options]\n"
18
+
19
+ opts.separator ""
20
+ opts.separator "Configuration:"
21
+
22
+ opts.on("-g", "--generate FILE", String, "Generate config file") do |path|
23
+ result[:config_path] = File.expand_path(path) if path
24
+ Config.generate(result[:config_path])
25
+ end
26
+
27
+ opts.on("-c", "--config FILE", String, "Path to configuration file.", "(default: #{Socky.config_path})") do |path|
28
+ result[:config_path] = File.expand_path(path)
29
+ end
30
+
31
+ opts.separator ""; opts.separator "Network:"
32
+
33
+ opts.on("-p", "--port PORT", Integer, "Specify port", "(default: 8080)") do |port|
34
+ result[:port] = port
35
+ end
36
+
37
+ opts.on("-s", "--secure", "Run in wss/ssl mode") do
38
+ result[:secure] = true
39
+ end
40
+
41
+ opts.separator ""; opts.separator "Daemonization:"
42
+
43
+ opts.on("-d", "--daemon", "Daemonize mode") do
44
+ result[:daemonize] = true
45
+ end
46
+
47
+ opts.on("-P", "--pid FILE", String, "Path to PID file when using -d option") do |path|
48
+ result[:pid_path] = File.expand_path(path)
49
+ end
50
+
51
+ opts.on("-k", "--kill", "Kill daemon from specified pid file path") do
52
+ result[:kill] = true
53
+ end
54
+
55
+ opts.separator ""; opts.separator "Logging:"
56
+
57
+ opts.on("-l", "--log FILE", String, "Path to print debugging information.", "(Print to STDOUT if empty)") do |path|
58
+ result[:log_path] = File.expand_path(path)
59
+ end
60
+
61
+ opts.on("--debug", "Run in debug mode") do
62
+ result[:debug] = true
63
+ end
64
+
65
+ opts.on("--deep-debug", "Run in debug mode that is even more verbose") do
66
+ result[:debug] = true
67
+ result[:deep_debug] = true
68
+ end
69
+
70
+ opts.separator ""; opts.separator "Miscellaneous:"
71
+
72
+ opts.on_tail("-?", "--help", "Display this usage information.") do
73
+ puts "#{opts}\n"
74
+ exit
75
+ end
76
+
77
+ opts.on_tail("-v", "--version", "Display version") do
78
+ puts "Socky #{VERSION}"
79
+ exit
80
+ end
81
+ end
82
+ opts.parse!(argv)
83
+ result
84
+ rescue OptionParser::InvalidOption => error
85
+ puts "#{opts}\n"
86
+ puts error.message
87
+ exit
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end