socky 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,99 +0,0 @@
1
- module Socky
2
- class Connection
3
- # authentication module - included in Socky::Connection
4
- module Authentication
5
- include Socky::Misc
6
-
7
- # check if user is valid and then send him authentication data and add to pool
8
- # if not then user is given failure response(so client javascript
9
- # will know that is should not reconnect again) and then is disconnected
10
- # admin user is automaticaly authenticated but isn't added to pool
11
- # he will be authenticated when he will try to send message
12
- # thanks to that admin don't need to wait for authentication confirmation
13
- # on every connection so it will fasten things for him
14
- def subscribe_request
15
- send_subscribe_request do |response|
16
- if response
17
- debug [self.name, "authentication successed"]
18
- add_to_pool
19
- EventMachine.add_timer(0.1) do
20
- send_authentication("success")
21
- end
22
- @authenticated_by_url = true
23
- else
24
- debug [self.name, "authentication failed"]
25
- EventMachine.add_timer(0.1) do
26
- send_authentication("failure")
27
- disconnect
28
- end
29
- end
30
- end unless admin || authenticated?
31
- end
32
-
33
- # if user is authenticated then he is removed from pool and
34
- # unsubscribe notification is sent to server unless he is admin
35
- # if user is not authenticated then nothing will happen
36
- def unsubscribe_request
37
- if authenticated?
38
- remove_from_pool
39
- send_unsubscribe_request{} unless admin
40
- end
41
- end
42
-
43
- # if user is admin then his secred is compared with server secred
44
- # in user isn't admin then it checks if user is authenticated by
45
- # server request(defaults to true if subscribe_url is nil)
46
- def authenticated?
47
- @authenticated ||= (admin ? authenticate_as_admin : authenticate_as_user)
48
- end
49
-
50
- private
51
-
52
- def send_authentication(msg)
53
- send_data({:type => :authentication, :body => msg})
54
- end
55
-
56
- def authenticate_as_admin
57
- options[:secret].nil? || secret == options[:secret]
58
- end
59
-
60
- def authenticate_as_user
61
- authenticated_by_url?
62
- end
63
-
64
- def authenticated_by_url?
65
- @authenticated_by_url
66
- end
67
-
68
- def send_subscribe_request(&block)
69
- subscribe_url = options[:subscribe_url]
70
- if subscribe_url
71
- debug [self.name, "sending subscribe request to", subscribe_url]
72
- Socky::NetRequest.post(subscribe_url, params_for_request, &block)
73
- true
74
- else
75
- yield true
76
- end
77
- end
78
-
79
- def send_unsubscribe_request(&block)
80
- unsubscribe_url = options[:unsubscribe_url]
81
- if unsubscribe_url
82
- debug [self.name, "sending unsubscribe request to", unsubscribe_url]
83
- Socky::NetRequest.post(unsubscribe_url, params_for_request, &block)
84
- else
85
- yield true
86
- end
87
- end
88
-
89
- def params_for_request
90
- {
91
- :client_id => client,
92
- :client_secret => secret,
93
- :channels => channels
94
- }.reject{|key,value| value.nil? || value.empty?}
95
- end
96
-
97
- end
98
- end
99
- end
@@ -1,67 +0,0 @@
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.collect do |connection|
48
- connection if (included_clients.empty? || included_clients.include?(connection.client)) && !excluded_clients.include?(connection.client)
49
- end.compact
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.collect 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.compact
64
- end
65
- end
66
- end
67
- end
@@ -1,85 +0,0 @@
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
@@ -1,74 +0,0 @@
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
@@ -1,27 +0,0 @@
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
@@ -1,39 +0,0 @@
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
@@ -1,79 +0,0 @@
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