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,95 @@
1
+ h1. Changelog
2
+
3
+ h2. 0.4.0 / 2010-10-27
4
+
5
+ * new features:
6
+ ** rename project to 'socky-server-ruby' for future compatibility
7
+ * bugfixes:
8
+ ** use fund_all in finders instead of collect in finders
9
+
10
+ h2. 0.2.1 / 2010-10-09
11
+
12
+ * new features:
13
+ ** default config should have subscribe url's commented out for starting users
14
+ * bugfixes:
15
+ ** show sent messages in true form instead of "inspect" one
16
+
17
+ h2. 0.2.0 / 2010-10-03
18
+
19
+ * new features:
20
+ ** inline documentation
21
+ * bugfixes:
22
+ ** send authentication after finishing handshake
23
+ ** tests less using stubs and more real cases
24
+ ** fix some tests
25
+
26
+ h2. 0.1.3 / 2010-09-23
27
+
28
+ * new features:
29
+ ** none
30
+ * bugfixes:
31
+ ** fix query :show_connections in ruby 1.9
32
+ ** fix user authentication when url subscribe/unsubscribe is disabled
33
+
34
+ h2. 0.1.2 / 2010-09-21
35
+
36
+ * new features:
37
+ ** none
38
+ * bugfixes:
39
+ ** fix routes to spec_helper in ruby 1.9.2
40
+ ** ruby 1.9 will no longer raise errors on String to_a call
41
+
42
+ h2. 0.1.1 / 2010-08-25
43
+
44
+ * new features:
45
+ ** socky has now support for signed certificates by :tls_options config option
46
+ * bugfixes:
47
+ ** none
48
+
49
+ h2. 0.1.0 / 2010-08-03
50
+
51
+ *IMPORTANT! This version will not work with plugin version lower than 0.1.0*
52
+
53
+ * new features:
54
+ ** server now send authentication status
55
+ ** new syntax
56
+ ** option to exclude clients/channels
57
+ * bugfixes:
58
+ ** none
59
+
60
+ h2. 0.0.9 / 2010-06-20
61
+
62
+ *IMPORTANT! This version will not work with plugin version lower than 0.0.9*
63
+
64
+ * new features:
65
+ ** support for next version of em-websocket debug options(based on mloughran branch)
66
+ ** support for websocket draft 76
67
+ ** support for wss/SSL connections
68
+ ** socky can now be started in 'daemon' mode
69
+ ** socky now sending all messages in JSON format
70
+ * bugfixes:
71
+ ** socky will no longer crash when when request query is invalid - i.e. chrome 6.0.*(with websocket draft76) and em-websocket 0.0.6
72
+
73
+ h2. 0.0.8 / 2010-06-06
74
+
75
+ * new features:
76
+ ** full rspec suite
77
+ ** allow admin param to be both "1" and "true"
78
+ * bugfixes:
79
+ ** proper configuration options parsing order
80
+ ** rescue from invalid config file
81
+ ** rescue from invalid args
82
+ ** symbolize_keys will no longer raise error if argument is not hash
83
+ ** modules will no longer depend on parent class
84
+ ** message will no longer raise exeption is JSON is invalid
85
+
86
+ h2. 0.0.7 / 2010-05-25
87
+
88
+ * new features:
89
+ ** user authentication is now async
90
+ * bugfixes:
91
+ ** server will no longer fail when starting from source instead of gem
92
+
93
+ h2. 0.0.6 / 2010-05-24
94
+
95
+ * initial release
@@ -0,0 +1,30 @@
1
+ Socky - server in Ruby
2
+ ===========
3
+
4
+ Socky is push server for Ruby based on WebSockets. It allows you to break border between your application and client browser by letting the server initialize a connection and push data to the client.
5
+
6
+ ## Getting Started
7
+
8
+ - [Install](http://github.com/socky/socky-server-ruby/wiki/install) the gem
9
+ - Read up about its [Usage](http://github.com/socky/socky-server-ruby/wiki/usage) and [Configuration](http://github.com/socky/socky-server-ruby/wiki/configuration)
10
+ - Try [Example Application](http://github.com/socky/socky-example) or [demo page](http://sockydemo.imanel.org)
11
+ - Fork and Contribute your own modifications
12
+ - See [sites using socky](http://github.com/socky/socky-server-ruby/wiki/sites)
13
+ - Discuss on [google group](http://groups.google.com/group/socky-framework)
14
+
15
+ ## Runtime Dependencies
16
+
17
+ - EM-WebSocket: Backend for WebSocket server
18
+ - EM-HTTP-Client: Sending authorize requests
19
+
20
+ ## License
21
+
22
+ (The MIT License)
23
+
24
+ Copyright (c) 2010 Bernard Potocki
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ CLEAN.include %w(**/*.{log,rbc})
4
+
5
+ require 'rspec/core/rake_task'
6
+
7
+ task :default => :spec
8
+
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ end
11
+
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gemspec|
15
+ gemspec.name = "socky-server"
16
+ gemspec.summary = "Socky is a WebSocket server and client for Ruby"
17
+ gemspec.description = "Socky is a WebSocket server and client for Ruby"
18
+ gemspec.email = "bernard.potocki@imanel.org"
19
+ gemspec.homepage = "http://imanel.org/projects/socky"
20
+ gemspec.authors = ["Bernard Potocki"]
21
+ gemspec.add_dependency 'em-websocket', '>= 0.1.4'
22
+ gemspec.add_dependency 'em-http-request'
23
+ gemspec.add_dependency 'json'
24
+ gemspec.add_development_dependency 'rspec', '~> 2.0'
25
+ gemspec.add_development_dependency 'rack'
26
+ gemspec.add_development_dependency 'mongrel'
27
+ gemspec.files.exclude ".gitignore"
28
+ end
29
+ rescue LoadError
30
+ puts "Jeweler not available. Install it with: gem install jeweler"
31
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.0
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require File.join(File.dirname(File.expand_path(__FILE__)), '..', 'lib', 'socky')
5
+ Socky::Runner.run
@@ -0,0 +1,15 @@
1
+ if defined?(EventMachine::WebSocket::Debugger)
2
+ klass = EventMachine::WebSocket::Debugger
3
+ else
4
+ klass = EventMachine::WebSocket::Connection
5
+ end
6
+
7
+ klass.class_eval do
8
+
9
+ def debug(*data)
10
+ if @debug
11
+ Socky.logger.debug "Socket " + data.flatten.collect{|line| line.to_s.gsub("\r\n","\n").gsub("\n","\\n")}.join(" ")
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,75 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+ require 'fileutils'
4
+ require 'em-websocket'
5
+ $:.unshift(File.dirname(__FILE__))
6
+ require 'em-websocket_hacks'
7
+
8
+ # Socky is a WebSocket server and client for Ruby on Rails
9
+ # @author Bernard "Imanel" Potocki
10
+ # @see http://github.com/imanel/socky_gem main repository
11
+ module Socky
12
+
13
+ class SockyError < StandardError; end #:nodoc:
14
+
15
+ # server version
16
+ VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').strip
17
+
18
+ class << self
19
+ # read server-wide options
20
+ def options
21
+ @options ||= {}
22
+ end
23
+
24
+ # write server-wide options
25
+ def options=(val)
26
+ @options = val
27
+ end
28
+
29
+ # access or initialize logger
30
+ # default logger writes to STDOUT
31
+ def logger
32
+ return @logger if defined?(@logger) && !@logger.nil?
33
+ path = log_path
34
+ FileUtils.mkdir_p(File.dirname(path))
35
+ prepare_logger(path)
36
+ rescue
37
+ prepare_logger(STDOUT)
38
+ end
39
+
40
+ # overwrite default logger
41
+ def logger=(logger)
42
+ @logger = logger
43
+ end
44
+
45
+ # default log path
46
+ def log_path
47
+ options[:log_path] || nil
48
+ end
49
+
50
+ # default pid path
51
+ def pid_path
52
+ options[:pid_path] || File.join(%w( / var run socky.pid ))
53
+ end
54
+
55
+ # default config path
56
+ def config_path
57
+ options[:config_path] || File.join(%w( / var run socky.yml ))
58
+ end
59
+
60
+ private
61
+
62
+ def prepare_logger(output)
63
+ @logger = Logger.new(output)
64
+ @logger.level = Logger::INFO unless options[:debug]
65
+ @logger
66
+ end
67
+ end
68
+ end
69
+
70
+ require 'socky/misc'
71
+ require 'socky/options'
72
+ require 'socky/runner'
73
+ require 'socky/connection'
74
+ require 'socky/net_request'
75
+ require 'socky/message'
@@ -0,0 +1,137 @@
1
+ require 'json'
2
+ require 'socky/connection/authentication'
3
+ require 'socky/connection/finders'
4
+
5
+ module Socky
6
+ # every connection to server creates one instance of Connection
7
+ class Connection
8
+ include Socky::Misc
9
+ include Socky::Connection::Authentication
10
+ extend Socky::Connection::Finders
11
+
12
+ # reference to connection socket
13
+ attr_reader :socket
14
+
15
+ class << self
16
+ # list of all connections in pool
17
+ # @return [Array] list of connections
18
+ def connections
19
+ @connections ||= []
20
+ end
21
+ end
22
+
23
+ # initialize new connection
24
+ # @param [WebSocket] socket valid connection socket
25
+ def initialize(socket)
26
+ @socket = socket
27
+ end
28
+
29
+ # read query data from socket request
30
+ # @return [Hash] hash of query data
31
+ def query
32
+ socket.request["Query"] || {}
33
+ end
34
+
35
+ # return if user appear to be admin
36
+ # this one should not be base to imply that user actually is
37
+ # admin - later authentication is needed
38
+ # @return [Boolean] is user claim to be admin?
39
+ def admin
40
+ ["true","1"].include?(query["admin"])
41
+ end
42
+
43
+ # client individual id - multiple connection can be organized
44
+ # by client id and later accessed at once
45
+ # @return [String] client id(might be nil)
46
+ def client
47
+ query["client_id"]
48
+ end
49
+
50
+ # client individual secret - used to check if user is admin
51
+ # and to sending authentication data to server(by subscribe_url)
52
+ # @return [String] client secret(might be nil)
53
+ def secret
54
+ query["client_secret"]
55
+ end
56
+
57
+ # client channel list - one client can have multiple channels
58
+ # every client have at last channel - if no channels are provided
59
+ # at default then user is assigned to nil channel
60
+ # @return [Array] list of client channels
61
+ def channels
62
+ @channels ||= query["channels"].to_s.split(",").collect(&:strip).reject(&:empty?)
63
+ @channels[0] ||= nil # Every user should have at last one channel
64
+ @channels
65
+ end
66
+
67
+ # check if client is valid and add him to pool or disconnect
68
+ def subscribe
69
+ debug [self.name, "incoming"]
70
+ subscribe_request
71
+ end
72
+
73
+ # remove client from pool and disconnect
74
+ def unsubscribe
75
+ debug [self.name, "terminated"]
76
+ unsubscribe_request
77
+ end
78
+
79
+ # check if client can send messages and process it
80
+ # @see Socky::Message.process
81
+ # @param [String] msg message to send in json format
82
+ def process_message(msg)
83
+ if admin && authenticated?
84
+ Socky::Message.process(self, msg)
85
+ else
86
+ self.send_message "You are not authorized to post messages"
87
+ end
88
+ end
89
+
90
+ # send message to client
91
+ # @param [Object] msg data to send(will be converted to json using to_json method)
92
+ def send_message(msg)
93
+ send_data({:type => :message, :body => msg})
94
+ end
95
+
96
+ # disconnect connection
97
+ def disconnect
98
+ socket.close_connection_after_writing
99
+ end
100
+
101
+ # convert connection to json(used in show_connection query)
102
+ # @param [Any] args it is required by different versions of ruby
103
+ # @return [JSon] id, client_id and channel list data
104
+ def to_json(*args)
105
+ {
106
+ :id => self.object_id,
107
+ :client_id => self.client,
108
+ :channels => self.channels.reject{|channel| channel.nil?}
109
+ }.to_json
110
+ end
111
+
112
+ private
113
+
114
+ def send_data(data)
115
+ json_data = data.to_json
116
+ debug [self.name, "sending data", json_data]
117
+ socket.send json_data
118
+ end
119
+
120
+ def connection_pool
121
+ self.class.connections
122
+ end
123
+
124
+ def in_connection_pool?
125
+ connection_pool.include?(self)
126
+ end
127
+
128
+ def add_to_pool
129
+ connection_pool << self unless self.admin || in_connection_pool?
130
+ end
131
+
132
+ def remove_from_pool
133
+ connection_pool.delete(self)
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,99 @@
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