socky 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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
+