socky 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,10 @@
1
+ socky_gem
2
+ ===========
3
+
4
+ Socky is a WebSocket server and client for Ruby on Rails
5
+
6
+ Please do not use it right now - it's unstable version so please wait for more stable(at last 0.1)
7
+
8
+ =======
9
+
10
+ Copyright (c) 2010 Bernard Potocki, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "socky"
5
+ gemspec.summary = "Socky is a WebSocket server and client for Ruby on Rails"
6
+ gemspec.description = "Socky is a WebSocket server and client for Ruby on Rails"
7
+ gemspec.email = "b.potocki@imanel.org"
8
+ gemspec.homepage = "http://github.com/imanel/socky_gem"
9
+ gemspec.authors = ["Bernard Potocki"]
10
+ gemspec.add_dependency('em-websocket', '>= 0.0.6')
11
+ gemspec.files.exclude ".gitignore"
12
+ end
13
+ rescue LoadError
14
+ puts "Jeweler not available. Install it with: gem install jeweler"
15
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.6
data/bin/socky ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'socky')
5
+ Socky::Runner.run
@@ -0,0 +1,11 @@
1
+ EventMachine::WebSocket::Connection.class_eval do
2
+
3
+ def debug(*data)
4
+ if @debug
5
+ data.each do |d|
6
+ Socky.logger.debug "Socket " + d.collect{|dd| dd.to_s.gsub("\r\n","\n").gsub("\n","\\n")}.join(" ")
7
+ end
8
+ end
9
+ end
10
+
11
+ end
data/lib/socky.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+ require 'fileutils'
4
+ require 'em-websocket'
5
+ require 'em-websocket_hacks'
6
+ $:.unshift(File.dirname(__FILE__))
7
+
8
+ module Socky
9
+
10
+ class SockyError < StandardError #:nodoc:
11
+ end
12
+
13
+ VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').strip
14
+
15
+ @@options = {}
16
+
17
+ class << self
18
+ def options
19
+ @@options
20
+ end
21
+
22
+ def options=(val)
23
+ @@options = val
24
+ end
25
+
26
+ def logger
27
+ return @@logger if defined?(@@logger) && !@@logger.nil?
28
+ FileUtils.mkdir_p(File.dirname(log_path))
29
+ @@logger = Logger.new(log_path)
30
+ @@logger.level = Logger::INFO unless options[:debug]
31
+ @@logger
32
+ rescue
33
+ @@logger = Logger.new(STDOUT)
34
+ @@logger.level = Logger::INFO unless options[:debug]
35
+ @@logger
36
+ end
37
+
38
+ def logger=(logger)
39
+ @@logger = logger
40
+ end
41
+
42
+ def log_path
43
+ options[:log_path] || File.join(%w( / var run socky.log ))
44
+ end
45
+
46
+ def config_path
47
+ options[:config_path] || File.join(%w( / var run socky.yml ))
48
+ end
49
+ end
50
+ end
51
+
52
+ require 'socky/misc'
53
+ require 'socky/options'
54
+ require 'socky/runner'
55
+ require 'socky/connection'
56
+ require 'socky/net_request'
57
+ require 'socky/server'
58
+ require 'socky/message'
@@ -0,0 +1,84 @@
1
+ require 'socky/connection/authentication'
2
+ require 'socky/connection/finders'
3
+
4
+ module Socky
5
+ class Connection
6
+ include Socky::Misc
7
+ include Socky::Connection::Authentication
8
+ include Socky::Connection::Finders
9
+
10
+ @@connections = []
11
+
12
+ attr_reader :socket
13
+
14
+ class << self
15
+ def connections
16
+ @@connections
17
+ end
18
+ end
19
+
20
+ def initialize(socket)
21
+ @socket = socket
22
+ end
23
+
24
+ def admin
25
+ socket.request["Query"]["admin"] == "1"
26
+ end
27
+
28
+ def client
29
+ socket.request["Query"]["client_id"]
30
+ end
31
+
32
+ def secret
33
+ socket.request["Query"]["client_secret"]
34
+ end
35
+
36
+ def channels
37
+ @channels ||= socket.request["Query"]["channels"].to_s.split(",").collect(&:strip).reject(&:empty?)
38
+ end
39
+
40
+ def subscribe
41
+ debug [self.name, "incoming"]
42
+ subscribe_request
43
+ end
44
+
45
+ def unsubscribe
46
+ debug [self.name, "terminated"]
47
+ unsubscribe_request
48
+ end
49
+
50
+ def process_message(msg)
51
+ if admin
52
+ Socky::Message.process(self, msg)
53
+ else
54
+ self.send_message "You are not authorized to post messages"
55
+ end
56
+ end
57
+
58
+ def send_message(msg)
59
+ debug [self.name, "sending message", msg.inspect]
60
+ socket.send msg
61
+ end
62
+
63
+ def disconnect
64
+ socket.close_connection_after_writing
65
+ end
66
+
67
+ def add_to_pool
68
+ @@connections << self unless self.admin || @@connections.include?(self)
69
+ end
70
+
71
+ def remove_from_pool
72
+ @@connections.delete(self)
73
+ end
74
+
75
+ def to_json
76
+ {
77
+ :id => self.object_id,
78
+ :client_id => self.client,
79
+ :channels => self.channels
80
+ }.to_json
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,72 @@
1
+ module Socky
2
+ class Connection
3
+ module Authentication
4
+
5
+ def subscribe_request
6
+ if authenticated?
7
+ debug [self.name, "authentication successed"]
8
+ add_to_pool
9
+ else
10
+ debug [self.name, "authentication failed"]
11
+ disconnect
12
+ end
13
+ end
14
+
15
+ def unsubscribe_request
16
+ if authenticated?
17
+ remove_from_pool
18
+ send_unsubscribe_request unless admin
19
+ end
20
+ end
21
+
22
+ def authenticated?
23
+ if @authenticated.nil?
24
+ @authenticated = (admin ? authenticate_as_admin : authenticate_as_user)
25
+ else
26
+ @authenticated
27
+ end
28
+ end
29
+
30
+ def authenticate_as_admin
31
+ true
32
+ end
33
+
34
+ def authenticate_as_user
35
+ authenticated_by_url?
36
+ end
37
+
38
+ def authenticated_by_url?
39
+ send_subscribe_request
40
+ end
41
+
42
+ def send_subscribe_request
43
+ subscribe_url = options[:subscribe_url]
44
+ if subscribe_url
45
+ debug [self.name, "sending subscribe request to", subscribe_url]
46
+ Socky::NetRequest.post(subscribe_url, params_for_request)
47
+ else
48
+ true
49
+ end
50
+ end
51
+
52
+ def send_unsubscribe_request
53
+ unsubscribe_url = options[:unsubscribe_url]
54
+ if unsubscribe_url
55
+ debug [self.name, "sending unsubscribe request to", unsubscribe_url]
56
+ Socky::NetRequest.post(unsubscribe_url, params_for_request)
57
+ else
58
+ true
59
+ end
60
+ end
61
+
62
+ def params_for_request
63
+ params = {}
64
+ params.merge!(:client_id => self.client) unless self.client.nil?
65
+ params.merge!(:client_secret => self.secret) unless self.secret.nil?
66
+ params.merge!(:channels => self.channels) unless self.channels.empty?
67
+ params
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,50 @@
1
+ module Socky
2
+ class Connection
3
+ module Finders
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def find_all
11
+ connections
12
+ end
13
+
14
+ def find(opts = {})
15
+ clients = opts[:clients].to_a
16
+ channels = opts[:channels].to_a
17
+ connections = find_all
18
+ connections = filter_by_clients(connections, clients) unless clients.empty?
19
+ connections = filter_by_channels(connections, channels) unless channels.empty?
20
+
21
+ connections
22
+ end
23
+
24
+ def find_by_clients(clients)
25
+ find(:clients => clients)
26
+ end
27
+
28
+ def find_by_channels(channels)
29
+ find(:channels => channels)
30
+ end
31
+
32
+ def find_by_clients_and_channels(clients, channels)
33
+ find(:clients => clients, :channels => channels)
34
+ end
35
+
36
+ private
37
+
38
+ def filter_by_clients(connections, clients)
39
+ connections.collect{ |con| con if clients.include? con.client }.compact
40
+ end
41
+
42
+ def filter_by_channels(connections, channels)
43
+ connections.collect{ |con| con if channels.any?{ |chan| con.channels.include?(chan) } }.compact
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,94 @@
1
+ require 'json'
2
+
3
+ module Socky
4
+ class Message
5
+ include Socky::Misc
6
+
7
+ class UnauthorisedQuery < Socky::SockyError #:nodoc:
8
+ end
9
+
10
+ class InvalidBroadcast < Socky::SockyError #:nodoc:
11
+ end
12
+
13
+ class InvalidQuery < Socky::SockyError #:nodoc:
14
+ end
15
+
16
+ attr_reader :params, :creator
17
+
18
+ class << self
19
+ def process(connection, message)
20
+ m = new(connection, message)
21
+ m.process
22
+ rescue SockyError => e
23
+ error connection.name, e
24
+ m.respond e.message
25
+ end
26
+ end
27
+
28
+ def initialize(creator, message)
29
+ @params = symbolize_keys JSON.parse(message)
30
+ @creator = creator
31
+ end
32
+
33
+ def process
34
+ debug [self.name, "processing", params.inspect]
35
+
36
+ verify_secret!
37
+
38
+ case params[:command].to_sym
39
+ when :broadcast then broadcast
40
+ when :query then query
41
+ end
42
+ end
43
+
44
+ def verify_secret!
45
+ raise(UnauthorisedQuery, "invalid secret") unless options[:secret].nil? || options[:secret] == params[:secret]
46
+ end
47
+
48
+ def broadcast
49
+ case params[:type].to_sym
50
+ when :to_clients then broadcast_to_clients
51
+ when :to_channels then broadcast_to_channels
52
+ when :to_clients_on_channels then broadcast_to_clients_on_channels
53
+ else raise InvalidQuery, "unknown broadcast type"
54
+ end
55
+ end
56
+
57
+ def broadcast_to_clients
58
+ verify :clients
59
+ Socky::Server.send_to_clients(params[:body], params[:clients])
60
+ end
61
+
62
+ def broadcast_to_channels
63
+ verify :channels
64
+ Socky::Server.send_to_channels(params[:body], params[:channels])
65
+ end
66
+
67
+ def broadcast_to_clients_on_channels
68
+ verify :clients
69
+ verify :channels
70
+ Socky::Server.send_to_clients_on_channels(params[:body], params[:clients], params[:channels])
71
+ end
72
+
73
+ def query
74
+ case params[:type].to_sym
75
+ when :show_connections then query_show_connections
76
+ else raise InvalidQuery, "unknown query type"
77
+ end
78
+ end
79
+
80
+ def query_show_connections
81
+ respond Socky::Connection.find_all
82
+ end
83
+
84
+ def verify(field)
85
+ params[field] = params[field].to_a unless params[field].is_a?(Array)
86
+ params[field] = params[field].collect(&:to_s)
87
+ end
88
+
89
+ def respond(message)
90
+ creator.send_message(message.to_json)
91
+ end
92
+
93
+ end
94
+ end
data/lib/socky/misc.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Socky
2
+ module Misc
3
+
4
+ def self.included(base)
5
+ base.extend Socky::Misc
6
+ end
7
+
8
+ def options
9
+ Socky.options
10
+ end
11
+
12
+ def options=(ob)
13
+ Socky.options = ob
14
+ end
15
+
16
+ def name
17
+ "#{self.class.to_s.split("::").last}(#{self.object_id})"
18
+ end
19
+
20
+ def log_path
21
+ Socky.log_path
22
+ end
23
+
24
+ def config_path
25
+ Socky.config_path
26
+ end
27
+
28
+ def info(args)
29
+ Socky.logger.info args.join(" ")
30
+ end
31
+
32
+ def debug(args)
33
+ Socky.logger.debug args.join(" ")
34
+ end
35
+
36
+ def error(name, error)
37
+ debug [name, "raised:", error.class, error.message]
38
+ end
39
+
40
+ def symbolize_keys(hash)
41
+ return self unless hash.is_a?(Hash)
42
+ hash.inject({}) do |options, (key, value)|
43
+ options[(key.to_sym rescue key) || key] = value
44
+ options
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,50 @@
1
+ require 'timeout'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'cgi'
6
+
7
+ module Socky
8
+ class NetRequest
9
+ include Socky::Misc
10
+
11
+ class << self
12
+
13
+ def post(url, params = { })
14
+ uri = URI.parse(url)
15
+ params = params_from_hash(params)
16
+ headers = {"User-Agent" => "Ruby/#{RUBY_VERSION}"}
17
+ begin
18
+ http = Net::HTTP.new(uri.host, uri.port)
19
+ if uri.scheme == 'https'
20
+ http.use_ssl = true
21
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
22
+ end
23
+ http.read_timeout = options[:timeout] || 5
24
+ resp, data = http.post(uri.path, params, headers)
25
+ return resp.is_a?(Net::HTTPOK)
26
+ rescue => e
27
+ debug ["Bad request", url.to_s, e.class, e.message]
28
+ return false
29
+ rescue Timeout::Error
30
+ debug [url.to_s, "timeout"]
31
+ return false
32
+ end
33
+ end
34
+
35
+ def params_from_hash(params)
36
+ result = []
37
+ params.each do |key, value|
38
+ if value.is_a? Array
39
+ value.each{ |v| result << "#{key}[]=#{CGI::escape(v.to_s)}"}
40
+ else
41
+ result << "#{key}=#{CGI::escape(value.to_s)}"
42
+ end
43
+ end
44
+ result.join('&')
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+ require 'erb'
4
+ require 'socky/options/config'
5
+ require 'socky/options/parser'
6
+
7
+ module Socky
8
+ module Options
9
+ include Socky::Options::Config
10
+ include Socky::Options::Parser
11
+
12
+ def prepare_options(argv)
13
+ self.options = {
14
+ :config_path => config_path
15
+ }
16
+
17
+ parse_options(argv)
18
+ read_config_file
19
+
20
+ self.options = {
21
+ :port => 8080,
22
+ :debug => false,
23
+ :deep_debug => false,
24
+ :log_path => log_path
25
+ }.merge(self.options)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ module Socky
2
+ module Options
3
+ module Config
4
+
5
+ def read_config_file
6
+ if !File.exists?(config_path)
7
+ puts "You must generate a config file (socky -g filename.yml)"
8
+ exit
9
+ end
10
+
11
+ self.options = YAML::load(ERB.new(IO.read(config_path)).result).merge!(self.options)
12
+ end
13
+
14
+ def generate_config_file
15
+ if File.exists?(config_path)
16
+ puts "Config file already exists. You must remove it before generating a new one."
17
+ exit
18
+ end
19
+ puts "Generating config file...."
20
+ File.open(config_path, 'w+') do |file|
21
+ file.write DEFAULT_CONFIG_FILE
22
+ end
23
+ puts "Config file generated at #{config_path}"
24
+ exit
25
+ end
26
+
27
+ DEFAULT_CONFIG_FILE= <<-EOF
28
+ :port: 8080
29
+ :debug: false
30
+
31
+ :subscribe_url: http://localhost:3000/socky/subscribe
32
+ :unsubscribe_url: http://localhost:3000/socky/unsubscribe
33
+
34
+ :secret: my_secret_key
35
+ EOF
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ module Socky
2
+ module Options
3
+ module Parser
4
+
5
+ def parse_options(argv)
6
+ OptionParser.new do |opts|
7
+ opts.summary_width = 25
8
+ opts.banner = "Usage: socky [options]\n"
9
+
10
+ opts.separator ""
11
+ opts.separator "Configuration:"
12
+
13
+ opts.on("-g", "--generate FILE", String, "Generate config file") do |v|
14
+ options[:config_path] = File.expand_path(v) if v
15
+ generate_config_file
16
+ end
17
+
18
+ opts.on("-c", "--config FILE", String, "Path to configuration file.", "(default: #{Socky.config_path})") do |v|
19
+ options[:config_path] = File.expand_path(v)
20
+ end
21
+
22
+ opts.separator ""; opts.separator "Network:"
23
+
24
+ opts.on("-p", "--port PORT", Integer, "Specify port", "(default: 8080)") do |v|
25
+ options[:port] = v
26
+ end
27
+
28
+ opts.separator ""; opts.separator "Logging:"
29
+
30
+ opts.on("-l", "--log FILE", String, "Path to print debugging information.", "(default: #{Socky.log_path})") do |v|
31
+ options[:log_path] = File.expand_path(v)
32
+ end
33
+
34
+ opts.on("--debug", "Run in debug mode") do
35
+ options[:debug] = true
36
+ end
37
+
38
+ opts.on("--deep-debug", "Run in debug mode that is even more verbose") do
39
+ options[:debug] = true
40
+ options[:deep_debug] = true
41
+ end
42
+
43
+ opts.separator ""; opts.separator "Miscellaneous:"
44
+
45
+ opts.on_tail("-?", "--help", "Display this usage information.") do
46
+ puts "#{opts}\n"
47
+ exit
48
+ end
49
+
50
+ opts.on_tail("-v", "--version", "Display version") do |v|
51
+ puts "Socky #{VERSION}"
52
+ exit
53
+ end
54
+ end.parse!(argv)
55
+ options
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,45 @@
1
+ module Socky
2
+ class Runner
3
+ include Socky::Misc
4
+ include Socky::Options
5
+
6
+ class << self
7
+ def run(argv = ARGV)
8
+ self.new(argv)
9
+ end
10
+ end
11
+
12
+ def initialize(argv = ARGV)
13
+ prepare_options(argv)
14
+ start
15
+ end
16
+
17
+ def start
18
+ EventMachine.epoll
19
+
20
+ EventMachine.run do
21
+
22
+ trap("TERM") { stop }
23
+ trap("INT") { stop }
24
+
25
+ EventMachine::start_server("0.0.0.0", options[:port],
26
+ EventMachine::WebSocket::Connection, :debug => options[:deep_debug]) do |ws|
27
+
28
+ connection = Socky::Connection.new(ws)
29
+ ws.onopen { connection.subscribe }
30
+ ws.onmessage { |msg| connection.process_message(msg) }
31
+ ws.onclose { connection.unsubscribe }
32
+
33
+ end
34
+
35
+ info ["Server started"]
36
+ end
37
+ end
38
+
39
+ def stop
40
+ info ["Server stopping"]
41
+ EventMachine.stop
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ module Socky
2
+ class Server
3
+ include Socky::Misc
4
+
5
+ class << self
6
+
7
+ def send_to_clients(message, clients)
8
+ connections = Socky::Connection.find_by_clients(clients)
9
+ send_data(message, connections)
10
+ end
11
+
12
+ def send_to_channels(message, channels)
13
+ connections = Socky::Connection.find_by_channels(channels)
14
+ send_data(message, connections)
15
+ end
16
+
17
+ def send_to_clients_on_channels(message, clients, channels)
18
+ connections = Socky::Connection.find_by_clients_and_channels(clients, channels)
19
+ send_data(message, connections)
20
+ end
21
+
22
+ def send_data(message, connections)
23
+ connections.each{|c| c.send_message message}
24
+ end
25
+
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: socky
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 6
9
+ version: 0.0.6
10
+ platform: ruby
11
+ authors:
12
+ - Bernard Potocki
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-24 00:00:00 +02:00
18
+ default_executable: socky
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: em-websocket
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 0
30
+ - 6
31
+ version: 0.0.6
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Socky is a WebSocket server and client for Ruby on Rails
35
+ email: b.potocki@imanel.org
36
+ executables:
37
+ - socky
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README
42
+ files:
43
+ - README
44
+ - Rakefile
45
+ - VERSION
46
+ - bin/socky
47
+ - lib/em-websocket_hacks.rb
48
+ - lib/socky.rb
49
+ - lib/socky/connection.rb
50
+ - lib/socky/connection/authentication.rb
51
+ - lib/socky/connection/finders.rb
52
+ - lib/socky/message.rb
53
+ - lib/socky/misc.rb
54
+ - lib/socky/net_request.rb
55
+ - lib/socky/options.rb
56
+ - lib/socky/options/config.rb
57
+ - lib/socky/options/parser.rb
58
+ - lib/socky/runner.rb
59
+ - lib/socky/server.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/imanel/socky_gem
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.6
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Socky is a WebSocket server and client for Ruby on Rails
90
+ test_files: []
91
+